SmartApp SDK¶
Release v0.6.7
smartapp-sdk is a Python library to build a webhook-based SmartApp for the SmartThings platform.
The SDK is intended to be easy to use no matter how you choose to structure your code, whether that’s a traditional Python webapp (such as FastAPI on Uvicorn) or a serverless application (such as AWS Lambda).
The SDK handles all the mechanics of the webhook lifecycle interface on your behalf. You just implement a single endpoint to accept the SmartApp webhook requests, and a single callba ck class where you define specialized behavior for the webhook events. A clean attrs object interface is exposed for use by your callback.
Installation¶
Install the package with pip:
$ pip install smartapp-sdk
API Documentation¶
Using the SDK¶
Below are some notes on how to use the SDK. The smartapp-sensortrack repo on GitHub is also a good example of how to use the SDK to build a traditional Python webapp.
Event Handler¶
First, create your event handler class:
from smartapp.interface import (
ConfigurationRequest,
ConfirmationRequest,
EventRequest,
EventType,
InstallRequest,
OauthCallbackRequest,
SmartAppEventHandler,
UninstallRequest,
UpdateRequest,
)
class EventHandler(SmartAppEventHandler):
"""SmartApp event handler."""
def handle_confirmation(self, correlation_id: Optional[str], request: ConfirmationRequest) -> None:
"""Handle a CONFIRMATION lifecycle request"""
def handle_configuration(self, correlation_id: Optional[str], request: ConfigurationRequest) -> None:
"""Handle a CONFIGURATION lifecycle request."""
def handle_install(self, correlation_id: Optional[str], request: InstallRequest) -> None:
"""Handle an INSTALL lifecycle request."""
def handle_update(self, correlation_id: Optional[str], request: UpdateRequest) -> None:
"""Handle an UPDATE lifecycle request."""
def handle_uninstall(self, correlation_id: Optional[str], request: UninstallRequest) -> None:
"""Handle an UNINSTALL lifecycle request."""
def handle_oauth_callback(self, correlation_id: Optional[str], request: OauthCallbackRequest) -> None:
"""Handle an OAUTH_CALLBACK lifecycle request."""
def handle_event(self, correlation_id: Optional[str], request: EventRequest) -> None:
"""Handle an EVENT lifecycle request."""
This empty event handler is perfectly legal and is good enough for now.
SmartApp Definition¶
Every SmartApp needs a definition, which provides an id, name, description, target URL, and usually at least one configuration page:
from smartapp.interface import ConfigSection, DeviceSetting, SmartAppConfigPage, SmartAppDefinition
definition = SmartAppDefinition(
id="example",
name="Example App",
description="Example SmartApp with temperature sensor",
target_url="https://example.com/smartapp",
permissions=["r:devices:*", "r:locations:*"],
config_pages=[
SmartAppConfigPage(
page_name="Configuration",
sections=[
ConfigSection(
name="Devices",
settings=[
DeviceSetting(
id="temperature-devices",
name="Temperature Devices",
description="Sensor devices to track temperature for",
required=False,
multiple=True,
capabilities=["temperatureMeasurement"],
permissions=["r"],
),
],
)
],
)
],
)
You can also use smartapp.converter.CONVERTER
to round-trip between object
representation and YAML or JSON representation. The YAML format looks like
this:
id: example
name: Example App
description: Example SmartApp with temperature sensor
targetUrl: https://example.com/smartapp
permissions:
- r:devices:*
- r:locations:*
configPages:
- pageName: Configuration
sections:
- name: Devices
settings:
- id: temperature-devices
name: Temperature Devices
description: Sensor devices to track temperature for
type: DEVICE
required: false
multiple: true
capabilities:
- temperatureMeasurement
permissions:
- r
One convenient option is to keep the SmartApp YAML definition somewhere in your source tree:
with importlib.resources.open_text("myapp.data", "definition.yaml") as f:
definition = CONVERTER.from_yaml(f.read(), SmartAppDefinition)
It’s often easier to maintain the definition in YAML rather than in code, and it’s also somewhat more legible.
Dispatcher¶
Once you have an event handler and a SmartApp definition, you can create your dispatcher:
dispatcher = SmartAppDispatcher(definition=definition, event_handler=EventHandler())
There’s also an optional config
parameter, but you probably don’t need to
change any of the default configuration.
POST Endpoint¶
Finally, handle all SmartApp POST
requests with just two lines of code:
context = SmartAppRequestContext(headers=request_headers, body=request_body)
response_body = dispatcher.dispatch(context=context)
Source the request headers and JSON request body in any way that makes sense for the web application framework you are using. For instance, with FastAPI, this is one way to do it (although in a real application, you’d also want some exception handlers, etc.):
@API.post("/smartapp")
async def smartapp(request: Request) -> Response:
headers = request.headers
body = codecs.decode(await request.body(), "UTF-8")
context = SmartAppRequestContext(headers=headers, body=body)
content = dispatcher.dispatch(context=context)
return Response(status_code=200, content=content, media_type="application/json")
Then, make sure your web application is exposed on the public internet via https, and you are ready to follow the remaining setup steps in the SmartThings documentation.
More Implementation Notes¶
The guts of your SmartApp will be in your event handler.
The event handler is a synchronous and single-threaded interface. The assumption is that if you need high-volume asynchronous or multi-threaded processing, you will implement that at the calling tier (as shown in the FastAPI example above).
Some lifecycle events do not require you to implement any custom event handler logic:
CONFIRMATION
: normally no callback needed, since the dispatcher logs the app id and confirmation URLCONFIGURATION
: normally no callback needed, since the dispatcher has the information it needs to respondINSTALL
/UPDATE
: set up or replace subscriptions and schedules and persist required data, if anyUNINSTALL
: remove persisted data, if anyOAUTH_CALLBACK
: coordinate with your oauth provider as neededEVENT
: handle SmartThings events or scheduled triggers
The EventRequest
object that you receive in the handle_event()
callback
method includes an authorization token and also the entire configuration bundle
for the installed application. So, if your SmartApp is built around event
handling and scheduled actions triggered by SmartThings, your handler can
probably be stateless. There is probably is not any need to persist any of the
data returned in the INSTALL
or UPDATE
lifecycle events into your own
data store. This is the model folowed in the smartapp-sensortrack example.