Learn How to Combine the Smart Home API and Custom API in Your Skills with Multi-Capability Skills (MCS)

German Viscuso Jun 21, 2020
Share:
Smart Home Skills Inspiration Tips & Tools Smart Home Tutorial
Blog_Header_Post_Img

In this guide, you will learn how to create a multi-capability skill (MCS) that supports both a Custom and a Smart Home model as part of the same skill. Combining your skill models with MCS let's your expand the functionalities and control of your smart home devices by adding support for additional custom skill interactions.

Create Higher Quality Device Experiences with MCS

A multi-capability skill (MCS) is a skill that combines the Custom skill APIs with pre-built Smart Home API into a single skill. Previously, when creating a new skill, developers would have to build and manage two separate distinct skills in order to support a broader range of features and utterances for their devices. With MCS, developers can now maintain a single skill that supports pre-built and custom interaction models. 

You can use the Alexa Skill Management API (SMAPI), the ASK-CLI, or the Alexa Developer Console (as shown here) to create a new multi-capability skill or to upgrade your existing smart home or custom skill. Adding the new API in the skill manifest file (skill.json) and updating/deploying using SMAPI or the CLI will create an MCS. The Models section in Developer console also allows you to upgrade your skill to MCS.

Use a Single Lambda Function to Support Multiple Models

When you create an an MCS skill via SMAPI or the CLI, you need to ensure you support a back-end that satisfies more than one skill model. You can use a single Lambda function to process requests coming from the different sides of a MCS, for example smart home requests such as Discovery directives and Custom requests such as Launch or Intent requests.  In order to use a single Lambda function, you'll need to provide the same Amazon Resource Name (ARN) of your deployed Lambda function to both the Smart Home model settings and the Custom model settings.

In the Smart Home skill settings the ARN has to be provided here:

multi_capability_screen1

Additionally, you are required to configure Account Linking in your Smart Home skill settings. For more information on configuring account linking for smart home skills using MCS, see our step-by-step tutorial.

In the Custom skill settings the ARN has to provided in this section:

multi_capability_screen

Note: if you are using an Alexa-hosted Skill, you can copy the ARN and use it as shown above.

Next, you need to route the appropriate type of request in the Lambda depending on which skill model they come from. If you are working with a Node.js based Lambda, you can have a main entry point as index.js and detect if the incoming request is a Smart Home directive or a Custom skill request as part of a session. Then you can define separate sub-modules to process each type of request such as indexDirective.js and indexSession.js.

 

The below sample code shows an example MCS skill for creating a virtual beeper device that supports the functionality of turning on and off. In this case, the main entry point of the Lambda (index.js) would be as follows:

Copied to clipboard
const IndexDirective = require('indexDirective');
const IndexSession = require('indexSession');

exports.handler = async function(request, context) {

    let response;

    if ('directive' in request) {
        console.log("----- Routing Directive Handler");
        response = await IndexDirective.handler(request, context);

        let properties = ((response || {}).context || {}).properties;
        if (!!properties && properties[0].name === "powerState") {
            if (properties[0].value === "ON")
                await sendMessage("BEEPER_ON");
            else
                await sendMessage("BEEPER_OFF");
        }
    }

    if ('session' in request) {
        console.log("----- Routing Session Handler");
        response = await IndexSession.handler(request, context);

        // Handle any command passed back from a the custom handler
        let command = ((response || {}).sessionAttributes || {}).command;
        if (!!command) {
            await sendMessage(command);
        }
    }

    return response;
};

If you already created Lambda code to process your custom skill requests (such as LaunchRequest, IntentRequest, etc) you can copy it into indexSession.js and use session attributes to pass back commands to your device:

Copied to clipboard
const BeepIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'BeepIntent';
    },
    handle(handlerInput) {
        const speechText = 'Beeping!';

        handlerInput.attributesManager.setSessionAttributes({"command":"BEEP"});

        return handlerInput.responseBuilder
            .speak(speechText)
            .withSimpleCard('Beeping!', speechText)
            .getResponse();
    }
};

Next, in indexDirective.js, you will host your classic Smart Home lambda code to process the API directives. In this case, we're responding to Authorization and Discovery directives and supporting the PowerController interface so the virtual beeper can be turned on/off:

Copied to clipboard
    if (namespace === 'Alexa.Authorization') {
        let aar = new AlexaResponse({"namespace": "Alexa.Authorization", "name": "AcceptGrant.Response",});
        return aar.get();
    }

    if (namespace === 'Alexa.Discovery') {
        let adr = new AlexaResponse({"namespace": "Alexa.Discovery", "name": "Discover.Response"});
        let capability_alexa = adr.createPayloadEndpointCapability();
        let capability_alexa_customintent = adr.createPayloadEndpointCapability({"interface": "Alexa.CustomIntent", "supportedIntents": [{"name": "BeepIntent"}]});
        let capability_alexa_powercontroller = adr.createPayloadEndpointCapability({"interface": "Alexa.PowerController", "supported": [{"name": "powerState"}]});
        let capabilities = [capability_alexa, capability_alexa_customintent, capability_alexa_powercontroller];
        adr.addPayloadEndpoint({"endpointId": "beeper", "friendlyName": "Beeper", "description": "A computer beeper", "capabilities": capabilities});
        return adr.get();
    }

    if (namespace === 'Alexa.PowerController') {

        let power_state_value = "OFF";
        if (request.directive.header.name === "TurnOn")
            power_state_value = "ON";

        let endpoint_id = request.directive.endpoint.endpointId;
        let token = request.directive.endpoint.scope.token;
        let correlationToken = request.directive.header.correlationToken;

        let ar = new AlexaResponse(
            {
                "correlationToken": correlationToken,
                "token": token,
                "endpointId": endpoint_id
            }
        );
        ar.addContextProperty({"namespace":"Alexa.PowerController", "name": "powerState", "value": power_state_value});

        return ar.get();
    }

In the sample code above, you may have noticed an extra interface in the Alexa.Discovery namespace: Alexa.CustomIntent. This interface enables you to use the Smart Home skill discovery process to associate custom intents with device endpoints. A customer can then use the intents with that device by including the device's invocation name when they make an Alexa request. This provides a more seamless and consistent experience customers, as a custom intent can be triggered by a non-custom invocation (e.g. one without "open"). With this, you can extend the ways customers can talk to your devices beyond what's supported in the pre-built Smart Home APIs interaction model.

To learn more, follow the step-by-step guide with instructions on how to build a sample Multi-capability skill that controls a virtual beeper.

Resources

Subscribe