Zigbee Driver Structures
A ZigbeeDriver is an extension of the Driver
and contains some more specific structures that are
only applicable to a Zigbee device.
zigbee_handlers
The zigbee_handlers
are the most common additional field you will be adding to the driver template used to build
your driver. The zigbee_handlers
structure is used to register message handlers for any message coming from the
device that you may need to convert to a SmartThings capability event, or use as state to manage the continued execution
of the device. Detailed information on the types of handlers as well as the structure of the table can be found
on the RX message handlers page.
There are some additional caveats to take into account when setting these up in your driver. First is the interaction
between zigbee_handlers
you define in your driver template, and handlers defined in
default Zigbee behavior that you pull in using the defaults.register_for_default_handlers
call. The handlers you provide directly in your driver template will take precedence over any default behavior that
would be present. More concretely, if you provide a handler for the OnOff attribute of the OnOff cluster in your driver
template, but then call to register defaults for the switch capability (which also provides a handler for that cluster
and attribute); your handler would be the one called with the message. In this way you can
choose to only override the specific handlers in which your device strays from the standard behavior.
Another note is that each registered “handler” can be either a function, or a list of functions. If you provide a list EACH function in that list will be called with the same message.
Example
Below is an example creating a simple set of zigbee_handlers
for a Zigbee switch that supports switch and level
(cluster_configurations, default handlers, and capability_handlers
omitted for
example simplicity).
local capabilities = require "st.capabilities"
local ZigbeeDriver = require "st.zigbee"
local clusters = require "st.zigbee.zcl.clusters"
local OnOff = clusters.OnOff
local Level = clusters.Level
local function on_off_attr_handler(driver, device, value)
local attr = capabilities.switch.switch
device:emit_event(value.value and attr.on() or attr.off())
end
local function level_attr_handler(driver, device, value)
device:emit_event(capabilities.switchLevel.level(math.floor((value.value / 254.0 * 100) + 0.5)))
end
local zigbee_switch_driver_template = {
supported_capabilities = {
capabilities.switch,
capabilities.switchLevel,
},
zigbee_handlers = {
global = {},
cluster = {},
attr = {
[OnOff.ID] = {
[OnOff.attributes.OnOff.ID] = on_off_attr_handler
},
[Level.ID] = {
[Level.attributes.CurrentLevel.ID] = level_attr_handler
}
}
}
}
local zigbee_bulb = ZigbeeDriver("zigbee-switch", zigbee_bulb_driver_template)
zigbee_bulb:run()
In addition there are 2 extra keys that can be added to the zigbee_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.
cluster_configurations
The cluster_configurations
are another ZigbeeDriver template option that maps pretty closely to the
ConfigureReporting
Zigbee command. These configurations are used to
drive how we configure a device newly joined to the hub to inform us of changes in state. See the class
AttributeConfiguration
for exactly what components go into each record. There are 2 use cases for these
configurations and they can be controlled using the two optional fields configurable
and monitored
.
These configurations should be grouped into lists, under the key of the capability ID they are associated with. This is used in terms of registering defaults, if you define a set of configurations on your driver under a given capability, the corresponding defaults will not be included even if you call to register them.
Cluster configurations are not supported in sub-drivers.
Configured Attribute
An AttributeConfiguration
that has configurable
set to true (which is the default if it is not explicitly set),
will result in a ConfigureReporting
command being sent to the device
when ZigbeeDevice:configure
is called (by default hooked up to the capability command configuration.configure
).
This means that if the device supports reporting, it will send an
ReportAttribute
command to the hub when the corresponding attribute
changes according the the other paramaters.
An AttributeConfiguration
that has this field set to false will not send the commands to configure reporting. One
common example of this would be a ZLL device as the ZLL profile does not support reporting.
Monitored Attribute
An AttributeConfiguration
that has monitored
set to true (which is the default if it is not explicitly set),
will enroll that attribute to be tracked by the driver. What this means is that each time that attribute is reported,
or we receive a read attribute response
the driver will
keep a timestamp. Then, periodically (on a 30 second interval) all monitored attributes will be checked, and if any of
those attributes hasn’t reported in 1.5x the maximum_interval
a
ReadAttribute
command will be sent to try to refresh the attribute value,
and will continue to be sent every 1.5x the maximum_interval
until it is heard from again. For most Zigbee devices
behaving well, these read attribute commands will never need to be sent, but if network congestions causes a missed
report, this can keep us from being out of date for too long. The most common use case for this is again ZLL devices
which, if changed out of band (e.g. a physical switch is turned off then on), we will not know about until we poll.
Example
Below is an example creating a simple set of attribute_configurations
for a Zigbee switch that supports switch
and level (zigbee_handlers, default handlers, and capability_handlers
omitted for
example simplicity).
local capabilities = require "st.capabilities"
local ZigbeeDriver = require "st.zigbee"
local clusters = require "st.zigbee.zcl.clusters"
local OnOff = clusters.OnOff
local Level = clusters.Level
local data_types = require "st.zigbee.data_types"
local zigbee_switch_driver_template = {
supported_capabilities = {
capabilities.switch,
capabilities.switchLevel,
},
cluster_configurations = {
[capabilities.switch.ID] = {
{
cluster = zcl_clusters.OnOff.ID,
attribute = zcl_clusters.OnOff.attributes.OnOff.ID,
minimum_interval = 0,
maximum_interval = 300,
data_type = data_types.Boolean
}
},
[capabilities.switchLevel.ID] = {
{
cluster = zcl_clusters.Level.ID,
attribute = zcl_clusters.Level.attributes.CurrentLevel.ID,
minimum_interval = 1,
maximum_interval = 3600,
data_type = data_types.Uint8,
reportable_change = 1
}
}
}
}
local zigbee_bulb = ZigbeeDriver("zigbee-switch", zigbee_bulb_driver_template)
zigbee_bulb:run()
additional_zcl_profiles
By default when we receive a Zigbee message we will deserialize it into the ZigbeeMessageRx structure, which makes interacting with the messages more straightforward. It is also based on these structures that the zigbee_handlers are dispatched to. During that deserialization, messages with the HA (0x0104), and ZLL (0xC05E) profile IDs will be deserialized in the ZCL format (with a zcl_header and zcl_body within the body structure), and messages with the ZDO profile (0x0000) will be deserialized with the ZDO body format (a zdo_header and zdo_body). All other profiles will be deserialized as a GenericBody as we don’t assume the structure will match either of the above. This also means that any GenericBody message won’t be able to use the normal zigbee_handlers structure as we require ZCL header information to route them. If a device uses a custom profile, but still uses ZCL for the messages, you can specify that in the driver template using the additional_zcl_profiles field with the following format:
local custom_profile_id_1 = 0xDEAD
local custom_profile_id_2 = 0xBEEF
local zigbee_driver_template = {
-- All the other fields needed for your driver
additional_zcl_profiles = {
[custom_profile_id_1] = true,
[custom_profile_id_2] = true,
}
}
When these are specified those messages will attempt to deserialize the zcl_header and body and use the normal zigbee_handler format.
ZigbeeDriver Class Documentation
- class ZigbeeDriver: Driver
- zigbee_channel: message_channel
the communication channel for Zigbee devices
- cluster_configurations: list[st.zigbee.AttributeConfiguration]
A list of configurations for reporting attributes
- zigbee_handlers: table
A structure definining different ZigbeeHandlers mapped to what they handle (only used on creation)
- zigbee_message_handler(self, message_channel)
Handler function for the raw zigbee channel message receive
This will be the default registered handler for the Zigbee message_channel receive callback. It will parse the raw serialized message into a ZigbeeMessageRx and then use the zigbee_message_dispatcher to find a handler that can deal with it.
Handlers have various levels of specificity. Global handlers are for global ZCL commands, and are specified with a cluster, then command ID. Cluster handlers are for cluster specific commands and are again defined by cluster, then command id. Attr handlers are used for an attribute report, or read response for a specific cluster, attribute ID. and finally zdo handlers are for ZDO commands and are defined by the “cluster” of the command.
- Parameters
self (
Driver
) – the driver contextmessage_channel (
message_channel
) – the Zigbee message_channel with a message ready to be read
- static populate_zigbee_dispatcher_from_sub_drivers(driver)
Add a number of child handlers that override the top level driver behavior
Each handler set can contain a handlers field that follow exactly the same pattern as the base driver format. It must also contain a zigbee_can_handle(driver, device, zb_rx) function that returns true if the corresponding handlers should be considered.
This will recursively follow the sub_drivers and build a structure that will correctly find and execute a handler that matches. It should be noted that a child handler will always be preferred over a handler at the same level, but that if multiple child handlers report that they can handle a message, it will be sent to each handler that reports it can handle the message.
- Parameters
driver (
Driver
) – the executing zigbee driver (or sub handler set)
- add_hub_to_zigbee_group(group_id)
- Parameters
group_id (
any
) –
- build_child_device(raw_device_table)
- Parameters
raw_device_table (
any
) –
- static init(cls, name, driver_template)
Build a Zigbee driver from the specified template
This can be used to, given a template, build a Zigbee driver that can be run to support devices. The name field is used for logging and other debugging purposes. The driver should also include a set of capability_handlers and zigbee_handlers to handle messages for the corresponding message types. It is recommended that you use the call syntax on the ZigbeeDriver to execute this (e.g. ZigbeeDriver(“my_driver”, {}) )
Attribute Configuration Documentation
- class st.zigbee.AttributeConfiguration
- cluster: number
Cluster ID this attribute is a part of
- attribute: number
the attribute ID
- minimum_interval: number
the minimum reporting interval for this configuration
- maximum_interval: number
the maximum reporting interval for this configuration
- data_type: st.zigbee.data_types.DataType
the data type class for this attribute
- reportable_change: st.zigbee.data_types.DataType
(optional) the amount of change needed to trigger a report. Only necessary for non-discrete attributes
- mfg_code: number
the manufacturer-specific code
- configurable: boolean
(optional default = true) Should this result in a Configure Reporting command to the device
- monitored: boolean
(optional default = true) Should this result in a expected report monitoring