SDK that wraps the SmartThings REST API and reduces the amount of code necessary to write a SmartApp app. It supports both webhook and AWS Lambda implementations. This is a preview version of the API and will change over time time.
ATTENTION! This major release is not completely backwardly compatible with the 1.X version, though for most SmartApps the changes required should be relatively minor. The major non-backwardly changes are:
- Methods that return lists now return arrays rather that an object with the properties
items
and_links
. - Axios is now used rather than request-promise-native for making HTTP calls, resulting in changes to the error objects thrown when exceptions occur.
See the Version 2.0.0 release notes for more information.
npm i @smartthings/smartapp --save
NodeJS
:
const SmartApp = require('@smartthings/smartapp')
Or ES2015
+:
import SmartApp from '@smartthings/smartapp'
- Javascript API hides details of REST calls and authentication.
- Event handler framework dispatches lifecycle events to named event handlers.
- Configuration page API simplifies page definition.
- Integrated i18n framework provides configuration page localization.
- Winston framework manges log messages.
- Context Store plugins – easily scale access token management (and more) to support many users
- AWS DynamoDB plugin (usage)
- Firebase Cloud Firestore plugin (usage)
The following example is the equivalent of the original SmartThings Groovy Let There Be Light app that turns on and off a light when a door opens and closes.
To run the app with an HTTP server, like Express.js:
const express = require('express');
const SmartApp = require('@smartthings/smartapp');
const server = module.exports = express();
const PORT = 8080;
server.use(express.json());
/* Define the SmartApp */
const smartapp = new SmartApp()
// @smartthings_rsa.pub is your on-disk public key
// If you do not have it yet, omit publicKey()
.publicKey('@smartthings_rsa.pub') // optional until app verified
.enableEventLogging(2) // logs all lifecycle event requests and responses as pretty-printed JSON. Omit in production
.configureI18n()
.page('mainPage', (context, page, configData) => {
page.section('sensors', section => {
section
.deviceSetting('contactSensor')
.capabilities(['contactSensor'])
.required(false);
});
page.section('lights', section => {
section
.deviceSetting('lights')
.capabilities(['switch'])
.multiple(true)
.permissions('rx');
});
})
.updated(async (context, updateData) => {
// Called for both INSTALLED and UPDATED lifecycle events if there is no separate installed() handler
await context.api.subscriptions.unsubscribeAll()
return context.api.subscriptions.subscribeToDevices(context.config.contactSensor, 'contactSensor', 'contact', 'myDeviceEventHandler');
})
.subscribedEventHandler('myDeviceEventHandler', (context, event) => {
const value = event.value === 'open' ? 'on' : 'off';
context.api.devices.sendCommands(context.config.lights, 'switch', value);
});
/* Handle POST requests */
server.post('/', function(req, res, next) {
smartapp.handleHttpCallback(req, res);
});
/* Start listening at your defined PORT */
server.listen(PORT, () => console.log(`Server is up and running on port ${PORT}`));
To run as a Lambda function instead of an HTTP server, ensure that your main entry file exports smartapp.handleLambdaCallback(...)
.
Note: This snippet is heavily truncated for brevity – see the web service example above a more detailed example of how to define a
smartapp
.
const SmartApp = require('@smartthings/smartapp')
const smartapp = new SmartApp()
.enableEventLogging() // logs all lifecycle event requests and responses. Omit in production
.page( ... )
.updated(() => { ... })
.subscribedEventHandler( ... );
exports.handler = (event, context, callback) => {
smartapp.handleLambdaCallback(event, context, callback);
};
There are also a few Glitch examples:
- Simple SmartThings Automation App using Contact Sensors
- Simple SmartThings Automation App using Motion Detectors
- Simple Switch Cloud-to-Cloud (C2C) App
Configuration page strings are specified in a separate locales/en.json
file, which can be automatically created the first time you run the app. Here's a completed English localization file for the previous example:
{
"pages.mainPage.name": "Let There Be Light",
"pages.mainPage.sections.sensors.name": "When this door or window opens or closes",
"pages.mainPage.settings.contactSensor.name": "Select open/close sensor",
"pages.mainPage.sections.lights.name": "Turn on and off these lights and switches",
"pages.mainPage.settings.lights.name": "Select lights and switches",
"Tap to set": "Tap to set"
}
By default, instantiation of the SmartApp object registers an "unhandledReject" handler
that logs unhandled promise rejections. If you don't want this behavior you can disable
it by passing an option to the SmartApp instantiation, e.g. new SmartApp({logUnhandledRejections: false})
.
If you want to replace the handler you can do that by calling unhandledRejectionHandler(promise => {...})
on the SmartApp object.
By default, the SmartApp SDK will facilitate API calls on behalf of a user within the EVENT
lifecycle. These user tokens are ephemeral and last 5 minutes. These access tokens are not able to be refreshed and should not be stored. If you're making out-of-band API calls on behalf of a user's installed app, you will need to use the 24-hour access token that are supplied after INSTALL
and UPDATE
lifecycles. This token includes a refresh_token
, and will be automatically refreshed by the SDK when necessary.
Be aware that there is no in-memory context store, you must use a context store plugin. If you'd like to add a custom context store plugin, please contribute!
To get started, let's add a compatible ContextStore
plugin that will persist these tokens (among other things) to a database.
Available as a node package on NPM or fork on GitHub.
If you are hosting your SmartApp as an AWS Lambda, this DynamoDB context store makes perfect sense. This assumes you've already configured the aws-sdk
package to interact with your Lambda, so extending your context store to DynamoDB is a drop-in solution.
If you are self-hosted and still want to use DynamoDB, you can do that, too:
- Import the package to your project:
npm i --save @smartthings/dynamodb-context-store
- Note: when adding this package, you also have
aws-sdk
available at the global scope, so you can configure the AWS SDK:AWS.config.loadFromPath(creds)
- Note: when adding this package, you also have
- Get an AWS Access Key and Secret
- Set your credentials for your app, any of the following ways are fine.
- Register your Context Store plugin as described on the project repository's readme.
For complete directions on usage, please see this project's GitHub repository. (SmartThingsCommunity/dynamodb-context-store-nodejs)
Available as a node package on NPM or fork on GitHub. Usage is generally the same as DynamoDB:
- Generate a Firebase service account. You will receive a JSON file with the credentials.
- Load your Google Services JSON
- Create the context store
See the full usage guide on the project's GitHub repository. (SmartThingsCommunity/firestore-context-store-nodejs)
If you are not familiar with SmartThings, we have extensive on-line documentation.
To create and manage your services and devices on SmartThings, create an account in the developer workspace.
The SmartThings Community is a good place share and ask questions.
There is also a SmartThings reddit community where you can read and share information.
Licensed under the Apache License, Version 2.0
Copyright 2019 SmartThings, Inc.