# Zigbee

Before you can get started developing Zigbee apps for Homey, a good place to start is with the basic Zigbee concepts and terminology. We will explain everything you need to know to get started here.

![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-a65befafdac38760ccd97fd8c3fba8ac7f5e6e33%2Fzigbee-mesh.png?alt=media)

{% hint style="info" %}
You can view a working example of a Homey App that uses Zigbee at: <https://github.com/athombv/com.ikea.tradfri-example>
{% endhint %}

### Endpoints and Clusters

A Zigbee device is made up of one or more `endpoints`, these are collections of `clusters`. A cluster is essentially a capability of a Zigbee device.

A cluster can be implemented in two ways:

* As server
* As client

From the [Zigbee Cluster Specification](https://etc.athom.com/zigbee_cluster_specification.pdf): "Typically, the entity that stores the attributes of a cluster is referred to as the server of that cluster and an entity that affects or manipulates those attributes is referred to as the client of that cluster." More information on this can be found in the [Zigbee Cluster Specification](https://etc.athom.com/zigbee_cluster_specification.pdf) section 2.2.2.

### Commands and Attributes

Every cluster supports a set of commands and attributes. Attributes are properties of the cluster which can be read, written to and reported to any other bound node. An example would be the "current level" attribute implemented by the "level control" cluster, it represents the value of the current level of the device. The cluster may also support commands which are used to perform actions. An example would be the "move to level" command implemented by the "level control" cluster which makes the cluster change its current level to the level set by the command.

Commands can be sent in two directions:

1. from client to server
2. from server to client

The first is probably the most common direction, for example when sending a command from Homey (the client) to a bulb (the server) using the "onOff" cluster and the "toggle" command. The second is when a node (the server) sends a command to Homey (the client), for example a remote which sends the "toggle" command from the "onOff" cluster to Homey to indicate that the toggle button was pressed.

A cluster can report attributes, this means it will send a message to any bound node whenever the value of the attribute changes. Not all attributes are reportable, check the Zigbee Cluster Specification for the specifics of each attribute. In order to make a cluster report an attribute, a binding must be created between the reporting and receiving node.

### Bindings and Bound Clusters

The difference between server and client clusters is important for the following reason. Nodes can be receivers of commands (i.e. servers), or senders of commands (i.e. clients), and sometimes both. Receiving commands from a node most often requires a binding to be made from the controller (in this case Homey) to the cluster on the node, as well as an implementation of `BoundCluster` to receive and handle the incoming commands in your app. For more information on implementing a `BoundCluster` check out the API section.

### Groups

Zigbee allows another way of inter-node communication which is called groups. This concept can be compared to association groups in Z-Wave. It allows nodes to listen to broadcast messages from other nodes. For example, a remote control which broadcasts its commands to a set of light bulbs. The light bulbs would need to be in the same group as the remote is broadcasting on in order to be controlled with the remote. In order to group devices together the Find and Bind procedure can be used, often this means holding the controlling device close to the receiving device and initiating a commissioning process (how to initiate this process differs from device to device, check the device's manual for these instructions).

By default, Homey listens to all group broadcast communication on its network. Therefore, it does not have to be added to a node's group using Find and Bind in order to receive its commands. This makes it very easy to implement functionality based on group communication in your app.

### Example structure of a Zigbee Node

![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-b4c75a6a8d203424ca0f7d8127d6b34b17477ab9%2Fzigbee-node-structure.png?alt=media)

### Routers, End Devices and SEDs

There are a couple of different types of Zigbee devices:

* *Routers*: these are nodes on the network that are capable of routing messages between devices. Usually these are the non-battery powered devices. These extend the range of the Zigbee mesh network.
* *End Devices*: these are nodes on the network that are not capable of routing messages between devices. Usually these are battery powered devices. These do not extend the range of the Zigbee mesh network.

An important concept in Zigbee is Sleepy End Device (SED). SEDs are End Devices which are asleep most of the time, they only wake up to poll their parent (the Router they are paired to) for new messages every once in a while. A Router keeps track of a couple of messages targeted at the SED while it is asleep, so that the SED can retrieve these messages from the Router when it awakens.

When communicating with SEDs it is important to consider the possibility that it may not respond any time soon, or not at all. Most devices remain awake for a short amount of time directly after pairing, this is therefore the only time you can reliably communicate with the node. Additionally, since the Router has to store the messages for the SED, and the SED wakes up on a variable interval and fetches a single message every time, it is advised to only perform one request at a time for the most reliable communication.

A SED can be identified by the "Receive When Idle" flag in the nodes table in the [Zigbee developer tools](https://tools.developer.homey.app/tools/zigbee) or programmatically as a property of `ZigBeeNode`.

### Zigbee Cluster Specification

The full Zigbee Cluster Specification can be found at [Zigbee Cluster Specification (PDF)](https://etc.athom.com/zigbee_cluster_specification.pdf). This contains all information on clusters, commands and attributes you would need to create a driver for a Zigbee device.

## Pairing

The first step in creating a Zigbee driver is to retrieve the following properties of the device:

* `manufacturerName`
* `productId`

These can both be found by pairing the device as Basic Zigbee Device to Homey. Pairing is completely handled by Homey, similar to Z-Wave drivers and in contrast to other types of drivers, you don't have to implement your own pairing views. After pairing check the device settings or go to the [Zigbee developer tools](https://tools.developer.homey.app/tools/zigbee) and take a look at the nodes table where you can find the `manufacturerName` and `productId`.

The second step is finding out how the endpoints and clusters are structured on the device. In order to retrieve this information, interview the device from the Zigbee developer tools.

{% hint style="info" %}
For Sleepy End Devices this interview might take a while. After it finishes it will provide the required information.
{% endhint %}

## Manifest

After retrieving the `manufacturerName`, `productId` and endpoint definitions, the driver's manifest can be created. This is where is defined that this driver is for a Zigbee device, for which Zigbee device specifically (hence the `manufacturerName` and `productId`), and what the endpoints and clusters of this Zigbee device are (hence the endpoint definitions).

This information is added to the `zigbee` object to the driver's manifest.

{% code title="/drivers/\<driver\_id>/driver.compose.json" %}

```javascript
{
  "name": { "en": "My Driver" },
  "class": "socket",
  "capabilities": ["onoff", "dim"],
  "zigbee": {
    "manufacturerName": "DummyManuf",
    "productId": ["control outlet 123"],
    "endpoints": {
      "1": {
        "clusters": [0, 4, 5, 6],
        "bindings": [6]
      }
    },
    "learnmode": {
      "image": "/drivers/my_driver/assets/learnmode.svg",
      "instruction": { "en": "Press the button on your device three times" }
    }
  }
}
```

{% endcode %}

The `zigbee` object contains the following properties:

* `manufacturerName`: This is the manufacturer id which is needed to identify the device.
* `productId`: This is the product id which is needed to identify the device. It is possible to add multiple product ids if the driver targets a couple of very similar devices with different product ids.
* `endpoints`: This is the endpoint definition for the device. Only the endpoints and clusters listed here will be available on the `ZCLNode` instance (see the documentation on `homey-zigbeedriver` and `zigbee-clusters` in below in the API section). The keys of the `endpoints` object refer to the endpoint id of the node.
  * `clusters`: This lists the cluster ids we want to implement as client. This means, the clusters we want to send commands to, or read attributes from, on the remote node.
  * `bindings`: This lists the cluster ids we want to implement as server. This means, the clusters we want to be able to receive commands on from a remote node. For each entry in `bindings` a bind request will be made to the remote node during pairing. In case you want to implement attribute reporting for a specific cluster, add the cluster id here and the required binding will be made during pairing.

## Dependencies

In order to create a Zigbee driver a Homey App needs the following dependencies:

* [homey-zigbeedriver](https://athombv.github.io/node-homey-zigbeedriver/)
* [zigbee-clusters](https://github.com/athombv/node-zigbee-clusters)

You can install these dependencies by running:

```bash
npm install --save homey-zigbeedriver zigbee-clusters
```

{% hint style="warning" %}
Note that zigbee-clusters is a `peerDependency` of homey-zigbeedriver. This means that homey-zigbeedriver depends on zigbee-clusters being installed with a compatible version.
{% endhint %}

## Driver and Device

Finally, the driver needs to be created. Most of the time we can suffice with only `/drivers/my_driver/device.js`. Please refer to the Drivers guide for more information on this topic.

The easiest way to implement a Zigbee driver is to use `homey-zigbeedriver`, which is a library we've created which does a lot of the heavy lifting for Zigbee apps. Take a look at the API section for the specifics of this library. Below, a number of basic implementations of various aspects of a Zigbee driver is demonstrated based on `homey-zigbeedriver`.

### Best Practices for Device Initialization

Avoid initiating communication with the node in the `onInit` or `onNodeInit` phases. Zigbee may not be ready during the initialization of the device, leading to undesirable consequences. Communicating with the node in `onInit` or `onNodeInit` can result in queuing numerous requests when the app is started, potentially causing performance bottlenecks.

If communication with the node is attempted in `onInit` or `onNodeInit` without catching the request promise, the device may become unavailable, accompanied by an error message indicating that Zigbee was not ready. This is true for versions up to `homey-zigbeedriver@2.1.3`. While versions 2.1.4 and newer prevent the device from becoming unavailable, there is a risk that the `onInit` or `onNodeInit` may not complete as expected.

If there's a genuine need to request data in the `onInit` or `onNodeInit` phases, it's important to handle the request promise appropriately. Failing to catch the promise may lead to unforeseen issues. Below is a recommended approach:

#### Do

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // Read the "onOff" attribute from the "onOff" cluster
    const currentOnOffValue = await zclNode.endpoints[1].clusters.onOff.readAttributes(
      ["onOff"]
    ).catch(err => { this.error(err); /* Always catch Promises, especially in onNodeInit */ });
  }
}

module.exports = Device;
```

#### Don't

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // Read the "onOff" attribute from the "onOff" cluster
    const currentOnOffValue = await zclNode.endpoints[1].clusters.onOff.readAttributes(
      ["onOff"]
    );
  }
}

module.exports = Device;
```

#### First initialization

In some cases, you might want to perform certain actions only the first time `onInit` or `onNodeInit` is called, right after adding the device to Homey. You can achieve this using `ZigbeeDevice.isFirstInit()` from `homey-zigbeedriver`. Here's an example:

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
  
    // Only if this is the first time this device is initialized, right after
    // adding the device to Homey
    if (this.isFirstInit() === true) {
      // Read the "onOff" attribute from the "onOff" cluster
      const currentOnOffValue = await zclNode.endpoints[1].clusters.onOff.readAttributes(
        ["onOff"]
      ).catch(err => { this.error(err); /* Always catch Promises, especially in onNodeInit */ });
    }
  }
}

module.exports = Device;
```

### Debugging

When developing a Zigbee driver, it can be very useful to see all Zigbee communication between Homey and the Zigbee node. Debug logging for this can be enabled very easily as follows.

> It is not recommended to keep this debug logging enabled when publishing your app.

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

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");
const { debug } = require("zigbee-clusters");

// Enable debug logging of all relevant Zigbee communication
debug(true);

class Device extends ZigBeeDevice {}

module.exports = Device;
```

{% endcode %}

Example logging of a received attribute report frame:

```
2020-08-07T13:04:30.933Z zigbee-clusters:cluster ep: 1, cl: illuminanceMeasurement (1024) received frame reportAttributes illuminanceMeasurement.reportAttributes {
  attributes: <Buffer 00 00 21 7e 00>
}
```

### Commands

The `zclNode` is an instance of `ZCLNode` as exported by `zigbee-clusters` (check the API section for more information on this library). It can be used to directly communicate with the node using the Zigbee Cluster Library (ZCL).

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

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // Send the "toggle" command to cluster "onOff" on endpoint 1
    await zclNode.endpoints[1].clusters.onOff.toggle()
      .catch(err => { this.error(err); /* Always catch Promises, especially in onNodeInit */ });

    // Read the "onOff" attribute from the "onOff" cluster
    const currentOnOffValue = await zclNode.endpoints[1].clusters.onOff.readAttributes(
      ["onOff"]
    ).catch(err => { this.error(err); /* Always catch Promises, especially in onNodeInit */ });
  }
}

