Adding ZCL clusters to application

Once you are familiar with Zigbee in the nRF Connect SDK and you have tested some of the available Zigbee samples, you can use the Zigbee template sample to create your own application with a custom set of ZCL clusters. For example, you can create a sensor application that uses a temperature sensor with an on/off switch, with the sensor periodically updating its measured value when it is active.

Adding ZCL clusters to an application consists of expanding the Zigbee template sample with new application clusters. By default, the template sample includes only mandatory Zigbee clusters, that is the Basic and Identify clusters, required to identify a device within a Zigbee network.

Cluster is a representation of a single functionality within a Zigbee node. Each cluster contains attributes that are stored in the device’s memory and commands that can be used to modify or read the state of the device, including the cluster attributes. Clusters appropriate for a single device type such as a sensor or a light bulb are organized into an addressable container that is called an endpoint. For more information about the ZCL terminology, see Common ZCL terms and definitions in the ZBOSS stack user guide.

An application can implement appropriate callback functions to be informed about specific cluster state changes. These functions can be used to alter the device’s behavior when the state of a cluster is changing as a result of some external event.

Read the following sections for detailed steps about how to expand the Zigbee template sample.

Requirements

To take advantage of this guide, you need to be familiar with Zigbee architectures and Configuring Zigbee in nRF Connect SDK, and have built and tested at least one of the available Zigbee samples.

To test the functionalities implemented in this user guide, you need the following hardware and software:

You can also optionally use nRF Sniffer for 802.15.4 configured for capturing Zigbee packets with Wireshark (see Configuring Wireshark for Zigbee) to gather some of the required information, such as the short address of a Zigbee node. However, this guide does not describe Wireshark usage.

Copy Zigbee template sample

Use the Zigbee template sample as the base for adding new ZCL clusters:

  1. Make sure that you meet the requirements for building the sample.

  2. Build and test the sample as described on its documentation page.

  3. Copy the contents of the samples/zigbee/template directory to a new directory meant for your custom application. For example, samples/zigbee/sensor.

Add On/Off Switch and Temperature Sensor functionalities

As mentioned in the introduction, each functionality of a Zigbee node is defined as a cluster, with its unique group of attributes, commands, or functions. Clusters can be grouped into devices with unique device identifiers. Moreover, clusters can have either server or client role, which defines the way they are used.

To learn more about the ZCL clusters and their terminology, see the following documentation available to the Connectivity Standards Alliance members:

  • Home Automation Profile specification

  • ZCL library specification, Client/Server Model section

Extending the Zigbee node’s functionalities with an On/Off Switch and a Temperature Sensor requires adding On/Off Switch and Temperature Sensor logical devices defined in the Home Automation Profile specification.

Add On/Off Switch device

To create an On/Off Switch device, we are going to declare the following clusters:

  • Identify

  • Basic

  • On/Off Switch Configuration

  • On/Off

  • Scenes

  • Groups

We are going to create a cluster list that specifies an On/Off Switch device using the ZB_HA_DECLARE_ON_OFF_SWITCH_CLUSTER_LIST macro, which declares a static array of clusters. Before we declare the cluster list, we are going to create clusters with attributes that can be manipulated, while the macro is going to seamlessly declare clusters that lacks attributes:

  • Identify and Basic - These clusters are already declared in the Zigbee template sample.

  • On/Off Switch Configuration - This cluster we are going to create manually.

  • On/Off, Scenes, and Groups - These clusters are going to be declared by the macro.

