Alexa Gadget Skill API: Using the SetLight Directive

In a previous post, we began developing an Alexa Gadget Skill and set up a simple roll call dialog. One thing that our sample could really benefit from is providing visual feedback to the user when the Echo Buttons are pressed or selected during the roll call process. The Alexa Gadget Skills API gives us control over each gadget’s light. In this post we dive into and have our skill take advantage of this functionality.

Exploring the SetLight Directive

So far, we have been working with the GameEngineInterface. This interface allows us to set input handlers on gadgets and to receive users’ gadget input events. The interface that lets us control the device itself is the GadgetControllerInterface. This interface contains one directive called SetLight and one request called System.ExceptionEncountered. The System.ExceptionEncountered request is sent to our skill when the SetLight directive has failed for whatever reason. In this post, we focus look at the SetLight directive.

The SetLight directive allows developers to set animations on the devices discovered during the roll call process. The following is an example of the directive:


 {
   "type": "GadgetController.SetLight",
   "version": 1,
   "targetGadgets": [ "gadgetId1", "gadgetId2" ],
   "parameters": {
      "triggerEvent": "none",
      "triggerEventTimeMs": 0,
      "animations": [ 
        {
          "repeat": 1,
          "targetLights": ["1"],
          "sequence": [ 
           {
              "durationMs": 10000,
              "blend": false,
              "color": "0000FF"
           }
          ] 
        }
      ]
    }
 }

The directive can act on either a specified collection of gadgets or, if the targetGadgets array remains empty, all paired gadgets. The parameters field contains the details of what event triggers the animation and the animation’s definition. The triggerEvent can be a button up, down or none, in which case the animation begins playing immediately. triggerEventTimeMs is a delay in milliseconds after the event occurs before the animation begins. The animations object includes instructions on how many times to repeat a sequence (repeat field), which lights on the gadget animation is for (targetLights field) and a list of step by step instructions on how to execute the animation (sequence field). Each sequence step has a duration in milliseconds, a color in HEX without the # character and a blend flag indicating whether the device should interpolate the color from its current state to the step color. A few additional items to note.

  • The targetLights array is simply [ "1" ] because the Echo Buttons have one light. Future gadgets might have more. This field will provide fine tuned control over each light when those gadgets come out.
  • The number of sequence steps allowed is limited by the length of the targetGadgets array. The formula for the limit is: 38 - targetGadgets.length * 3. Of course, that might be subject to change, so please consult the official docs.
  • Each Echo Button can have one animation set per trigger. Any directive that sends a different animation for a trigger will overwrite whatever animation was set before.

Let us now turn our attention back to the roll call code. We would like to do the following:

  1. Set all lights to an animation when the skill launches.
  2. Set all lights to some color and fade out when the roll call initializes.
  3. Set a light to a solid color if a button has been selected.
  4. Once roll call is finished, set the buttons that are not used to black and set the used buttons to some animation indicating that they are in the game.

We first create something to help us build the animations JSON. As it turns out, the trivia game sample has a really cool animations helper that we can use. I went ahead and translated it to TypeScript. The helper now has two modules: BasicAnimations and ComplexAnimations. BasicAnimations contains many functions such as setting a static color (SolidAnimation), fade in/out (FadeInAnimation/FadeOutAnimation) or alternating between a color and black (BlinkAnimation). ComplexAnimations contains two functions, one of which is SpectrumAnimation, an animation that takes the light through any number of color transitions. The code for all of these is fairly easy to follow. Here is what two of them look like.


export module BasicAnimations {
    export function SolidAnimation(cycles: number, color: string, duration: number): Array {
        return [
            {
                "repeat": cycles,
                "targetLights": ["1"],
                "sequence": [
                    {
                        "durationMs": duration,
                        "blend": false,
                        "color": ColorHelper.validateColor(color)
                    }
                ]
            }
        ];
    }
    ...
    export function BlinkAnimation(cycles: number, color: string): Array {
        return [
            {
                "repeat": cycles,
                "targetLights": ["1"],
                "sequence": [
                    {
                        "durationMs": 500,
                        "blend": false,
                        "color": ColorHelper.validateColor(color)
                    }, {
                        "durationMs": 500,
                        "blend": false,
                        "color": "000000"
                    }
                ]
            }
        ];
    }
    ...
}

export module ComplexAnimations {
    export function SpectrumAnimation(cycles: number, color: string[]): Array {
        let colorSequence = [];
        for (let i = 0; i < color.length; i++) {

            colorSequence.push({
                "durationMs": 400,
                "color": ColorHelper.validateColor(color[i]),
                "blend": true
            });
        }
        return [
            {
                "repeat": cycles,
                "targetLights": ["1"],
                "sequence": colorSequence
            }
        ];
    }
    ...
}

Here is the generated JSON:


// Solid Animation
[
  {
    "repeat": 1,
    "targetLights": [
      "1"
    ],
    "sequence": [
      {
        "durationMs": 2000,
        "blend": false,
        "color": "ff0000"
      }
    ]
  }
]
// Fade In Animation
[
  {
    "repeat": 4,
    "targetLights": [
      "1"
    ],
    "sequence": [
      {
        "durationMs": 1,
        "blend": true,
        "color": "000000"
      },
      {
        "durationMs": 1000,
        "blend": true,
        "color": "ffd400"
      }
    ]
  }
]
// Spectrum Animation
[
  {
    "repeat": 3,
    "targetLights": [
      "1"
    ],
    "sequence": [
      {
        "durationMs": 400,
        "color": "ff0000",
        "blend": true
      },
      {
        "durationMs": 400,
        "color": "0000ff",
        "blend": true
      },
      {
        "durationMs": 400,
        "color": "00ff00",
        "blend": true
      },
      {
        "durationMs": 400,
        "color": "ffffff",
        "blend": true
      }
    ]
  }
]

