Capabilities
Capabilities are a familiar abstraction used throughout the SmartThings Platform. However, this document is not meant to be an exhaustive look at capabilities and instead will focus on the interaction with Capabilities from within the lua code environment. There are two basic ways that you will interact with Capabilities when supporting a device, one is generating events, and one is receiving commands. The sections below will explain these in more detail.
Capability Events
Events can be thought of as one of the outputs of your driver. The most common use case here is receiving a message from the device informing your driver of a change of state, and converting that change of state to a Capability attribute state update so it can propagate throughout the system and be used in rules and apps. Exactly what information is necessary for a Capability event is dependent on the Capability and attribute that you are generating it for and you should look at the Capability specific documentation to understand the specifics, but in most cases, a single “value” update is enough. However, there is some additional information the system needs to be able to handle the event correctly, namely, the device and component that the event is for. More in depth discussion of device profiles and components can be found elsewhere, but within edge device drivers, most of you event generation can be done using helper methods defined on the device object. See that page for more in depth discussion, but a simple example would be:
my_device:emit_event(capabilities.switch.switch.on())
my_multi_component_device.profile.components["my_component_name"]:emit_event(capabilities.switch.switch.on())
Both of these emit_event
functions are taking an argument that represents the Capability event values other than
those related to the device, and we will describe this process in more detail.
There is a single top level Capability file that you can include in your driver to gain access to these helper methods,
and can be pulled in with reqiure "st.capabilities"
. The module table that is returned will have a table entry
under each Capability ID representing that Capability (e.g. the capabilities.switch
in the example above). This is
itself a lua table with some methods provided to make building the events you want easier.
Attributes
Most Capabilities will have several attributes which will track the current state of the device. Changing one of these
attributes is one of the most common actions you will want to do as a part of your driver. The syntax for doing so is
by using the _call
function on the attribute table on the Capability, and passing in the value you want for the
event.
local capabilities = require "st.capabilities"
local event_state = capabilities.switchLevel.level(50)
The above would create the state part of the event (i.e. the part independent of the device it is tied to). This case covers the simplest form of event generation and will work for the vast majority of attributes, but complexity can come into place depending on the “schema” of the attribute, which defines it’s type, as well as potentially additional augmenting information (unit for example).
Another very common type for attributes is an enum
. The enum type is basically just a string value, but restricted
to a specific set of options. Because this is such a common Capability type, and having just arbitrary strings in the
code can be undesirable, we have provided an additional helper option for generating those events.
local capabilities = require "st.capabilities"
local event_state = capabilities.switch.switch.on()
That is capabilities.<capability_id>.<attribute_name>.<enum_value>()
which is functionally equivalent to
capabilties.<capability_id>.<attribute_name>("<enum_value>")
. For state events that are more complicated, you can
define the additional values by passing a table fully describing the event.
local capabilities = require "st.capabilities"
local event_state = capabilities.temperatureMeasurement.temperature({ value = 50, unit = "C" })
What exactly is necessary, is again defined within the schema. Also, of note, the earlier examples with a single value passed in are equivalent to passing in a table with a single key “value” set to the arg.
It should also be noted that the Capability definitions include schemas defining the required and allowed values for
attributes, and these will be enforced on event generation. That is, for something like level
for switchLevel
it defines a maximum value of 100 and a minimum of 0. Thus, if an event is attempted to be generated with a value of
150 an error will be raised.
metadata
There are a few additional, optional fields that can be specified for an event on creation. These values can be passed in an additional table when using the capability library to create an event after the core value argument to the creation functions.
state_change
This field allows you to explicitly inform the platform that an event is a state change. The most common use case for this is button events. Because the attribute value for a button press is always the same value, if you press a button twice in a row, regardless of the time between presses, the platform won’t see a change in value and thus would treat the event as not being a state change, and so the event would not be forwarded to subscriptions (rules, connected services, etc.) By specifying state_change = true you can force the event to be treated as a state change regardless of its relationship to the previous value. It is important to note that state_change = false is not guaranteed to be treated as not a state change, as a value _different_ from the previous state value will always be treated as a state change regardless of this field.
local capabilities = require "st.capabilities"
local event_state = capabilities.button.button.pushed({ state_change = true })
-- or if an attribute doesn't have enum values
local event_state_2 = capabilities.button.button("pushed", { state_change = true })
visibility
The visibility field allows some control over how the event is displayed and stored by SmartThings. It has 3 fields that can be set:
- displayed
If false this event will not show up in the history of the device on the mobile app
- non_archivable
If true this event will not be stored for longer than the standard 7 day recent history
- ephemeral
If true this event will not be stored in recent history (7 days)
local capabilities = require "st.capabilities"
local event_state = capabilities.energyMeter.energy({value = 1.2345, unit = "kWh" }, { visibility = { displayed = false } })
Capability Commands
The other main component of Capabilities are the commands they support. Commands will typically be originated from either the mobile app, or a rule. These can be “received” on the Capability channel. If you use the built in driver constructions it will automatically register a handler for these commands which can be used to convert the Capability command into a protocol message to the device.
capability_handlers
All Drivers regardless of the protocol supported can have a list of capability_handlers
defined as a part of the
driver. The structure needed is fairly straightforward, it is simply a nested table keying on Capability ID, then
command name, to point to the function that will handle it. Following is an example from a Zigbee driver to support
switch and switchLevel commands.
my_driver.capability_handlers = {
[capabilities.switch.ID] = {
[capabilities.switch.commands.on.NAME] = switch_defaults.on,
[capabilities.switch.commands.off.NAME] = switch_defaults.off
},
[capabilities.switchLevel.ID] = {
[capabilities.switchLevel.commands.setLevel.NAME] = switch_level_defaults.set_level
},
}
These should be added to the driver template that is passed into the Driver.init
function via the Driver()
call.
The signature of each of these handlers should be handler(driver, device, capability_command)
. The Capability
command will have the following structure
local setLevelCommand={
capability="switchLevel",
command="setLevel",
args={
level=57,
rate=0,
},
positional_args={
57,
0,
},
}
Note that there are actually 2 versions of the command arguments here, both positional and named. Per the Capability specification, all arguments are positional arguments. However, because names are included with them in the definition we map the args to their name in a table for convenience of use, and to help the code to be more self documenting.
In addition there are 2 extra keys that can be added to the capability_handlers table. The fallback
and error
fields can be set to be used as the special handlers for the Dispatchers to be handlers that get
called in the case of no other matching handler or an error is encountered respectively.
Native capability command handlers
Many of the capability_handler
implementations are provided by the protocol defaults (See Zigbee,
Z-Wave, and Matter defaults).
Since SmartThings maintains these default handlers, there are commands that can be handled natively
without a driver ever needing to
execute a Lua handler, which allows for avoiding the overhead of performing the same operation in Lua,
and decreases command handler execution latency.
Devices that are in compliance with their protocol specification can capitalize on this improvement by
registering to have a capability command handled natively with the Device.register_native_capability_cmd_handler()
API.
Any device that uses one of the existing default Lua handlers may now be handled natively.
The Lua library API v11 supports the following capability commands and devices:
switch
on
/off
for single component Matter, Zigbee, and Z-Wave devices.switchLevel
setLevel
for single component Matter, Zigbee, and Z-Wave devices
Custom Capabilities
Documentation on full custom Capability support coming soon.