Complete the following steps:

  1. Open the main.c file of the copied template sample. You can find this file in the directory to which you copied the sample.

  2. Define variables for the On/Off Switch Configuration cluster’s attributes and declare attribute list for them by embedding these attributes into the zb_device_ctx structure:

    struct zb_device_ctx {
            zb_zcl_basic_attrs_t     basic_attr;
            zb_zcl_identify_attrs_t  identify_attr;
            zb_uint8_t               on_off_switch_type_attr;
            zb_uint8_t               on_off_switch_actions_attr;
    };
    
    ZB_ZCL_DECLARE_ON_OFF_SWITCH_CONFIGURATION_ATTRIB_LIST(
            on_off_switch_attr_list,
            &dev_ctx.on_off_switch_type_attr, &dev_ctx.on_off_switch_actions_attr);
    

    At this point, you have all the clusters required to declare the On/Off Switch cluster list.

  3. Create the On/Off Switch cluster list using the Basic and Identify clusters from the Zigbee template sample and the On/Off Switch Configuration cluster you’ve just created, as shown in the following snippet:

    ZB_HA_DECLARE_ON_OFF_SWITCH_CLUSTER_LIST(on_off_switch_clusters,
            on_off_switch_attr_list, basic_attr_list, identify_attr_list);
    

    You can read more about this step in the Declaring attributes section of the ZBOSS stack documentation.

  4. Choose and declare the endpoint for the On/Off Switch device. For example:

    // Exemplary 11. endpoint will be used for On/Off Switch cluster
    #define ON_OFF_SWITCH_ENDPOINT          11
    
    ZB_HA_DECLARE_ON_OFF_SWITCH_EP(on_off_switch_ep, ON_OFF_SWITCH_ENDPOINT, on_off_switch_clusters);
    

    Every cluster in the On/Off cluster list that you’ve declared in the previous step is going to use the same endpoint.

    Note

    Every endpoint has an associated Simple Descriptor, which contains a variety of information, such as the application profile identifier, the number of input and output clusters, or the device version. Simple Descriptors are used to find and identify specific devices in the Zigbee network, for example to bind a light switch with a light bulb. Declaring an endpoint for a device (in this case, the On/Off Switch device) actually defines a Simple Descriptor for the endpoint. You can read more about Simple Descriptors in the official Zigbee Cluster Library specification from Connectivity Standards Alliance.

    You can read more about this step in the Declaring endpoint and Declaring simple descriptors sections of the ZBOSS stack documentation.

  5. Create an application context with all declared endpoints, given that the Zigbee template sample declares the device context for a single endpoint. Modify this declaration, so that the device can have another endpoint for the On/Off device:

    ZBOSS_DECLARE_DEVICE_CTX_2_EP(app_template_ctx, on_off_switch_ep, app_template_ep);
    

    You can read more about this step in the Declaring Zigbee device context and Declaring Zigbee device context with multiple endpoints sections of the ZBOSS stack documentation.

  6. Make sure that the device context is registered in the main function, within the ZB_AF_REGISTER_DEVICE_CTX macro:

    ZB_AF_REGISTER_DEVICE_CTX(&app_template_ctx);
    

    This creates a link between the application device context and the internal ZBOSS structures. You can read more about this step in the Registering device context section of the ZBOSS stack documentation.

Add Temperature Sensor device

The process of adding Temperature Sensor device is similar to adding the On/Off switch device.

To create a Temperature Sensor device, we are going to declare the following clusters:

  • Identify and Basic - These clusters are already declared in the Zigbee template sample.

  • Temperature Measurement - This cluster we are going to create manually.

This time, we are going to use the ZB_HA_DECLARE_TEMPERATURE_SENSOR_CLUSTER_LIST macro.

