-
Notifications
You must be signed in to change notification settings - Fork 0
Device Class
This page serves as an overview of the Device
class.
Work in progress
The class has a generic view of a device which works on dynamic Resource
and ResourceItem
instances without knowing what they represent. The most important detail is that this class knows nothing about specific devices. Internally the class is driven by a state machine which reacts to events and can also emit events to the outside world.
The class isn't aware about sleeping end-devices or routers, the knowledge that a device is able to receive commands comes from the outside via Awake and Poll Events.
The class depends only on a few other classes and modules.
-
Resource
as base class and to reference sub devices, which are just seen as Resource instances; -
Event
to interact with other components and provide lose coupling; -
deCONZ::Node
to access Simple Descriptors and Node Descriptor; -
Device Access (DA) module to execute abstract Parse, Read and Write functions which can be attached to a
ResourceItem
.
Examples: IKEA KADRILJ DDF, state/on item; - ZDP module to query ZDP descriptors during setup;
- ZCL module to query ZCL Basic Cluster attributes during setup.
Note: There are no dependencies to DeRestPlugin
, RestNodeBase
, Sensor
or LightNode
classes, even deCONZ::ApsController
is just an opaque pointer.
All the class sees, is a device which has sub devices (as Resource
) and their ResourceItems
.
Beside loose coupling, the minimal dependencies allow easier testing of the class and its behaviour of the state machine. In general all Device
code is kept small and has no functions longer than two screen pages (if it does it is a bug). The goal is to get close to 100% test coverage, since internally mostly free standing functions are used this becomes straight forward.
First steps to test the Device class behaviour can be found in 001-device-1.cpp.
Each state and sub states can be tested in isolation as well as the complete tasks like device pairing.
TODO
Each state in device.cpp is internaly implemented as a function which acts on Event
where the entry point is the Device::handleEvent(Event)
. The current state is therefore just a function pointer to a state function.
void DEV_InitStateHandler(Device *device, const Event &event);
void DEV_IdleStateHandler(Device *device, const Event &event);
void DEV_NodeDescriptorStateHandler(Device *device, const Event &event);
void DEV_ActiveEndpointsStateHandler(Device *device, const Event &event);
void DEV_SimpleDescriptorStateHandler(Device *device, const Event &event);
void DEV_BasicClusterStateHandler(Device *device, const Event &event);
void DEV_GetDeviceDescriptionHandler(Device *device, const Event &event);
void DEV_BindingHandler(Device *device, const Event &event);
void DEV_BindingTableVerifyHandler(Device *device, const Event &event);
void DEV_PollIdleStateHandler(Device *device, const Event &event);
void DEV_PollNextStateHandler(Device *device, const Event &event);
void DEV_PollBusyStateHandler(Device *device, const Event &event);
The following diagram shows the general flow of the state machine. Once the basic setup is done the device enters the Operating/Idle State which runs multiple states in parallel — as array of function pointers. Going back to Init State can be done at any time, for example when reloading the DDF file. For a already initialized device it takes 3–5 milliseconds to go from Init State to Operating/Idle State.
Other than internal timeouts the timing of the state machine is controlled by events generated from the outside.
Diagram source: device_bindings.puml
ZDP binding and ZCL reporting configuration is maintained during the full life cycle of the device. Discussion in issues/6
Diagram source: device_bindings.puml
The Device
class can only setup a few steps on its own like query ZDP descriptors and ZCL Basic Cluster attributes like modelid and manufacturer name. After that DDF files are used to setup the related sub resources.
This is done outside of the Device
class by a DDF Loader. First a Device
sends an Init Request Event and waits, later on it receives an Init Response Event, at that time all sub device Resources
and ResourceItems
are initialized and the Device
class goes into Operational/Idle state.
The Device Compat (compatibility) module creates empty Sensor
and LightNode
objects and adds them to the respective plugin sensors
and nodes
containers. The DDF Init module then fills the ResourceItems
based on the DDF and registers the Resource
in the Device
.
This way the "old" code as well as the Device
can work side by side, albeit the Device
doesn't know anything about this.
This process can be repeated at any time, for example during development of DDF files they can be reloaded on-the-fly without restarting deCONZ.
TODO