module.exports = Device;
```

{% endcode %}

### Capabilities

Using `homey-zigbeedriver` it is very easy to map Homey's capabilities to Zigbee clusters. The library contains a set of [system capabilities](https://github.com/athombv/node-homey-zigbeedriver/tree/master/lib/system/capabilities), which are basically very common mappings between capabilities and clusters. It is possible to extend these system capabilities when registering them, for more information take a look at the `homey-zigbeedriver` [documentation](https://github.com/athombv/node-homey-zigbeedriver) and API section.

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

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");
const { CLUSTER } = require("zigbee-clusters");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // This maps the `onoff` capability to the "onOff" cluster
    this.registerCapability("onoff", CLUSTER.ON_OFF);

    // This maps the `dim` capability to the "levelControl" cluster
    this.registerCapability("dim", CLUSTER.LEVEL_CONTROL);
  }
}

module.exports = Device;
```

{% endcode %}

### Attribute Reporting

As described above, nodes can report attribute changes to any other bound node. This often requires a binding to be made to the node that should report. This can be done using the driver's manifest `bindings` property. After that, the attribute reporting must be configured on the node's cluster. The example below demonstrates two ways of configuring attribute reporting:

1. Directly configuring the attribute reporting.
2. Configuring the attribute reporting in combination with mapping it to a capability.

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

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");
const { CLUSTER } = require("zigbee-clusters");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // 1.1.) Configure attribute reporting without registering a capability
    await this.configureAttributeReporting([
      {
        endpointId: 1,
        cluster: CLUSTER.COLOR_CONTROL,
        attributeName: "currentHue",
        minInterval: 0,
        maxInterval: 300,
        minChange: 10,
      },
    ]).catch(err => { this.error(err); /* Always catch Promises, especially in onNodeInit */ });

    // 1.2.) Listen to attribute reports for the above configured attribute reporting
    zclNode.endpoints[1].clusters.colorControl.on(
      "attr.currentHue",
      (currentHue) => {
        // Do something with the received attribute report
      }
    );

    // 2) This maps the `dim` capability to the "levelControl" cluster and additionally configures attribute reporting for the `currentLevel` attribute as specified in the system capability
    this.registerCapability("dim", CLUSTER.LEVEL_CONTROL, {
      reportOpts: {
        configureAttributeReporting: {
          minInterval: 0, // No minimum reporting interval
          maxInterval: 60000, // Maximally every ~16 hours
          minChange: 5, // Report when value changed by 5
        },
      },
    });
  }
}

