MatterDevice Class
This is a class that inherits from the Device
class, but extends behavior
with Matter-specific functionality. These functions are documented in the class documentation below.
Subscribing to Matter Devices
Matter devices commonly subscribe to a cluster’s attributes and events to be informed of changes that occur on the device. The hub maintains a single subscription with every device that is joined to the fabric. When a driver sends a Subscribe InteractionRequest, the hub will ensure those attributes and events are included in the subscription. Subsequent Subscribe InteractionRequests will override previous requests; only the attribute/event paths in the most recent subscription request are guaranteed to be a part of the subscription.
There are a few different ways to setup the attribute/events in the subsciption. The first option
is to define the init
lifecycle event handler for your driver, manually building and sending a subscribe
interaction request with the needed attribute and event paths to monitor on the device. The Matter Device object
also provides subscribed_attributes
and subscribed_events
fields which list the cluster attributes/events that
should be included in the subscription, and are used to build and send a subscription request when MatterDevice:subscribe()
is called.
Subscribed Attributes
The subscribed_attributes
on a device object will be included in the subscription request when MatterDevice:subscribe()
is called.
These can be added to a device using the MatterDevice:add_subscribed_attribute
.
Typically these attributes are added as a part of the device object construction via the init
lifecycle event,
however, it can be done in whatever way makes sense for the device. These attributes will also automatically be read
from the device when MatterDevice:refresh()
is called.
Subscribed Events
A subscribed event will be included in the subscription request when MatterDevice:subscribe()
is called.
These can be added to a device using the MatterDevice:add_subscribed_event
.
Typically these events are added as a part of the device object construction via the init
lifecycle event,
however, it can be done in whatever way makes sense for the device. These events will NOT be read
from the device when MatterDevice:refresh()
is called. This is because such events are things that have happened in the past
and capability events represent something that has just happened to the device.
Defaults and Subscribed Attributes/Events
As described in the defaults section, quite a bit of default behavior is supported based on the capabilities a device supports (defined in the device’s profile). If your driver registers for defaults, there are a number of subscribed attributes and subscribed events that that will be added to the device, which means calling MatterDevice:subscribe() or MatterDevice:refresh() will include these defaults.
Multi-Component devices
There are a number of situations where the SmartThings model of the device makes sense to be broken down into several “components”. A classic example of this would be a smart power strip, where it is a single device on the network, but each outlet can be controlled separately. This would best be modeled as a single device, with a component for each switch.
Within Matter, these separate pieces of functionality are often modeled as “endpoints”, and messages to the devices can
be addressed to the specific endpoint you want to control and interact with. The MatterDevice
object provides a way for
your driver to define how you want to map between components and endpoints that can then be used by the rest of the Matter
Lua standard library to automatically generate events for the correct components or send messages to the correct endpoint.
As an important note, as with most things in the standard library, this is built to support the most common model, but it is
likely that there will be individual devices that don’t adhere to this model and will need to override this behavior.
In order to opt in to this behavior you can use the following functions:
function MatterDevice:set_component_to_endpoint_fn(comp_ep_fn)
function MatterDevice:set_endpoint_to_component_fn(ep_comp_fn)
Here you can provide a function for each direction to map a Matter endpoint id (number) to a SmartThings component id (string), and vice versa. Once these are set, the following functions on the device are used for event generation and message addressing:
function MatterDevice:endpoint_to_component(comp_id)
function MatterDevice:component_to_endpoint(ep)
If these functions are used without setting the mapping functions above, they will return the defaults (“main” for a
component, and MATTER_DEFAULT_ENDPOINT
, which is 1, for the endpoint). For convenience, the following
function is provided as well:
function MatterDevice:emit_event_for_endpoint(endpoint, event)
example
Here is a simple example of a driver that supports multi-switch Matter outlet where the profiles are defined as follows:
name: two-outlet
components:
- id: main
capabilities:
- id: switch
version: 1
categories:
- name: Switch
- id: switch1
capabilities:
- id: switch
version: 1
categories:
- name: Switch
name: three-outlet
components:
- id: main
capabilities:
- id: switch
version: 1
categories:
- name: Switch
- id: switch1
capabilities:
- id: switch
version: 1
categories:
- name: Switch
- id: switch2
capabilities:
- id: switch
version: 1
categories:
- name: Switch
And uses Matter endpoints 0x01
for the first outlet and increments by one for each additional outlet. Then the
following driver will be able to use the built-in behavior to correctly generate events and address commands.
local capabilities = require "st.capabilities"
local MatterDriver = require "st.matter.driver"
local matter_device_module = require "st.matter.device"
local defaults = require "st.matter.defaults"
local function component_to_endpoint(device, component_id)
if component_id == "main" then
return matter_device_module.DEFAULT_MATTER_ENDPOINT --DEFAULT_MATTER_ENDPOINT == 1
else
local ep_num = component_id:match("switch(%d)")
return ep_num and (tonumber(ep_num) + 1) or matter_device_module.DEFAULT_MATTER_ENDPOINT
end
end
local function endpoint_to_component(device, ep)
if ep == matter_device_module.DEFAULT_MATTER_ENDPOINT then
return "main"
else
return string.format("switch%d", ep - 1)
end
end
local device_init = function(self, device)
device:set_component_to_endpoint_fn(component_to_endpoint)
device:set_endpoint_to_component_fn(endpoint_to_component)
end
local matter_outlet_driver_template = {
supported_capabilities = {
capabilities.switch,
},
lifecycle_handlers = {
init = device_init,
},
}
defaults.register_for_default_handlers(matter_outlet_driver_template, matter_outlet_driver_template.supported_capabilities)
local matter_outlet = MatterDriver("matter-outlet", matter_outlet_driver_template)
matter_outlet:run()
Parent/Child Devices
Very similar to multi component devices there may be some situations where you prefer to model a single network device as multiple SmartThings device records. This can be done by creating “Child” devices to represent individual endpoints on the device. NOTE: If you aren’t modeling each child as an endpoint, you won’t be able to use the library abstractions, but you can build your own handling of messages for whatever model you desire.
You can define a find_child
function using the set_find_child
method on device objects. Then any use of
emit_event_for_endpoint
including the default capability handlers will use this to emit events on the appropriate
children. The find_child
function you define will be passed the source endpoint a message came from and should then
return the device object representing the child device that is responsible for that endpoint, or nil
if there isn’t
one.
When deleting devices in a parent/child relationship, deleting the parent will result in the removal of all of the children as well as all communication goes through the parent device. However, you can delete individual children without affecting the others. It should be noted though that typically the only way to create the children is on device join so if deleted getting the child devices back will often require deleting and re-onboarding the parent.
example
Profile:
name: outlet
components:
- id: main
capabilities:
- id: switch
version: 1
- id: refresh
version: 1
categories:
- name: Outlet
Driver:
local capabilities = require "st.capabilities"
local clusters = require "st.matter.clusters"
local MatterDriver = require "st.matter.driver"
local device_lib = require "st.device"
local function find_child(parent, ep_id)
return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id))
end
local function added(driver, device, event)
-- Only create children for the actual Zigbee device and not the children
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
for i = 2, 5, 1 do
local name = string.format("%s outlet %d", device.label, i)
local metadata = {
type = "EDGE_CHILD",
label = name,
profile = "outlet",
parent_device_id = device.id,
parent_assigned_child_key = string.format("%02X", i),
vendor_provided_label = name,
}
driver:try_create_device(metadata)
end
end
end
local function init(driver, device, event)
if device.network_type == device_lib.NETWORK_TYPE_MATTER then
device:set_find_child(find_child)
end
end
local function on_off_attr_handler(driver, device, ib, response)
-- This device will _always_ be the parent because the parent is the only one actually on the network sending messages
-- but this call to `emit_event_for_endpoint` will use the `find_child` function set below to find the child and
-- emit the event for that SmartThings device instead
if ib.data.value then
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on())
else
device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off())
end
end
local function handle_switch_on(driver, device, cmd)
-- Since we will receive commands separately for the different children, the device object here could be a parent or
-- child. The message building will work the same as the child will defer to the parent for addressing information.
local endpoint_id = device:component_to_endpoint(cmd.component)
local req = clusters.OnOff.server.commands.On(device, endpoint_id)
device:send(req)
end
local function handle_switch_off(driver, device, cmd)
-- Since we will receive commands separately for the different children, the device object here could be a parent or
-- child. The message building will work the same as the child will defer to the parent for addressing information.
local endpoint_id = device:component_to_endpoint(cmd.component)
local req = clusters.OnOff.server.commands.Off(device, endpoint_id)
device:send(req)
end
local matter_driver_template = {
lifecycle_handlers = {
init = init,
},
matter_handlers = {
attr = {
[clusters.OnOff.ID] = {
[clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler,
},
},
},
capability_handlers = {
[capabilities.switch.ID] = {
[capabilities.switch.commands.on.NAME] = handle_switch_on,
[capabilities.switch.commands.off.NAME] = handle_switch_off,
},
},
}
-- The on_handler and on_off_attr_handler above would be covered by these defaults, but are included for example purposes
local matter_driver = MatterDriver("matter-child-example", matter_driver_template)
matter_driver:run()
Class Documentation
- class st.matter.Device: st.Device
- get_endpoints(cluster_id, opts)
Get the endpoints on a device that support a particular cluster, attribute, or command.
By default, only server type clusters will be returned unless otherwise specified with the “cluster_type” optional parameter.
- Parameters
cluster_id (
number or nil
) – the cluster ID to check for (if nil, all endpoint ids are returned)opts (
table or nil
) – valid options include feature_bitmap to check cluster feature support, and cluster_type (using st.matter.constants.CLUSTER_TYPE)
- Returns
list of endpoint ids that support the given server cluster
- Return type
list[number]
- supports_server_cluster(cluster_id, endpoint_id)
Check if this device endpoint supports a specific cluster
- Parameters
cluster_id (
number
) – the cluster ID to check forendpoint_id (
number or nil
) – the endpoint to check cluster support (default to search all endpoints)
- Return type
boolean
- set_component_to_endpoint_fn(comp_ep_fn)
Set a function to map this devices SmartThings components to Matter endpoints
- Parameters
comp_ep_fn (
CompToEp
) – function to do the mapping for this device
- set_endpoint_to_component_fn(ep_comp_fn)
Set a function to map this devices Matter endpoints to SmartThings components
- Parameters
ep_comp_fn (
EpToComp
) – function to do the mapping for this device
- component_to_endpoint(comp_id)
Given the component ID find the corresponding endpoint for this device
This will use the function set by st.matter.Device:set_component_to_endpoint_fn to return the appropriate endpoint given the component. If the function is unset it defaults to the MATTER_ROOT_ENDPOINT
- Parameters
comp_id (
str
) – the component ID to find the endpoint for- Returns
the endpoint this component matches to
- Return type
number
- endpoint_to_component(ep)
Given the endpoint ID find the corresponding component for this device
This will use the function set by st.matter.Device:set_endpoint_to_component_fn to return the appropriate component given the endpoint. If the function is unset it defaults to “main”
- Parameters
ep (
number
) – the endpoint ID to find the component for- Returns
the component ID the endpoint matches to
- Return type
str
- add_subscribed_attribute(attr)
Add a subscribed attribute for this device
This will not take effect until subscribe is called for the device
- Parameters
attr (
table
) – Attribute object from the cluster library or a table containing cluster and attribute fields
- add_subscribed_event(attr)
Add a subscribed event for this device
This will not take effect until subscribe is called for the device
- Parameters
attr (
table
) – Event object from the cluster library or a table containing cluster and event fields
- subscribe()
Send request to the hub to subscribe to the device’s subscribed_attributes and subscribed_events list
- refresh()
Send a read attribute request with all subscribed attributes on this device
Note that this does not read the subscribed_events
- emit_event_for_endpoint(endpoint, event)
Emit a capability event for this device coming from the given endpoint
This uses st.matter.Device:endpoint_to_component to find the appropriate component and emit the event for that component
- Parameters
endpoint (
number
) – the endpoint ID a message was received fromevent (
table
) – the capability event to generate
- send(req)
Send an InteractionRequest to this device
- Parameters
req (
st.matter.interaction_model.InteractionRequest
) – the interaction request to send to this device
- pretty_print()
- Returns
string representation of the object
- Return type
str
- class st.matter.ChildDevice: st.Device
- get_endpoint()
- get_endpoints(cluster_id, opts)
Get the endpoints on a device that support a particular cluster, attribute, or command
Because the default implementation of children is that a child represents a single endpoint, this will only ever return a single endpoint representing this device if the cluster is supported
- Parameters
cluster_id (
number
) – the cluster ID to check foropts (
table or nil
) – valid options include a server_command_id, client_command_id, attribute_id, or feature_bitmap to search (only one).
- Returns
list of endpoint ids that support the given server cluster
- Return type
list[number]
- supports_server_cluster(cluster_id, endpoint_id)
Check if this device endpoint supports a specific cluster
- Parameters
cluster_id (
number
) – the cluster ID to check forendpoint_id (
number or nil
) – the endpoint to check cluster support (default to search all endpoints)
- Return type
boolean
- component_to_endpoint(comp_id)
Given the component ID find the corresponding endpoint for this device
This will use the function set by st.matter.Device:set_component_to_endpoint_fn to return the appropriate endpoint given the component. If the function is unset it defaults to the MATTER_ROOT_ENDPOINT
- Parameters
comp_id (
str
) – the component ID to find the endpoint for- Returns
the endpoint this component matches to
- Return type
number
- add_subscribed_attribute(attr)
Add a subscribed attribute for this device
This will not take effect until subscribe is called for the device
- Parameters
attr (
table
) – Attribute object from the cluster library or a table containing cluster and attribute fields
- subscribe()
Send request to the hub to subscribe to the device’s subscribed_attributes list
- send(req)
Send an InteractionRequest to this device
- Parameters
req (
st.matter.interaction_model.InteractionRequest
) – the interaction request to send to this device
- debug_pretty_print()
- pretty_print()