Skip to content

Device Class

Manuel Pietschmann edited this page Apr 19, 2021 · 11 revisions

This page serves as an overview of the Device class.

Work in progress

device.h device.cpp

Purpose

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.

Dependencies

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.

Device Dependencies

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.

Testing

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.

State Machine

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

High Level View

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.

Device State Machine

Diagram source: device_bindings.puml

Bindings Sub State Machine

ZDP binding and ZCL reporting configuration is maintained during the full life cycle of the device. Discussion in issues/6

Binding State Machine

Diagram source: device_bindings.puml

Relation to Device Description Files (DDF)

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.

Device Description File (DDF) Init

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.

State Change and REST API Functionality

TODO

Clone this wiki locally