module.exports = Device;
```

{% endcode %}

For more information on configuring attribute reporting check [ZigBeeDevice#configureAttributeReporting](https://athombv.github.io/node-homey-zigbeedriver/ZigBeeDevice.html#configureAttributeReporting).

> It is recommended to configure attribute reporting for sleepy end devices. This ensures that the device periodically sends a message to Homey, allowing Homey to verify that the device is still present on the network. In the future, Homey may use this information to show users which devices have been unresponsive for some time.

### Bindings and Groups

In order to act on incoming commands from bindings or groups it is necessary to implement a `BoundCluster` (this is exported by `zigbee-clusters`):

{% code title="/lib/LevelControlBoundCluster.js" %}

```javascript
const { BoundCluster } = require("zigbee-clusters");

class LevelControlBoundCluster extends BoundCluster {
  constructor({ onMove }) {
    super();
    this._onMove = onMove;
  }

  // This function name is directly derived from the `move`
  // command in `zigbee-clusters/lib/clusters/levelControl.js`
  // the payload received is the payload specified in
  // `LevelControlCluster.COMMANDS.move.args`
  move(payload) {
    this._onMove(payload);
  }
}

module.exports = LevelControlBoundCluster;
```

{% endcode %}

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

```javascript
const LevelControlBoundCluster = require("../../lib/LevelControlBoundCluster");

