Lights

Best practices for lights.

Lights are devices that are light sources itself or devices that can directly control light sources. This entails smart light bulbs (e.g. LIFX), sockets (e.g. Fibaro Wall Plug) and (flush) modules (e.g. Fibaro Dimmer 2). Sockets can be seen as a different category, however sockets that have the onoff and dim capabilities are most often used for lights and are therefore relevant to consider in this category.

Choosing a device class

In general light devices have the light device class. In some cases socket is more applicable. A socket device has a โ€œWhatโ€™s plugged in?โ€ setting that allows a user to choose a virtual device class (e.g. light). Obviously, for socket devices such as Fibaro Wall Plug and Qubino Plug socket is the required device class.

For (flush) modules, such as the Qubino Flush Dimmer, a tradeoff has to be made. If there are use cases for the device other than controlling a light source the required device class should be socket. The Qubino Flush Dimmer 0 - 10V has various use cases other than controlling light sources, for example bathroom ventilation control. When in doubt, use socket since this allows users to configure the device as light (or something else) after pairing.

Although socket allows the user to choose โ€œwhatโ€™s plugged inโ€ it is required to give a device the class light when possible. This provides a better user experience since it will have the right UI components by default. Otherwise the user needs to find the โ€œwhatโ€™s plugged inโ€ setting to correct it.

Capabilities and behaviour

Implementing capabilities of lights needs some consideration. We start with the two basic capabilities onoff and dim.

Capabilities onoff and dim

A light device can be turned on and off via the toggle UI component (see left image below) as well as the dim level UI component (see right image below). When the device is off, dragging the dim slider from 0 to non-zero must turn the device on (this means the onoff capability needs to be set to true). When the device is on, dragging the dim slider from non-zero to 0 must turn the device off (this means the onoff capability needs to be set to false).

If the device is using a technology that allows it to report its onoff and dim state to Homey when it is changed through external inputs (such as connected switches) this should be reflected in the device UI. If the device is turned off externally, the device should be turned off in Homey, same for dimming. Zigbee and Z-Wave are examples of technologies that almost always support this.

The onoff and dim capabilities should be coupled together and debounced. This can be done using the Device#registerMultipleCapabilityListener() method. This is required because users can create Flows with both an onoff and dim action. In order to prevent duplicate (potentially conflicting) commands being sent to a device (e.g. turn on and dim to 50% which would result in a device turning on to the last known dim value and then dimming back to 50%) these capabilities need to be debounced together and combined into one command to the device.

In the unexpected case that a user creates a conflicting Flow, such as turn on and dim to 0%, or turn off and dim to 50%, make sure the onoff capability is leading. That means, if the dim value is zero, but the new onoff value is true, turn the light on and disregard the dim value and vice versa.

/drivers/<driver_id>/device.js
const Homey = require('homey');
const DeviceApi = require('device-api');
โ€‹
class Device extends Homey.Device {
async onInit() {
this.registerMultipleCapabilityListener(['onoff', 'dim'], async ({ onoff, dim }) => {
if (dim > 0 && onoff === false) {
await DeviceApi.setOnOffAsync(false); // turn off
} else if (dim <= 0 && onoff === true) {
await DeviceApi.setOnOffAsync(true); // turn on
} else {
await DeviceApi.setOnOffAndDimAsync({ onoff, dim }); // turn on or off and set dim level in one command
}
});
}
}
โ€‹
module.exports = Device;

Using the setOnDim capability option for the onoff capability will result in Homey sending only a dim capability set instead of both onoff and dim when a user or Flow changes the dim level of a device.

Color and temperature capabilities

Light devices supporting one or multiple of the following capabilities are a little bit more complex:

  • light_hue

  • light_saturation

  • light_temperature

  • light_mode

A few things are important in this case.

Similar to onoff and dim the color and temperature capabilities need to be grouped and debounced to prevent flickering of the device. In general it is best to debounce the color and temperature capabilities together with onoff and dim, but this might depend on the technology you are using and how the device responds to different commands. The general rule is to prevent flickering (due to multiple commands being sent from one Flow or user action) as much as possible.

โ€‹ โ€‹

When the device is turned off, changing the light_hue, light_saturation, light_temperature or light_mode capabilities must not turn the device on. Only onoff and dim are allowed to change the on/off state of a device. In the case that the user turns the device on through external inputs (e.g. connected switches) the driver should listen (or actively request if needed) for updates with regard to the light_hue, light_saturation, light_temperature or light_mode capabilities and reflect the actual state of the device in the UI.

In the unexpected case that a user creates a conflicting Flow, such as turn off and set color to red, make sure the onoff capability is leading. That means, turn off the light even though the user changed the color to red.

/drivers/<driver_id>/device.js
const Homey = require('homey');
const DeviceApi = require('device-api');
โ€‹
class Device extends Homey.Device {
async onInit() {
const lightCapabilities = [
"onoff",
"dim",
"light_hue",
"light_temperature",
"light_saturation",
"light_mode",
];
this.registerMultipleCapabilityListener(
lightCapabilities,
async ({
onoff,
dim,
light_hue,
light_temperature,
light_saturation,
light_mode
}) => {
// handle the changed capabilities here all at once
await DeviceApi.setOnOffAndDimAndColorAsync();
}
);
}
}
โ€‹
module.exports = Device;