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 dispatcher

  • dispatcher_filter (function) – a can_handle function for this MessageDispatcher (in addition to normal child/default matching)

  • default_handlers (table) – implementation specific structure of default handlers

  • dispatcher_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.

Parameters
  • driver (Driver) – the driver context

  • device (st.Device) – the device the message came from/is for

  • vararg (table) – MessageDispatcher child implementation specific args

Returns

a flat list of the default handlers that can handle this message

Return type

list[function]

find_child_dispatchers(driver, device, ...)

Return a flat list of all the child dispatchers whose can_handle returned true for this message.

Parameters
  • driver (Driver) – the driver context

  • device (st.Device) – the device the message came from/is for

  • vararg (table) – MessageDispatcher child implementation specific args

Returns

the flat list of child dispatchers that can handle this message

Return type

list[MessageDispatcher]

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
  • driver (Driver) – the driver context

  • device (st.Device) – the device the message came from/is for

  • vararg (table) – MessageDispatcher child implementation specific args

Returns

true if the the message can be handled by this dispatcher, false if it cannot

Return type

boolean

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.

Parameters
  • driver (Driver) – the driver context

  • device (st.Device) – the device the message came from/is for

  • vararg (table) – MessageDispatcher child implementation specific args

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
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
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)

Parameters
  • driver (Driver) – the driver context

  • device (st.Device) – the device the message came from/is for

  • cap_command (CapabilityCommand) – The capability command table

Returns

a flat list of the default handlers that can handle this message

Return type

list[function]

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