const { ZigBeeDevice } = require("homey-zigbeedriver");
const { CLUSTER } = require("zigbee-clusters");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // Register the `BoundCluster` implementation with the `ZCLNode`
    zclNode.endpoints[1].bind(
      CLUSTER.LEVEL_CONTROL.NAME,
      new LevelControlBoundCluster({
        onMove: (payload) => {
          // Do something with the received payload
        },
      })
    );
  }
}

module.exports = Device;
```

{% endcode %}

For more information on implementing a bound cluster checkout the `zigbee-clusters` documentation on [Implementing a bound cluster](https://athombv.github.io/node-zigbee-clusters/).

### Custom Clusters

It is possible to implement custom clusters, these are often manufacturer specific implementations of existing clusters. This is too in-depth to cover here, but is documented in [Implementing a cluster](https://github.com/athombv/node-zigbee-clusters#implementing-a-cluster) and [Implementing a custom cluster](https://github.com/athombv/node-zigbee-clusters#implementing-a-custom-cluster).

## Sub Devices

> This feature is available as of Homey v5.0.0 and requires homey-zigbeedriver\@1.6.0 or higher. Additionally, `Driver` must extend `ZigBeeDriver` as exported by homey-zigbeedriver.

In some cases a single physical Zigbee device should be represented as multiple devices in Homey after pairing. Most physical Zigbee devices should be represented by a single Homey device for the best user experience. However, for some devices, like a socket with multiple outputs, the user experience is improved when there are multiple Homey devices representing it. Sub devices enable you to automatically create multiple devices in Homey after pairing a single physical Zigbee device. In order for Homey to create multiple instances of `Device` you need to define a `devices` property in your Zigbee driver's manifest. The keys of this object represent a unique sub device ID which will be added to the device data object as `subDeviceId`. For example, based on the manifest below:

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

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    const { subDeviceId } = this.getData();
    // subDeviceId === 'secondOutlet'
  }
}

module.exports = Device;
```