Complete the following steps:

  1. In the main.c file of the copied template sample, extend the zb_device_ctx structure with the Temperature Measurement attributes and declare the attributes list.

    In case of Temperature Measurement cluster, variables needed to hold its attributes are declared in the zb_zcl_temp_measurement_attrs_t structure, which is defined in addons/zcl/zb_zcl_temp_measurement_addons.h. Some of clusters have its attributes combined into helper structures in addons/zcl/zb_zcl_*_addons.h. The following snippet shows how to include the header and add a new field in zb_device_ctx, and then declare the attribute list:

    #include <addons/zcl/zb_zcl_temp_measurement_addons.h>
    
    
    struct zb_device_ctx {
            zb_zcl_basic_attrs_t            basic_attr;
            zb_zcl_identify_attrs_t         identify_attr;
            zb_uint8_t                      on_off_switch_type_attr;
            zb_uint8_t                      on_off_switch_actions_attr;
            zb_zcl_temp_measurement_attrs_t temp_measure_attrs;
    };
    
    
    ZB_ZCL_DECLARE_TEMP_MEASUREMENT_ATTRIB_LIST(temp_measurement_attr_list,
                                                &dev_ctx.temp_measure_attrs.measure_value,
                                                &dev_ctx.temp_measure_attrs.min_measure_value,
                                                &dev_ctx.temp_measure_attrs.max_measure_value,
                                                &dev_ctx.temp_measure_attrs.tolerance);
    
  2. Create a Temperature Sensor device by declaring its cluster list using Basic, Identify, and the newly created Temperature Measurement clusters:

    ZB_HA_DECLARE_TEMPERATURE_SENSOR_CLUSTER_LIST(temperature_sensor_clusters, basic_attr_list, identify_attr_list, temp_measurement_attr_list);
    
  3. Choose and declare the endpoint for the Temperature Sensor device:

    #define TEMPERATURE_SENSOR_ENDPOINT  12
    
    ZB_HA_DECLARE_TEMPERATURE_SENSOR_EP(temperature_sensor_ep, TEMPERATURE_SENSOR_ENDPOINT, temperature_sensor_clusters);
    
  4. Declare the device context for the created endpoint by modifying the device context declaration, so that the device can have another endpoint:

    ZBOSS_DECLARE_DEVICE_CTX_3_EP(app_template_ctx, temperature_sensor_ep, on_off_switch_ep, app_template_ep);
    

At this point, we have added On/Off Switch and Temperature Sensor functionalities to the application.

Verify cluster changes

To verify the existence of the On/Off Switch and Temperature Sensor clusters in the device, we are going to send ZDO commands to read Simple Descriptors. For this purpose, we are going to use the Zigbee network coordinator to create a simple Zigbee network with the node that is programmed with the extended application.

Prepare network coordinator for testing

To prepare the network coordinator for testing the newly extended application based on the Zigbee template, complete the following steps:

  1. Make sure you meet the requirements to use the Zigbee network coordinator sample.

  2. Enable the Zigbee shell in the network coordinator sample by adding the following line to network_coordinator/prj.conf file:

    CONFIG_ZIGBEE_SHELL=y
    
  3. Build the sample and program it to the development kit.

  4. Open the serial port and issue the help command. The following output appears:

    help
    Please press the <Tab> button to see all available commands.
    You can also use the <Tab> button to prompt or auto-complete all commands or its subcommands.
    You can try to call commands with <-h> or <--help> parameter for more information.
    
    Shell supports following meta-keys:
    Ctrl + (a key from: abcdefklnpuw)
    Alt  + (a key from: bf)
    Please refer to shell documentation for more details.
    
    Available commands:
    bdb                :Base device behaviour manipulation.
    clear              :Clear screen.
    debug              :Return state of debug mode.
    device             :Device commands
    devmem             :Read/write physical memory"devmem address [width [value]]"
    flash              :Flash shell commands
    help               :Prints the help message.
    history            :Command history.
    kernel             :Kernel commands
    nbr                :Zigbee neighbor table.
    nrf_clock_control  :Clock control commmands
    nvram              :Zigbee NVRAM manipulation.
    resize             :Console gets terminal screen size or assumes default in
                        case the readout fails. It must be executed after each
                        terminal width change to ensure correct text display.
    sensor             :Sensor commands
    shell              :Useful, not Unix-like shell commands.
    version            :Print firmware version
    zcl                :ZCL subsystem commands.
    zdo                :ZDO manipulation.
    zscheduler         :Zigbee scheduler manipulation.
    

This output means that the Zigbee shell is enabled on the network coordinator node device. You can read more about the Zigbee shell on its documentation page.

Run the extended application

