Building a Web Services SmartApp - Part 1¶
This is the first part of two that will teach you how to build a WebServices SmartApp.
In part 1 of this tutorial, you will learn:
- How to develop a Web Services SmartApp that exposes endpoints.
- How to call the Web Services SmartApp using simple API calls.
Contents
Overview¶
Part 1 of this tutorial will build a simple SmartApp that exposes endpoints to get information about and control switches.
Create a new SmartApp¶
Create a new SmartApp in the IDE. Fill in the required fields, and make sure to click on Enable OAuth in SmartApp to receive an auto-generated client ID and secret.
Note the Client ID and secret - they’ll be used later (should you forget, you can get them by viewing the “App Settings” in the IDE).
Define Preferences¶
SmartApps declare preferences metadata that is used at installation and configuration time, to allow the user to control what devices the SmartApp will have access to.
This is a configuration step, but also a security step, whereby the users must explicitly select what devices the SmartApp can control.
Web Services SmartApps are no different, and this is part of the power of this approach. The end user controls exactly what devices the SmartApp will have access to, and therefore what devices the external systems that consume those web services will have access to.
The preferences definition should look like this:
preferences {
section ("Allow external service to control these things...") {
input "switches", "capability.switch", multiple: true, required: true
}
}
Also ensure that you have an installed()
and updated()
method defined (this should be created by default when creating a SmartApp). They can remain empty, since we are not subscribing to any device events in this example.
Specify Endpoints¶
The mappings
declaration allows developers to expose HTTP endpoints, and map the various supported HTTP operations to an associated handler.
The handler can process the HTTP request and provide a response, including both the HTTP status code, as well as the response body.
Our SmartApp will expose two endpoints:
- The
/switches
endpoint will support a GET request. A GET request to this endpoint will return state information for the configured switches. - The
/switches/:command
endpoint will support a PUT request. A PUT request to this endpoint will execute the specified command ("on"
or"off"
) on the configured switches.
Tip
There is no limit to the number of endpoints a SmartApp exposes, but the path level is restricted to four levels deep (i.e., /level1/level2/level3/level4).
Here’s the code for our mappings definition. This is defined at the top-level in our SmartApp (i.e., not in another method):
mappings {
path("/switches") {
action: [
GET: "listSwitches"
]
}
path("/switches/:command") {
action: [
PUT: "updateSwitches"
]
}
}
The mappings configuration is made up of one or many path
definitions. Each path
defines the endpoint, and also is configured for each HTTP operation using the action
definition.
action
is a simple map, where the key is the HTTP operation (e.g., GET
, PUT
, POST
, etc.), and the value is the name of the handler method to be called when this endpoint is called.
Note the use of variable parameters in our PUT endpoint. Use the :
prefix to specify that the value will be variable. We’ll see later how to get this value.
Tip
Endpoints can support multiple REST methods. If we wanted the /switches
endpoint to also support a PUT request, simply add another entry to the action
configuration:
action: [
GET: "listSwitches",
PUT: "putHandlerMethodName"
]
Go ahead and add empty methods for the various handlers. We’ll fill these in in the next step:
def listSwitches() {}
def updateSwitches() {}
GET Switch Information¶
Now that we’ve defined our endpoints, we need to handle the requests in the handler methods we stubbed in above.
Let’s start with the handler for GET requests to the /switches
endpoint. When a GET request to the /switches
endpoint is called, we want to return the display name, and the current switch value (e.g., on or off) for the configured switch.
Our handler method returns a list of maps, which is then serialized by the SmartThings platform into JSON:
// returns a list like
// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
def listSwitches() {
def resp = []
switches.each {
resp << [name: it.displayName, value: it.currentValue("switch")]
}
return resp
}
UPDATE the Switches¶
We also need to handle a PUT request to the /switches/:command
endpoint. /switches/on
will turn the switches on, and /switches/off
will turn the switches off.
If any of the configured switches does not support the specified command, we’ll return a 501
HTTP error.
void updateSwitches() {
// use the built-in request object to get the command parameter
def command = params.command
if (command) {
// check that the switch supports the specified command
// If not, return an error using httpError, providing a HTTP status code.
switches.each {
if (!it.hasCommand(command)) {
httpError(501, "$command is not a valid command for all switches specified")
}
}
// all switches have the comand
// execute the command on all switches
// (note we can do this on the array - the command will be invoked on every element
switches."$command"()
}
}
Tip
Our example uses the endpoint itself to get the command. If you would instead like to pass parameters via the request body, you can retrieve those parameters via the built-in request
object as well. Assuming the request body looked like {"command": "on"}
, we can get the specified command parameter like this:
// Get the JSON body from the request.
// Safe de-reference using the "?." operator
// to avoid NullPointerException if no JSON is passed.
def command = request.JSON?.command
Self-publish the SmartApp¶
Publish the app for yourself, by clicking on the “Publish” button and selecting “For Me”.
Run the SmartApp in the Simulator¶
Using the simulator, we can quickly test our Web Services SmartApp.
Click the Install button in the simulator, select a Location to install the SmartApp into, and select a switch.
Note that in the lower right of the simulator there is an API token and an API endpoint. We can use these to test making requests to our SmartApp.
Make API Calls to the SmartApp¶
Using whatever tool you prefer for making web requests (this example will use curl, but Apigee is a good UI-based tool for making requests), we will call one of our SmartApp endpoints.
From the simulator, grab the API endpoint. It will look something like this:
https://graph.api.smartthings.com/api/smartapps/installations/158ef595-3695-49ab-acc1-80e93288c0c8
Your installation will have a different, unique URL.
To get information about the switch, we will call the /switch endpoint using a GET request. You’ll need to substitute your unique endpoint and API key.
curl -H "Authorization: Bearer <api token>" <api endpoint>/switch
This should return a JSON response like the following:
[{"name":"Kitchen 2","value":"off"},{"name":"Living room window","value":"off"}]
To turn the switch on or off, call the /switch endpoint using a PUT request, passing the command in the request body. Again, you’ll need to substitute your unique endpoing and API key:
curl -H "Authorization: Bearer <api token>" -X PUT <api endpoint>/switch/on
Change the command value to "off"
to turn the switch off. Try turning the switch on and off, and then using curl to get the status, to see that it changed.
Tip
You can also pass the API token directly on the URL, via the access_token
URL parameter, instead of using the Authorization header. This may be useful when you do not have the ability to set request headers.
Uninstall the SmartApp¶
Finally, uninstall the SmartApp using the Uninstall button in the IDE simulator.
Summary¶
In this tutorial, you learned how to create a SmartApp that exposes endpoints to get information about, and control, a device. You also learned how to install the SmartApp in the simulator, and then make API calls to the endpoint.
In the next part of this tutorial, we’ll look at how a external application might interact with SmartThings using the OAuth2 flow (instead of simply using the simulator and its generated access token).
Source Code¶
The full source code for this tutorial (both parts), can be found here.