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.