We are now going to add the extended application node device to the Zigbee network. Complete the following steps:

  1. Make sure that the network coordinator node device is running.

  2. Build the extended application and program it to a compatible development kit, that is one of the development kits compatible with the template sample.

  3. Connect the kit to the computer using a USB cable. The kit is assigned a COM port (Windows) or ttyACM device (Linux), which is visible in the Device Manager.

  4. Connect to the kit with a terminal emulator (for example, PuTTY). See How to connect with PuTTY for the required settings.

  5. Observe the output. The device connects to the Zigbee network when a notification similar to the following one appears:

    I: Joined network successfully (Extended PAN ID: f4ce36e005691785, PAN ID: 0xf7a7)
    

    The Extended PAN ID and the PAN ID in your notification will be different.

Verify cluster operation

Reading Simple Descriptors that we have implemented when adding new clusters can help us verify that everything is working as it should. You can read the descriptors in different ways, but for the purpose of this guide we are going to use Zigbee Device Object commands (ZDO commands) issued from the network coordinator node with the Zigbee shell enabled. ZDO is an interface for accessing lower layers of the Zigbee network. Issuing ZDO commands allows us to check different information about the network.

For this guide, you can use one of the following options:

  • Issue ZDO commands to find the specific cluster in the Zigbee network, if you don’t know the device’s short address.

  • Issue ZDO commands to read the list of clusters contained on a device, if you know the device’s short address.

Both options are described in the following sections.

Looking for a specific cluster in the Zigbee network

You can send a match descriptor request when you want to find a specific cluster in the network. This request is a broadcast command that expects profile and cluster IDs as return values. When using the network coordinator node with the Zigbee shell enabled, you can send the ZDO match descriptor request in the following way:

  1. Make sure that the network coordinator is still connected with the serial port.

  2. Issue the zdo help command to print the information about the zdo commands, including the match descriptor command. The following output appears:

    [...]
    
    match_desc - Send match descriptor request.
            Usage: match_desc <h:16-bit destination_address> <h:requested
            address/type> <h:profile ID> <d:number of input clusters> [<h:input
            cluster IDs> ...] <d:number of output clusters> [<h:output cluster
            IDs> ...] [-t | --timeout d:number of seconds to wait for answers]
    
    [...]
    
  3. Send the zdo match_desc command to find a device with the On/Off cluster (ID: 0x0006) from the Home Automation profile (ID: 0x0104) using the following command:

    zdo match_desc 0xfffd 0xfffd 0x0104 0 1 0x06
    

    The following output appears:

    Sending broadcast request.
    
    src_addr=8083 ep=11
    

Reading a clusters list of a specific Zigbee node

To read the cluster list existing on a Zigbee node, you can use the Simple Descriptor Request command. This command requires the short address and the endpoint as arguments.

Complete the following steps to read the cluster list of a Zigbee node:

  1. Send the match descriptor request to learn the short address of device, as described in Looking for a specific cluster in the Zigbee network:

    zdo match_desc 0xfffd 0xfffd 0x0104 0 1 0x06
    

    The following output appears, where 8083 is the short address of the Zigbee node and 11 is the endpoint number of the On/Off switch device:

    Sending broadcast request.
    
    src_addr=8083 ep=11
    

    Alternatively, you can check the short address by looking at the network coordinator logs showed during device association or by sniffing the communication and reading packets in Wireshark (see Configuring Wireshark for Zigbee).

  2. Send the zdo simple_desc_req command to the Zigbee node using the short address and the endpoint number:

    zdo simple_desc_req 0x8083 11
    

    The output similar to the following one appears:

    src_addr=0x8083 ep=11 profile_id=0x0104 app_dev_id=0x0 app_dev_ver=0x0
    in_clusters=0x0000,0x0003,0x0007 out_clusters=0x0006,0x0005,0x0004,0x0003
    

    In this notification, the simple descriptor contains Basic Cluster, Identify Cluster and On/Off Switch Configuration with server roles (in_clusters) and Identify, Groups, Scenes, and On/Off configured with client roles (out_clusters).

Further expanding the custom application

You can further expand the application with more features, such as OTA support.

Adding OTA

To extend the sample with OTA support, we would have to complete steps similar to adding On/Off Switch and Temperature Sensor functionalities. Then, we would have to implement the ZCL device callback to control the process of collecting chunks of new firmware. This is described more broadly in the following sections.

