# 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`).

![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-080026791b167448140a2e1dabcf9be397f5c84d%2Flight-device-slider-dim.png?alt=media)

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.

{% tabs %}
{% tab title="JavaScript" %}
The `onoff` and `dim` capabilities should be coupled together and debounced. This can be done using the [`Device#registerMultipleCapabilityListener()`](https://apps-sdk-v3.developer.homey.app/Device.html#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.

{% code title="/drivers/\<driver\_id>/device.js" %}

```javascript
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;
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
The `onoff` and `dim` capabilities should be coupled together and debounced. This can be done using the [`Device#registerMultipleCapabilityListener()`](https://apps-sdk-v3.developer.homey.app/Device.html#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.

{% code title="/drivers/\<driver\_id>/device.mts" %}

```javascript
import Homey from "homey";
import DeviceApi from "./device-api.mjs";

type LightCapabilityValues = { onoff: boolean; dim: number };

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    this.registerMultipleCapabilityListener(["onoff", "dim"], async ({ onoff, dim }: LightCapabilityValues) => {
      if (dim > 0 && !onoff) {
        await DeviceApi.setOnOffAsync(false); // turn off
      } else if (dim <= 0 && onoff) {
        await DeviceApi.setOnOffAsync(true); // turn on
      } else {
        await DeviceApi.setOnOffAndDimAsync({ onoff, dim }); // turn on or off and set dim level in one command
      }
    });
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
The `onoff` and `dim` capabilities should be coupled together and debounced. This can be done using the [`Device#register_multiple_capability_listener()`](https://python-apps-sdk-v3.developer.homey.app/device.html#homey.device.Device.register_multiple_capability_listener) 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.

{% code title="/drivers/\<driver\_id>/device.py" %}

```python
from typing import TypedDict

from device_api import DeviceApi
from homey import device


class LightCapabilityValues(TypedDict, extra_items=device.CapabilityValue):
    onoff: bool
    dim: float


class Device(device.Device):
    async def on_init(self) -> None:
        async def light_capability_listener(
            values: LightCapabilityValues, **kwargs
        ) -> None:
            onoff, dim = values["onoff"], values["dim"]
            if dim > 0 and not onoff:
                await DeviceApi.set_on_off_async(False)  # turn off
            elif dim <= 0 and onoff:
                await DeviceApi.set_on_off_async(True)  # turn on
            else:
                await DeviceApi.set_on_off_and_dim_async(
                    **values
                )  # turn on or off and set dim level in one command

        self.register_multiple_capability_listener(
            ["onoff", "dim"], light_capability_listener
        )


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
Using the `setOnDim` [capability option](https://apps.developer.homey.app/the-basics/capabilities#capability-options) for the `onoff` capability will result in Homey sending only a `dim` capability set instead of both `onoff` and `dim` when a Flow changes the dim level of a device.
{% endhint %}

### 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.

![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-99dfa5d43a89ee55362dc7f4669eac63f919eec3%2Flight-device-color.png?alt=media) ![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-efbe38f58bf522f1a9d1be5755d526f37c139f2b%2Flight-device-color-temperature.png?alt=media)

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.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/\<driver\_id>/device.js" %}

```javascript
const Homey = require('homey');
const DeviceApi = require('device-api');

const lightCapabilities = [
  "onoff",
  "dim",
  "light_hue",
  "light_temperature",
  "light_saturation",
  "light_mode",
];

class Device extends Homey.Device {
  async onInit() {
    this.registerMultipleCapabilityListener(
      lightCapabilities,
      async ({
        onoff,
        dim,
        light_hue,
        light_temperature,
        light_saturation,
        light_mode
      }) => {
        // handle the changed capabilities all at once
        await DeviceApi.setOnOffAndDimAndColorAsync();
      }
    );
  }
}

module.exports = Device;
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/\<driver\_id>/device.mts" %}

```mts
import Homey from "homey";
import DeviceApi from "./device-api.mjs";

type LightCapabilityValues = {
  onoff: boolean;
  dim: number;
  light_hue: number;
  light_temperature: number;
  light_saturation: number;
  light_mode: "color" | "temperature";
};

const lightCapabilities = [
  "onoff",
  "dim",
  "light_hue",
  "light_temperature",
  "light_saturation",
  "light_mode"
];

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    this.registerMultipleCapabilityListener(lightCapabilities, async (values: LightCapabilityValues) => {
      // Handle the changed capabilities all at once
      await DeviceApi.setOnOffAndDimAndColorAsync(values);
    });
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/\<driver\_id>/device.py" %}

```python
from typing import Literal, TypedDict

from device_api import DeviceApi
from homey import device


class LightCapabilityValues(TypedDict, extra_items=device.CapabilityValue):
    onoff: bool
    dim: float
    light_hue: float
    light_temperature: float
    light_saturation: float
    light_mode: Literal["color", "temperature"]


light_capabilities = [
    "onoff",
    "dim",
    "light_hue",
    "light_temperature",
    "light_saturation",
    "light_mode",
]


class Device(device.Device):
    async def on_init(self) -> None:
        async def light_capability_listener(
            values: LightCapabilityValues, **kwargs
        ) -> None:
            # Handle the changed capabilities all at once
            await DeviceApi.set_on_off_and_dim_and_color_async(values)

        self.register_multiple_capability_listener(
            light_capabilities, light_capability_listener
        )


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}
