Dispatchers
Dispatchers are a structure that was built primarily to support the subdriver structures (described in
driver) but can be used to handle any hierarchical message dispatching. That is there
will be a single root dispatcher which may have many generations of descendants. Each descendant represents a more
specific restriction on whether a message can be handled (i.e. all ancestors must have had their filter
pass the message
through). Once this hierarchical structure is built, a message can be passed to the root dispatcher and it will
recursively check descendants and pass the message to the most restrictive handler (i.e. the greatest descendant depth
that can handle it). More discussion on how this works will happen below in the section on filters. In general, each
dispatcher contains 3 core components, as well as some additional, less impactful elements. The sections below will
describe these in more detail.
default_handlers
The purpose of default_handlers
is to provide the logic to handle messages at a given level of the
hierarchy.
All handlers should be of the signature handler(driver, device, ...)
where the
varargs are specific to the type of event the dispatcher is handling, but should be homogenous. For example,
these could be the capability_handlers
defined in the Driver document. The specific structure of
these handlers is up to the type of message being handled, and the logic to find which default handler matches a given
message is unimplemented in the base class and is one of the pieces of logic that must be implemented by a child class
by implementing the function find_default_handlers(driver, device, ...)
that will return a flat list of all default
handlers that match.
There are 2 special fields that can be included in the default_handlers table.
- error
This will be a handler that will be called when any of the handlers in this or any child dispatcher raise an error while processing a message. The same arguments will be passed into this handler as were passed to the original handler, with the final argument being the error value returned from the handler.
- fallback
This handler will be called any time dispatch is called with a message/event with no handler being found for it. Note that if you set this on child dispatchers, it won’t be called as a part of any message passed to the root dispatcher because the child dispatcher can_handle will be
false
without any matching handler.
dispatcher_filter
The dispatcher_filter
is used to test if a message could potentially be handled by this dispatcher or its
descendants. Note that this does not need to check for the existence of a matching default handler as that is handled
in a different step. Instead, this will typically test some aspect of the device the message came from to
determine if this level of the hierarchy is appropriate to handle it. In practice this is most commonly used to test
the manufacturer or model (or both) of the specific device to handle any behavior unique to the individual device.
dispatcher_filters
are implicitly chained through descendants. That is, if a grandchild
of the root dispatcher is determined to be the correct handler for a message, it means that that message was passed
through the filters of all of its ancestors.
root_dispatcher (all messages pass filter)
child_1 (filter device manufacturer = "man one")
grandchild_1 (filter device model = "model one")
grandchild_2 (filter device model = "model two")
child_2 (filter device manufacturer = "man two")
grandchild_3 (filter device model = "model one")
grandchild_4 (filter device model = "model two")
In the above example we can see a nested structure representing our dispatcher filter functions. Note the duplicate filter functions present within the structure with different ancestors.
Below is a table showing which of the above dispatchers would handle a message received from a given device (assuming there are matching default handlers at each level):
Device |
dispatcher |
---|---|
Device(manufacturer = “man one”, model = “model two”) |
grandchild_2 |
Device(manufacturer = “man two”, model = “model two”) |
grandchild_4 |
Device(manufacturer = “man one”, model = “other”) |
child_1 |
Device(manufacturer = “my man”, model = “model two”) |
root_dispatcher |
child_dispatchers
Each dispatcher can have any number of child dispatchers. These are functionally identical at each level. The only difference is when they will be called to handle a message. Where the root dispatcher will be called upon to handle every message that a driver receives, the children will only be called if their filter function matches, as well as the filter of all of their ancestors.
Additional Fields
In addition to the 3 core components described above that drive most of the functionality of dispatchers, there are a number of other components that have additional usages.
- name
This is a human-readable name for what this dispatcher represents (e.g. “Manufacturer One”); used primarily in logging
- dispatcher_class_name
A human-readable name describing the class of messages this is meant to handle (e.g. “capability command dispatcher”); used primarily in logging
In addition there are a number of methods used for getting strings representing the dispatcher that can be used for logging to help understand the structure. These will be documented in the class docs below.
Class Documentation
- class MessageDispatcher
The MessageDispatcher class can be used to construct a hierarchical structure for handling a message of some sort. Message dispatchers contain sets of default_handlers with implmentation-specific structure. Each MessageDispatcher can contain any number of child_dispatchers, which are themselves MessageDispatchers. Each message dispatcher must implement a can_handle method which will recurse to return true if the dispatcher or one of its children can handle the message. The class also has a dispatch function that will find handlers for the message. If a child dispatcher says it can handle it, it will be forwarded there. If multiple children at a given recursion level report they can handle a message, all children will receive the message. If no child reports it can handle a message, it will be sent to a default handler at the current level, if any exists.
- name: str
A logging string to describe this recursion level of the dispatcher
- child_dispatchers: list[MessageDispatcher]
those below this message dispatcher in the hierarchy
- default_handlers: table
implementation-specific structure containing the default handlers
- dispatcher_class_name: str
The dispatcher type
- error_handler: function
(driver, device, vararg) A function that will be called if any of the matching handlers raises an error during execution
- fallback_handler: function
(driver, device, vararg) A function that will be called if no matching handler is found when dispatch is called
- lazy_loaded: boolean
a flag to check if handlers have been loaded in the case of a lazy subdriver load
- static init(cls, name, dispatcher_filter, default_handlers, dispatcher_class_name)
Initialize a MessageDispatcher.
- Parameters
cls (
MessageDispatcher
) – the class we are initing (probably a child class)name (
str
) – the name of this dispatcherdispatcher_filter (
function
) – a can_handle function for this MessageDispatcher (in addition to normal child/default matching)default_handlers (
table
) – implementation specific structure of default handlersdispatcher_class_name (
str
) – the class name for logging purposes
- register_child_dispatcher(dispatcher)
Add a child to this dispatcher.
- Parameters
dispatcher (
MessageDispatcher
) – the child dispatcher to add
- find_default_handlers(driver, device, ...)
Return a flat list of default handlers that can handle the message.
This is virtual on this base class, and must be implemented by inheritors.
- find_child_dispatchers(driver, device, ...)
Return a flat list of all the child dispatchers whose can_handle returned true for this message.
- can_handle(driver, device, ...)
Return true if this MessageDispatcher can handle the message.
A message can be handled if either A) a child dispatcher reports it can handle the message or B) self.dispatcher_filter returns true and a default handler will work for this message
- Parameters
- Returns
true if the the message can be handled by this dispatcher, false if it cannot
- Return type
boolean
- Returns
list of this dispatcher’s child dispatchers that can handle this message
- Return type
- get_dispatcher_path()
Return a string showing the hierarchy through which the dispatcher recursed to arrive at this level.
This provides useful trace.
- Returns
something of the form “root -> parent -> me” using the name of each dispatcher
- Return type
str
- dispatch(driver, device, ...)
Find a handler for this message and execute it.
This will either execute child dispatchers or default handlers, but never both. It will, however, execute any number of handlers of the given class that match.
- pretty_print(indent)
Return a multiline string representation of the dispatcher structure.
- Parameters
indent (
number
) – the indent for visually distinguishable representation of the dispatcher hierarchy- Returns
the string representation
- Return type
str
- pretty_print_default_handlers(indent)
Return a multiline string representation of the dispatcher’s default handlers.
This is virtual on this base class, and must be implemented by inheritors.
- Parameters
indent (
number
) – the indent for visually distinguishable representation of the dispatcher hierarchy- Returns
the string representation
- Return type
str
ZigbeeMessageDispatcher
- class ZigbeeMessageDispatcher: MessageDispatcher
This inherits from the MessageDispatcher and is intended to handle ZigbeeMessageRx messages
- name: str
A name of this level of dispatcher used for logging
- child_dispatchers: list[ZigbeeMessageDispatcher]
those below this handler in the hierarchy
- default_handlers: table
The handlers structure from the ZigbeeDriver
- dispatcher_class_name: str
“ZigbeeMessageDispatcher”
- standardize_default_handlers()
Standardize the handlers in a dispatcher by wrapping each handler in
the corresponding ZigbeeMessageGlobalCommandHandler, ZigbeeMessageClusterCommandHandler, ZigbeeMessageHandler or CommandResponseHandler.
- find_default_handlers(driver, device, zb_rx)
Return a flat list of default handlers that can handle the message
These will be one of the attr, global, cluster, or zdo message handlers defined on the driver or sub drivers. However, the attr handlers will be wrapped to allow them to be called with the same structure as the other handler types. E.g. hander(driver, device, zb_rx)
- Parameters
driver (
Driver
) – the driver contextdevice (
st.Device
) – the device the message came from/is forzb_rx (
st.zigbee.ZigbeeMessageRx
) – The received Zigbee message to handle
- Returns
a flat list of the default handlers that can handle this message
- Return type
list[function]
ZwaveDispatcher
- class st.zwave.Dispatcher: MessageDispatcher
This inherits from the MessageDispatcher and handles Z-Wave commands.
- name: str
A logging string to describe this recursion level of the dispatcher
- child_dispatchers: list[st.zwave.Dispatcher]
those below this recursion level in the hierarchy
- default_handlers: table
The zwave_handlers structure from the Driver
- dispatcher_class_name: str
“ZwaveDispatcher”
- find_default_handlers(driver, device, cmd)
Return a flat list of default handlers that can handle this Z-Wave command.
Handlers’ interfaces are of the form of those enclosed in the zwave_handlers table of the base driver, function(driver, device, cmd).
- Parameters
driver (
st.zwave.Driver
) –device (
st.zwave.Device
) –cmd (
st.zwave.Command
) –
- Returns
a flat list of the default callbacks that can handle this message
- Return type
list[function]
- static pretty_print_default_handlers(self, indent)
Return a multiline string representation of the dispatcher’s default handlers.
- Parameters
self (
st.zwave.Dispatcher
) –indent (
number
) – the indent for visually distinguishable representation of the dispatcher hierarchy
- Returns
the string representation
- Return type
str
CapabilityCommandDispatcher
- class CapabilityCommandDispatcher: MessageDispatcher
This inherits from the MessageDispatcher and is intended to handle capabiltiy commands
- name: str
A name of this level of dispatcher used for logging
- child_dispatchers: list[CapabilityCommandDispatcher]
those below this handler in the hierarchy
- default_handlers: table
The capability_handlers structure from the Driver
- dispatcher_class_name: str
“CapabilityCommandDispatcher”
- find_default_handlers(driver, device, cap_command)
Return a flat list of default handlers that can handle this capability command
These will be of the form of the capability_handlers on a driver E.g. hander(driver, device, cap_command)
- static pretty_print_default_handlers(self, indent)
Return a multiline string representation of the dispatchers default handlers
- Parameters
self (
CapabilityCommandDispatcher
) –indent (
number
) – the indent level to allow for the hierarchy to be visually distinguishable
- Returns
the string representation
- Return type
str