Fix legacy device definition for Adapter V2.0.1 or newer - ioBroker/ioBroker.zigbee GitHub Wiki

How to fix the device definition in order to retain the legacy device states from before V2.0.1

Context

With the change from Adapter version 1.10.x to 2.0.x, the adapter has dropped all adapter specific device definitions into legacy due to a huge change in the payloads sent by the zigbee-herdsman-converters in the update from the zigbee-herdsman-converters 20.x to 21.x. While it is theoretically possible to get the adapter specific device definitions working with the new payloads we (the developers of the adapter) decided to not take this path, as it cements a system which is outdated and requires constant supervision as well as access to the respective devices in order to do so. This is not feasible for the current adapter developers. Anyone who has an affected device is welcome to perform this work and update the legacy definition. This document explains the steps which are required to do so. It also attempts to explain the link between device message payload and state. Please note that this explanation only covers the most common means for this. There are additional considerations which may require an in depth discussion with an adapter developer. In that case, an issue requesting assistance in updating a device should provide the required information.

Structure

Any communication between the zigbee Adapter and the device is going through 2 translation layers. Layer 1 is the translation between the ioBroker State and the message payload. This is handled by the state definition. In case of legacy devices, this is defined explicitly in the file lib/states.js. In case of normal devices, the state definition is generated automatically with information drawn from the zigbee-herdsman-converters through the exposes functionality. Layer 2 is handled by the converter from the zigbee-herdsman-converters library, which handles the conversion between payload and zigbee message data.

The device definition in the zigbee Adapter defines the states a device will have. In case of legacy devices, this is defined explicitly in the file lib/devices.js. In case of normal devices, this is drawn from the zigbee-herdsman-converters through the exposes functionality.

Example code

devices.js:

const legacy_devices = [
.
.
.
{
        models: ['QBKG12LM'],
        icon: 'img/ctrl_ln2.png',
        states: [
            states.left_button, states.right_button, states.left_state, states.right_state,
            states.operation_mode_left, states.operation_mode_right,
            states.left_click_single, states.right_click_single, states.both_click_single,
            states.left_click_double, states.right_click_double, states.both_click_double,
        ],
},
.
.
.
]

states.js:

const states = {
.
.
.

   left_state: {
        id: 'left_state',
        prop: 'state_left',
        name: 'Left switch state',
        icon: undefined,
        role: 'switch',
        write: true,
        read: true,
        type: 'boolean',
        getter: payload => (payload.state_left === 'ON'),
        setter: (value) => (value) ? 'ON' : 'OFF',
        setattr: 'state',
        epname: 'left',
    },
    right_state: {
        id: 'right_state',
        prop: 'state_right',
        name: 'Right switch state',
        icon: undefined,
        role: 'switch',
        write: true,
        read: true,
        type: 'boolean',
        getter: payload => (payload.state_right === 'ON'),
        setter: (value) => (value) ? 'ON' : 'OFF',
        setattr: 'state',
        epname: 'right',
    },
   left_click_single: {
        id: 'left_click',
        prop: 'click',
        name: 'Left click event',
        icon: undefined,
        role: 'button',
        write: false,
        read: true,
        type: 'boolean',
        isEvent: true,
        getter: payload => (payload.click === 'left_single') ? true : undefined,
    },
    left_click_double: {
        id: 'left_click_double',
        prop: 'click',
        name: 'Left click double event',
        icon: undefined,
        role: 'button',
        write: false,
        read: true,
        type: 'boolean',
        isEvent: true,
        getter: payload => (payload.click === 'left_double') ? true : undefined,
    },

.
.
.
}

Note that the code example is from a working device, but it is incomplete.

link between device and device definition:

Any zigbee device contains a 'model' designation on the info screen (see image)

It is this string which is used to identify the device definition from the array of legacy devices.

Things to adjust in order to match a state to a message structure

In order to successfully do this, it is necessary to understand by which means the connection between the payload and the state is made. There are three possible means. Of those, only two are part of the examples above:

  • by id. Used as base fallback if neither of the other two methods trigger: If the payload object contains the property which matches the id property, the value for that property of the payload is set as value for the state. In case of state changes, the payload generated will contain the respective property/value pair. Example Code and Payload pair:
   example_state: {
        id: 'p1',
        write: true,
        read: true,
        type: 'boolean',
    },

Value for the state .p1 is set to 'randomstringvalue' if the payload { p1:'randomstringvalue' } is received. Note that this will generate a warning from the JSController as it is not proper to assign a string value to a boolean state.

State change of the state .p1 by the user to true will generate the payload {p1:true} and send it to the converter.

  • by prop. Used as fallback if no getter/setter is available: If the payload object contains the property which matches the prop property, the value for that property of the payload is set as value for the state. In case of state changes, the payload generated will contain the respective property/value pair. Example Code and Payload pair:
   example_state: {
        id: 'p1',
        prop: 'example'
        write: true,
        read: true,
        type: 'boolean',
    },

Value for the state .p1 is set to 99 if the payload { example:99 } is received. Note that this will generate a warning from the JSController as it is not proper to assign a number value to a boolean state.

State change of the state .p1 by the user to true will generate the payload {example:true} and send it to the converter.

  • by getter/setter. if the getter returns a non-undefined value, it is considered having handled the payload and the state will be updated. In case of state changes, setter (if available) is called with the parameter value Example Code and Payload pair:
   example_state: {
        id: 'p1',
        prop: 'example'
        write: true,
        read: true,
        type: 'boolean',
        getter: payload => (payload.state_right === 'ON'),
        setter: (value) => (value) ? 'ON' : 'OFF',
    },

Value for the state .p1 is set to true if the payload { state_right:'ON' } is received, (to false for any payload which contains the state_right property with a value not equal 'ON'.

State change of the state .p1 by the user to true will generate the payload {state_right:'ON'} and send it to the converter.

Additional considerations

There are a number of further important properties of the state definition:

  • write: if the state is writable by the user
  • read: if the state is readable by the user
  • max: a maximum value for numeric states
  • min: a minimum value for numeric states
  • type: the type of state
  • role: the role for the state as visible in the iobroker state display.
  • isEvent: only valid for boolean states. If this is set to true, the state will return to false 200 ms after it has been set to true by a received payload
  • isOption: this state will not generate its own converter payload. Instead, It will be included when other payload messages are sent.

This list is by no means complete. The states.js file contains a large number of states which can be used as examples for how to handle specific cases.

Warning

It is important to note that changes to states which are being used by multiple devices will affect all those devices. As such, only device-specific states should be updated in this manner. If a global state is being used, it should be copied into a device specific state and then adjusted to the new message structure.

Final notes

A means of generating an external device definition which self contained and only defines the 'modified' states while accessing the common states is under development. Once this is available, this document will be updated to reflect this change. There is no current ETA for this functionality though. Once changes are made successfully, we request that a pull request from a forked adapter repository is made towards the main branch of this adapter. This will allow for us to easily review, accept or decline changes. Note that automatic tests are part of the Pull Request functionality, and we expect any PR to pass most if not all these tests without errors. Any error/warning which remains needs to be justified in a comment to the PR.