Today’s post comes from J. Michael Palermo IV, Sr. Evangelist at Amazon Alexa. You will learn the process of device discovery and how to support it in code for your smart home skill.
Developing a smart home skill is different than building a custom skill. One of the main differences is the dependency on devices to control. The device might be a light bulb, thermostat, hub, or other device that can be controlled via a cloud based API. Or maybe you created an innovative IoT gadget and you want to make it discoverable by an Alexa enabled device. In this post, you will learn how the process of device discovery works, and how you can support discovery in your custom skill adapter communicating with the Smart Home Skill API.
To meet prerequisites and set the context of the technical information in this post, start by reading the five steps before developing a smart home skill and set up your initial code to support skill adapter directive communications. This post will be the next in the series of these posts and provides the foundation for code samples to follow.
To appreciate the role of device discovery, consider how a customer is involved in the process. The following steps assume a consumer has an Alexa-enabled device, such as the Echo or Echo Dot, already set up.
Once the first step is completed, the customer is able to control the smart home device typically through an app provided by the device maker, which is graphical user interface that manages device and owner information controlled in it’s own device cloud. The account created in the first step is the same account used in the second step when the consumer enables the associated smart home skill. This explains why account linking is mandatory for skills created with the Smart Home Skill API.
But what happens in the third step when the consumer makes a device discovery request? Does it actually seek for devices emitting some signal within the home? Is it querying everything it can within the local WIFI area? The answer to both questions is no. Although there are a couple of exceptions to enable early support of popular products such as Philips Hue and Belkin WeMo, the process described next is what is supported today and moving forward.
Figure 1: Device discovery process
When a request is made by the customer for devices to be discovered, the Alexa service identifies all the smart home skills associated with the consumers account, and makes a discover request to each one as seen here.
Let’s examine each step above in more detail. Notice the first step is the same as the last step we covered when considering the customer’s perspective, so this is a deeper dive as to what happens next. Also observe in Figure 1 that no communications occur directly between the Amazon Echo and the smart home device.
Once the skill adapter receives information regarding devices, it responds with a directive to the Smart Home Skill API with the device information received from the device cloud. The Alexa app will reveal all the devices discovered.
The discovery of devices is made by querying skill adapters for information, not by direct communication with the devices. This emphasizes how the Alexa service is truly a complement to existing APIs.
When a skill adapter makes a call to the device cloud API requesting all devices associated with a customer’s account, it needs to satisfy the following information for each device:
Property |
Description |
applianceId |
A device identifier. The identifier must be unique across all devices owned by an end user within the domain for the skill adapter. In addition, the identifier needs to be consistent across multiple discovery requests for the same device. An identifier can contain any letter or number and the following special characters: _ - = # ; : ? @ &. The identifier must be between 1 and 256 characters long. |
manufacturerName |
The name of the device manufacturer. This value must be between 1 and 128 characters long. |
modelName |
Device model name. This value must be between 1 and 128 characters long. |
version |
The vendor-provided version of the device. This value must be between 1 and 128 characters long. |
friendlyName |
The name used by the customer to identify the device. This value must be between 1 and 128 characters long. |
friendlyDescription |
A human-readable description of the device. This value must be between 1 and 128 characters long. The description should contain a description of how the device is connected. For example, "WiFi Thermostat connected via Wink". |
isReachable |
true to indicate the device is currently reachable; otherwise, false. |
actions |
An array of actions that this device supports. Valid actions for this directive are: setTargetTemperature, incrementTargetTemperature, decrementTargetTemperature, setPercentage, incrementPercentage, decrementPercentage, turnOff, turnOn. |
additionalApplianceDetails |
String name/value pairs that provide additional information about a device for use by the skill adapter. The contents of this property cannot exceed 5000 bytes. Also, the Smart Home Skill API does not understand or use this data. |
Whether or not the device cloud stores this information in this manner, it is the job of the skill adapter to make sure these properties are satisfied. Once the information is received from the device cloud, the skill adapter code will populate the response directive payload in JSON format as seen in this example:
"discoveredAppliances": [{ "applianceId": "sample-1", "manufacturerName": "Sample Manufacturer", "modelName": "Sample Thermostat", "version": "1", "friendlyName": "Sample Thermostat", "friendlyDescription": "Thermostat by Sample Manufacturer", "isReachable": true, "actions": [ "setTargetTemperature", "incrementTargetTemperature", "decrementTargetTemperature" ], "additionalApplianceDetails": { "extraDetail1": "This is a thermostat that is reachable" } }]
The code above is an example of a single item array where only one device is returned. Because the device is a thermostat, the supported actions are consistent with its behavior. Any additional details at the end are not needed for processing.
In a previous blog post, a number of supporting functions was defined to provide a simple workflow for handling common smart home skill tasks. One of the functions created was named handleDiscovery and is called by the entry function if an incoming request was determined to be device discovery. The function creates a response directive containing a payload returning an empty array as seen here.
var handleDiscovery = function(event) { var header = createHeader(NAMESPACE_DISCOVERY, RESPONSE_DISCOVER); var payload = { "discoveredAppliances": [] }; return createDirective(header,payload); }// handleDiscovery
Let’s change this function to return device information. Rewrite the above code so it appears as shown here.
var handleDiscovery = function(event, context) { var header = createHeader(NAMESPACE_DISCOVERY,RESPONSE_DISCOVER); // initialize empty payload var payload = {}; if (event.header.name == REQUEST_DISCOVER) { // function to retrieve device info from device cloud var appliances = getAppliances(event); payload = { "discoveredAppliances": appliances }; } return createDirective(header,payload); }// handleDiscovery
In addition to initializing an empty payload and validating the request directive is for discovery, the code introduces a call to a function named getAppliances. This passes in the event argument, which can be used to obtain the access token associated with the customer. This function has not be defined yet, but in context of code it is clear it is responsible for returning the JSON formatted array of devices. The comment preceding it states the function retrieves the information from the device cloud. Because each device cloud will have its own means to access it, add the code below to simulate a call by simply returning static device data.
var getAppliances = function(event) { // var accessToken = event.payload.accessToken return [{ "applianceId": "sample-1", "manufacturerName": "Sample Manufacturer", "modelName": "Sample Thermostat", "version": "1", "friendlyName": "Sample Thermostat", "friendlyDescription": "Thermostat by Sample Manufacturer", "isReachable": true, "actions": [ "setTargetTemperature", "incrementTargetTemperature", "decrementTargetTemperature" ], "additionalApplianceDetails": { "extraDetail1": "This is a thermostat that is reachable" }}, { "applianceId": "sample-2", "manufacturerName": "Sample Manufacturer", "modelName": "Sample Dimmer", "version": "1", "friendlyName": "Sample Dimmer", "friendlyDescription": "Dimmer by Sample Manufacturer", "isReachable": true, "actions": [ "turnOn", "turnOff", "setPercentage", "incrementPercentage", "decrementPercentage" ], "additionalApplianceDetails": { "extraDetail1": "This is a dimmer that is reachable" }}, { "applianceId": "sample-3", "manufacturerName": "Sample Manufacturer", "modelName": "Sample Switch", "version": "1", "friendlyName": "Sample Switch", "friendlyDescription": "Switch by Sample Manufacturer", "isReachable": true, "actions": [ "turnOn", "turnOff" ], "additionalApplianceDetails": { "extraDetail1": "This is a switch that is reachable" }}, { "applianceId": "sample-4", "manufacturerName": "Sample Manufacturer", "modelName": "Sample Fan", "version": "1", "friendlyName": "Sample Fan", "friendlyDescription": "Fan by Sample Manufacturer", "isReachable": true, "actions": [ "turnOn", "turnOff", "setPercentage", "incrementPercentage", "decrementPercentage" ], "additionalApplianceDetails": { "extraDetail1": "This is a fan that is reachable" }}, { "applianceId": "sample-5", "manufacturerName": "Sample Manufacturer", "modelName": "Sample Switch", "version": "1", "friendlyName": "Sample Switch Unreachable", "friendlyDescription": "Switch by Sample Manufacturer", "isReachable": false, "actions": [ "turnOn", "turnOff", ], "additionalApplianceDetails": { "extraDetail1": "This is a switch that is not reachable and should show as offline in the Alexa app" } }]; }// getSampleAppliances
The above function will always return the same five devices no matter who calls it. Note the comment in the first line that reveals how to obtain the value of the accessToken which is needed when making an actual call to a device cloud to associate the call with a specific customer. If you don’t want to return static data, replace the content in the function above with the code required to call your device cloud. Consult your devlce cloud documentation for support.
To confirm your skill adapter is receiving and responding appropriately to device discovery requests, test the behavior in the AWS console. With your code updated to reflect the changes made in this post, click on the “Actions” button above your code and select “Configure test event.”
Figure 2 : Configure test event
Choose any sample event template from the dropdown menu you don’t mind changing. Replace the content with the directive example shown here.
{ "header" : { "messageId" : "6d6d6e14-8aee-473e-8c24-0d31ff9c17a2", "name" : "DiscoverAppliancesRequest", "namespace" : "Alexa.ConnectedHome.Discovery", "payloadVersion" : "2" }, "payload" : { "accessToken" : "acc355t0ken" } }
After clicking the “Save and test” button, you should get results similar to what is shown here.
Figure 3 : Device discovery response
The response payload contains the array of discovered devices.
In this post you gained insight on how device discover works. You learned that device discovery is managed at the skill adapter and ultimately receives the information from a device cloud API, though this demonstrated static content. You also observed how to test if device discovery is working in your skill adapter.
In my next blog post, I’ll continue the smart home journey by showing how to write code to control devices. Stay tuned.