As a next step, we ensure that our skill has a set of reusable animations. We create a module called SkillAnimations for this purpose. We create one method per distinct animation that we want to send to our users. The module looks like this:


export module SkillAnimations {
    ...
    export function rollCallInitialized(): Array {
        return BasicAnimations.CrossFadeAnimation(1, "yellow", "black", 5000, 15000);
    }

    export function rollCallButtonSelected(): Array {
        return BasicAnimations.SolidAnimation(1, "orange", 300);
    }
    ...
}

Lastly, we create a SetLightDirectiveBuilder module to build the SetLight directive instances. The goal of this code is to generate the directives given an animation, an optional array of targetGadgetIds, the triggering event and the delay.


export module SetLightDirectiveBuilder {
    ...
    function setLightImpl(animations: Array,
        on: services.gadgetController.TriggerEventType,
        targetGadgets?: string[],
        delayInMs?: number): interfaces.gadgetController.SetLightDirective {
        const result: interfaces.gadgetController.SetLightDirective = {
            type: "GadgetController.SetLight",
            version: 1,
            targetGadgets: targetGadgets,
            parameters: {
                triggerEvent: on,
                triggerEventTimeMs: delayInMs,
                animations: animations
            }
        };
        return result;
    }
}

We create three helpers so we do not have to pass the TiggerEventType parameter. A minor convenience.


export module SetLightDirectiveBuilder {
    export function setLight(animations: Array,
        targetGadgets?: string[],
        delayInMs?: number): interfaces.gadgetController.SetLightDirective {
        return setLightImpl(animations, "none", targetGadgets, delayInMs);
    }

    export function setLightOnButtonDown(animations: Array,
        targetGadgets?: string[],
        delayInMs?: number): interfaces.gadgetController.SetLightDirective {
        return setLightImpl(animations, "buttonDown", targetGadgets, delayInMs);
    }

    export function setLightOnButtonUp(animations: Array,
        targetGadgets?: string[],
        delayInMs?: number): interfaces.gadgetController.SetLightDirective {
        return setLightImpl(animations, "buttonUp", targetGadgets, delayInMs);
    }
    ...
}

Now, we can call the code:


    SetLightDirectiveBuilder.setLightOnButtonDown(
        BasicAnimations.FadeInAnimation(1, "yellow", 500), ["left", "right"])

and receive the following JSON to send back as part of our response from a skill.


{
  "type": "GadgetController.SetLight",
  "version": 1,
  "targetGadgets": [
    "left",
    "right"
  ],
  "parameters": {
    "triggerEvent": "buttonDown",
    "animations": [
      {
        "repeat": 1,
        "targetLights": [
          "1"
        ],
        "sequence": [
          {
            "durationMs": 1,
            "blend": true,
            "color": "000000"
          },
          {
            "durationMs": 500,
            "blend": true,
            "color": "ffd400"
          }
        ]
      }
    ]
  }
}

Excellent! Let us integrate these new features into our skill to see the lights in action!

Fire It Up

We’ve actually done most of the work we needed to do already. The Roll Call code we had created in the previous part of this series is in a state where we can easily add the SetLight directive. The LaunchHandler changes only to add directives. Note that not only do we add the skillLaunch animation, which cycles through white, purple, and yellow, for 5 cycles. We also add the default button up and down animations. We do this so that whenever a user presses any button, we provide some sort of color feedback.


return handlerInput.responseBuilder
    .speak(resp.speech)
    .reprompt(resp.reprompt)
    .addDirective(SetLightDirectiveBuilder.setLightOnButtonDown(SkillAnimations.buttonDown()))
    .addDirective(SetLightDirectiveBuilder.setLightOnButtonDown(SkillAnimations.buttonUp()))
    .addDirective(SetLightDirectiveBuilder.setLight(SkillAnimations.skillLaunch()))
    .getResponse();

The other interesting change is specific to feedback when a button is selected during the roll call. Recall, when a user pressed the first button in the roll call, our skill acknowledges this via a voice response and asks the user to press the second button. We would like to have the light perform an animation at this point; a good visual cue for the user to know which buttons are selected and which are not.

We modify the handleButtonCheckin function in the RollCall module to send the directive for each gadgetId that was selected in the current request. We also do the math to ensure that the button count is reflected correctly if the skill received multiple button inputs simultaneously. I’m not certain this can actually occur, but since inputEvents is an array… better safe than sorry.


export module RollCall {
    ...
    export function handleButtonCheckin(handlerInput: HandlerInput, inputEvents: Array): Response {
        const sessionAttr = handlerInput.attributesManager.getSessionAttributes();


        const directives = inputEvents.map(ev => {
            const gadgetIds = getGadgetIds(ev);
            return SetLightDirectiveBuilder.setLight(SkillAnimations.rollCallButtonSelected(), gadgetIds);
        });

        sessionAttr.rollcallButtonsCheckedIn += directives.length;
        handlerInput.attributesManager.setSessionAttributes(sessionAttr);
        const resp = LocalizedStrings.rollcall_checkin(numOfButtons - sessionAttr.rollcallButtonsCheckedIn);

        let temp = handlerInput.responseBuilder.speak(resp.speech);
        directives.forEach(p => temp.addDirective(p));
        return temp.getResponse();
    }

    function getGadgetIds(ev: services.gameEngine.InputHandlerEvent): string[] {
        const btns = ev!.inputEvents!.map(p => { return p.gadgetId!; });
        return btns;
    }
    ...
}

Beyond that, it’s smooth sailing. You can deploy this code into a skill by using ask deploy. Here is a short video of the current code working on my desk.

Code can be found in the Github repo.