Fortunately, we can use the Zigbee FOTA library to handle the majority of these implementation steps. To add OTA support to the extended application, follow the steps in Configuring Zigbee FOTA.

Passing ZCL events to the application

Declaring and registering a set of clusters that defines a Zigbee node makes these clusters discoverable across the Zigbee network and ready to communicate with another nodes. For example, the communication between a light switch node and a light bulb node uses ZCL commands to change the attributes of the On/Off device embedded in the light bulb. Altering the attribute does nothing more than changing values of a specific variable. The application is supposed to react to these changes and produce appropriate behavior, but we need to inform it about these changes first.

To inform the application about attributes changes, you can pass ZCL events to it with a callback that follows generic callback definition (referred to as ZCL callback). This is shown in the following snippet:

typedef void (zb_callback_t)(zb_uint8_t param);

The param argument passed to the callback contains information about the changed attributes. This argument is actually a ZBOSS buffer that contains the zb_zcl_device_callback_param_t structure whose definition fragment is as follows:

/* For the full definition please refer to zboss_api_zcl.h */
typedef struct zb_zcl_device_callback_param_s
{
        /** Type of device callback */
        zb_zcl_device_callback_id_t device_cb_id;
        zb_uint8_t endpoint;
        zb_zcl_attr_access_t attr_type;

        /** Return status (see zb_ret_t) */
        zb_ret_t status;

        /** Callback custom data */
        union
        {
                zb_zcl_set_attr_value_param_t  set_attr_value_param;
                #if defined (ZB_ZCL_SUPPORT_CLUSTER_ON_OFF)
                /* Off with effect command, On/Off cluster */
                zb_zcl_on_off_set_effect_value_param_t  on_off_set_effect_value_param;
                /* */
                #endif
                #if defined(ZB_ZCL_SUPPORT_CLUSTER_IDENTIFY)
                zb_zcl_identify_effect_value_param_t  identify_effect_value_param;
                #endif

                /* .
                   .
                   .
                */

                zb_zcl_device_cmd_generic_param_t gnr;
        } cb_param;
} zb_zcl_device_callback_param_t;

Both the On/Off Switch device and the Temperature Sensor device have client roles. For this reason, their attributes are not supposed to change. To show how you can use the ZCL event, read the following section.

Reading device callback parameters from a ZBOSS buffer

The ZCL callback implemented in the Zigbee FOTA library is a good example of how to use the ZCL event.

To read the device callback parameters from a ZBOSS buffer, complete the following steps:

  1. In the main.c file of the copied template sample, use the zb_zcl_device_callback_param_t structure to get the ZCL callback parameters from the buffer in the following manner:

    zb_zcl_device_callback_param_t *device_cb_param = ZB_BUF_GET_PARAM(bufid, zb_zcl_device_callback_param_t);
    

    Once the parameters are obtained, the application can use them to perform some action based on a new attribute values.

  2. Make the application check the callback ID by reading the appropriate field from the zb_zcl_device_callback_id_t structure. For example, the Zigbee FOTA library uses the ZB_ZCL_OTA_UPGRADE_VALUE_CB_ID macro:

    if (device_cb_param->device_cb_id != ZB_ZCL_OTA_UPGRADE_VALUE_CB_ID) {
              return;
    }
    

    Depending on the device callback ID, different data is passed to the callback and held by the cb_param field of zb_zcl_device_callback_param_t. In general, the data associated with the callback ID is contained in the set_attr_value_param field of cb_param, but some clusters have their data structure already defined. For example, OTA uses ota_value_param fields, as shown in the following snippet:

    zb_zcl_ota_upgrade_value_param_t *ota_upgrade_value = &(device_cb_param->cb_param.ota_value_param);
    

    To see the field usage associated with other clusters, refer to the Zigbee: Light bulb sample.

  3. Make the ZCL callback pass the status of its execution to the caller by setting the appropriate return status in the status field of the zb_zcl_device_callback_param_t structure passed to the callback:

    device_cb_param->status = RET_OK;