{% endcode %}

This can be used in `device.js` to discern between the root device, and the various sub devices.

The sub device object can contain any property the root device can contain, e.g. `class`, `name`, and `capabilties`. Properties that are omitted in the sub device will be copied over from the root device. If the sub device should not get the same settings as the root device, make sure to add the `settings: []` property to the sub device, as demonstrated below.

{% code title="/drivers/\<driver\_id>/driver.compose.json" %}

```javascript
{
  "name": { "en": "My Driver" },
  "class": "socket",
  "capabilities": ["onoff", "dim"],
  "zigbee": {
    "manufacturerName": "DummyManuf",
    "productId": ["control outlet 123"],
    "endpoints": {
      "1": {
        "clusters": [0, 4, 5, 6],
        "bindings": [6]
      }
    },
    "devices": {
      "secondOutlet": {
        "class": "light",
        "capabilities": ["onoff"],
        "name": { "en": "Second Outlet" },
        "settings": []
      }
    }
  }
}
```

{% endcode %}

Each `Device` will have access to the same `ZCLNode` instance and can access all endpoints. By default, for each sub device a device instance, as exported in `device.js`, will be created. If the implementation of `device.js` is quite different between sub devices you can use [`Driver#onMapDeviceClass()`](https://apps-sdk-v3.developer.homey.app/Driver.html#onMapDeviceClass) to properly separate the logic for the root and sub devices by implementing multiple `Device` classes.

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

```javascript
const { ZigBeeDriver } = require("homey-zigbeedriver");

const RootDevice = require("./device.js");
const SecondOutletDevice = require("./secondOutlet.device.js");

class Driver extends ZigBeeDriver {
  onMapDeviceClass(device) {
    if (device.getData().subDeviceId === "secondOutlet") {
      return SecondOutletDevice;
    } else {
      return RootDevice;
    }
  }
}

module.exports = Driver;
```

{% endcode %}

## ZCL Intruder Alarm Systems (IAS)

As of Homey v13.1.2, Homey automatically handles full IAS Zone enrollment. Just add cluster id `1280` (IAS Zone) to the `clusters` array in driver.compose.json for the relevant endpoint. `onZoneEnrollRequest` events are no longer forwarded to apps. Homey always assigns `zoneId: 0`.

## API

There are three Zigbee API levels which all build on top of each other with increasing complexity to interact with Zigbee devices on Homey:

![](https://998911913-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MPk9cn4V7WnnKt7fbry%2Fuploads%2Fgit-blob-f153dc2f59237067b957d15d36da6b1a147c2bf4%2Fzigbee-api-overview.png?alt=media)

In general it is likely you will only need [homey-zigbeedriver](https://github.com/athombv/node-homey-zigbeedriver), and possibly [zigbee-clusters](https://github.com/athombv/node-zigbee-clusters) if you want to do some more advanced things. The Zigbee API is what `zigbee-clusters` is built on. It is not advised to use this API directly, but it is there in case you need it.

### 1. homey-zigbeedriver

This library for Node.js is developed by Athom to make it easier to create a driver for Zigbee devices.

It exposes classes which can be extended in your app and other useful functionality. The most important class is [`ZigBeeDevice`](https://athombv.github.io/node-homey-zigbeedriver/ZigBeeDevice.html). This class handles getting a `ZigBeeNode` and uses [`zigbee-clusters`](https://github.com/athombv/node-zigbee-clusters) to extend the ZigBeeNode with Zigbee Cluster Library (ZCL) functionality. Doing this enables a developer to directly communicate with the ZigBeeNode using ZCL very easily. The basic usage is demonstrated below, for more in-depth information take a look at the `homey-zigbeedriver` [documentation](https://athombv.github.io/node-homey-zigbeedriver/) or the [source code on GitHub](https://github.com/athombv/node-homey-zigbeedriver).

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

```javascript
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    await zclNode.endpoints[1].clusters.onOff.toggle()
      .catch(err => { this.error(err); /* Always catch Promises, especially in onNodeInit */ });
  }
}

module.exports = Device;
```

{% endcode %}

Note: for Zigbee light devices (e.g. bulbs and spots) we created [ZigBeeLightDevice](https://athombv.github.io/node-homey-zigbeedriver/ZigBeeLightDevice.html), this class can be extended in your driver and will by default handle all light related functionality.

### 2. zigbee-clusters

This is the library used by [homey-zigbeedriver](https://github.com/athombv/node-homey-zigbeedriver) to expose the Zigbee Cluster Library (ZCL) functionality. It is currently not available for Python apps. It implements all the clusters that are accessible through `zclNode` in [`lib/clusters`](https://github.com/athombv/node-zigbee-clusters/tree/master/lib/clusters). It is very easy to add new clusters, or add attributes or commands to an existing cluster, merely by changing the definition in one of the cluster files (e.g. the [onOff cluster](https://github.com/athombv/node-zigbee-clusters/blob/master/lib/clusters/onOff.js)). If you are interested in how this library works, take a look at the [Zigbee Cluster Specification (PDF)](https://etc.athom.com/zigbee_cluster_specification.pdf). The basic usage of this library is demonstrated below, note that it is usually better to use [`ZigBeeDevice`](https://athombv.github.io/node-homey-zigbeedriver/ZigBeeDevice.html) exported by [homey-zigbeedriver](https://github.com/athombv/node-homey-zigbeedriver) instead.

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

```javascript
const Homey = require("homey");
const { ZCLNode, CLUSTER } = require("zigbee-clusters");

class Device extends Homey.Device {
  async onInit() {
    // Get ZigBeeNode instance from ManagerZigBee
    const node = await this.homey.zigbee.getNode(this);

    // Create ZCLNode instance
    const zclNode = new ZCLNode(node);

    // Interact with the node
    await zclNode.endpoints[1].clusters.onOff.toggle()
      .catch(err => { this.error(err); /* Always catch Promises */ });
  }
}

module.exports = Device;
```

{% endcode %}

There are a few cases where you *need* to interact with [zigbee-clusters](https://github.com/athombv/node-zigbee-clusters) in your app.

#### 1. Interacting with homey-zigbeedriver

In order to inform `homey-zigbeedriver` about which cluster we are targeting we need to import the cluster specification from `zigbee-clusters` as demonstrated below.

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

```javascript
const { CLUSTER } = require("zigbee-clusters");
const { ZigBeeDevice } = require("homey-zigbeedriver");

class Device extends ZigBeeDevice {
  async onNodeInit({ zclNode }) {
    // Register onoff capability
    this.registerCapability("onoff", CLUSTER.ON_OFF);
  }
}

module.exports = Device;
```

{% endcode %}

This ensures the cluster to be registered will be available in `zigbee-clusters`.

#### 2. Implementing a new cluster, or improving an existing cluster

To add or improve clusters for `zigbee-clusters` , changes can be made to the cluster definition in [`lib/clusters`](https://github.com/athombv/node-zigbee-clusters/tree/master/lib/clusters). For more information on how to do this, check out [Implementing a cluster](https://github.com/athombv/node-zigbee-clusters#implementing-a-cluster).

#### 3. Implementing a bound cluster

As mentioned in the introduction, in order to receive commands from a node, a binding must be made to the respective cluster. This can be done by listing the cluster id in the `bindings` array in the driver's manifest. Please refer to [Implementing a bound cluster](https://github.com/athombv/node-zigbee-clusters#implementing-a-bound-cluster) for more information on how to create a `BoundCluster` in your driver.

#### 4. Implementing a custom cluster

Zigbee device manufacturers are allowed to implement custom clusters for their devices. In order to use such a custom cluster in your driver you need to extend an existing cluster with the custom behaviour. Check out [Implementing a custom cluster](https://github.com/athombv/node-zigbee-clusters#implementing-a-custom-cluster) on how to do this exactly.

### 3. Zigbee API

The Zigbee API can be used for directly communicating with a `ZigBeeNode` if needed. In general it is advised not to use this API and take a look at [zigbee-clusters](https://github.com/athombv/node-zigbee-clusters) and [homey-zigbeedriver](https://github.com/athombv/node-homey-zigbeedriver) which are libraries built on top of the Zigbee API which do most of the heavy lifting and make it even easier to develop Zigbee apps for Homey.

In the unexpected case that you do want to access the Zigbee API take a look below for a basic example.

{% tabs %}
{% tab title="JavaScript" %}
First, the `ZigBeeNode` must be retrieved from [`ManagerZigBee`](https://apps-sdk-v3.developer.homey.app/ManagerZigBee.html).

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

```javascript
const Homey = require("homey");

class Device extends Homey.Device {
  async onInit() {
    const node = await this.homey.zigbee.getNode(this);
  }
}

module.exports = Device;
```

{% endcode %}

Next, we can use the Zigbee API to directly communicate with the `ZigBeeNode` using [`ZigBeeNode#sendFrame()`](https://apps-sdk-v3.developer.homey.app/ZigBeeNode.html#sendFrame) and [`ZigBeeNode#handleFrame()`](https://apps-sdk-v3.developer.homey.app/ZigBeeNode.html#handleFrame)

{% hint style="warning" %}
Important: override the `handleFrame` method on `ZigBeeNode`, this method is called when a frame is received and if it is not overridden it will throw.
{% endhint %}

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

```javascript
const Homey = require("homey");

class Device extends Homey.Device {
  async onInit() {
    const node = await this.homey.zigbee.getNode(this);
    node.handleFrame = (endpointId, clusterId, frame, meta) => {
      if (endpointId === 1 && clusterId === 6) {
        // The node sent a frame to Homey from endpoint 1 and cluster 'onOff'
      }
    };

    // Send a frame to endpoint 1, cluster 6 ('onOff') which turns the node on
    await node.sendFrame(
      1, // endpoint id
      6, // cluster id
      Buffer.from([
        1, // frame control
        0, // transaction sequence number
        1, // command id ('on')
      ])
    ).catch(err => { this.error(err); /* Always catch Promises */ });

    // Send a frame to endpoint 1, cluster 6 ('onOff') which turns the node off
    await node.sendFrame(
      1, // endpoint id
      6, // cluster id
      Buffer.from([
        1, // frame control
        1, // transaction sequence number
        0, // command id ('off')
      ])
    ).catch(err => { this.error(err); /* Always catch Promises */ });
  }
}

module.exports = Device;
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
First, the `ZigBeeNode` must be retrieved from [`ManagerZigBee`](https://apps-sdk-v3.developer.homey.app/ManagerZigBee.html).

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

```mts
import Homey from "homey";

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    const node = await this.homey.zigbee.getNode(this);
  }
}

```

{% endcode %}

Next, we can use the Zigbee API to directly communicate with the `ZigBeeNode` using [`ZigBeeNode#sendFrame()`](https://apps-sdk-v3.developer.homey.app/ZigBeeNode.html#sendFrame) and [`ZigBeeNode#handleFrame()`](https://apps-sdk-v3.developer.homey.app/ZigBeeNode.html#handleFrame)

{% hint style="warning" %}
Important: override the `handleFrame` method on `ZigBeeNode`, this method is called when a frame is received and if it is not overridden it will throw.
{% endhint %}

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

```mts
import Homey from "homey";

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    const node = await this.homey.zigbee.getNode(this);
    node.handleFrame = async (endpointId: number, clusterId: number, frame: Buffer, meta: object): Promise<void> => {
      if (endpointId === 1 && clusterId === 6) {
        // The node sent a frame to Homey from endpoint 1 and cluster 'onOff'
      }
    };

    await node
      .sendFrame(
        1, // endpoint id
        6, // cluster id
        Buffer.from([
          1, // frame control
          0, // transaction sequence number
          1, // command id ('on')
        ]),
      )
      .catch(this.error /* Always catch Promises */);

    // Send a frame to endpoint 1, cluster 6 ('onOff') which turns the node off
    await node
      .sendFrame(
        1, // endpoint id
        6, // cluster id
        Buffer.from([
          1, // frame control
          1, // transaction sequence number
          0, // command id ('off')
        ]),
      )
      .catch(this.error /* Always catch Promises */);
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
First, the `ZigBeeNode` must be retrieved from [`ManagerZigBee`](https://python-apps-sdk-v3.developer.homey.app/manager/zigbee.html).

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

```python
from homey import device


class Device(device.Device):
    async def on_init(self) -> None:
        node = await self.homey.zigbee.get_node(self)


homey_export = Device

```

{% endcode %}

Next, we can use the Zigbee API to directly communicate with the `ZigBeeNode` using [`ZigBeeNode#send_frame()`](https://python-apps-sdk-v3.developer.homey.app/zigbee_node.html#homey.zigbee_node.ZigbeeNode.send_frame) and [`ZigBeeNode#handle_frame()`](https://python-apps-sdk-v3.developer.homey.app/zigbee_node.html#homey.zigbee_node.ZigbeeNode.handle_frame)

{% hint style="warning" %}
Important: override the `handle_frame` method on `ZigBeeNode`, this method is called when a frame is received and if it is not overridden it will throw.
{% endhint %}

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

```python
from homey import device


class Device(device.Device):
    async def on_init(self) -> None:
        node = await self.homey.zigbee.get_node(self)

        async def handle_frame(
            endpoint_id: int, cluster_id: int, frame: bytes, meta: dict
        ) -> None:
            if endpoint_id == 1 and cluster_id == 6:
                # The node sent a frame to Homey from endpoint 1 and cluster 'onOff'
                ...

        node.handle_frame = handle_frame

        try:
            await node.send_frame(
                1,  # endpoint id
                6,  # cluster id
                bytes(
                    [
                        1,  # frame control
                        0,  # transaction sequence number
                        1,  # command id ('on')
                    ]
                ),
            )
        except Exception as e:
            self.error(e)  # Always handle exceptions

        try:
            await node.send_frame(
                1,  # endpoint id
                6,  # cluster id
                bytes(
                    [
                        1,  # frame control
                        1,  # transaction sequence number
                        0,  # command id ('off')
                    ]
                ),
            )
        except Exception as e:
            self.error(e)  # Always handle exceptions


homey_export = Device

```

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