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:
One compatible development kit for programming the Zigbee template sample
One compatible development kit for programming the Zigbee network coordinator sample
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:
Make sure that you meet the requirements for building the sample.
Build and test the sample as described on its documentation page.
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:
Open the
main.c
file of the copied template sample. You can find this file in the directory to which you copied the sample.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.
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.
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.
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.
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:
In the
main.c
file of the copied template sample, extend thezb_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 inaddons/zcl/zb_zcl_temp_measurement_addons.h
. Some of clusters have its attributes combined into helper structures inaddons/zcl/zb_zcl_*_addons.h
. The following snippet shows how to include the header and add a new field inzb_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);
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);
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);
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:
Make sure you meet the requirements to use the Zigbee network coordinator sample.
Enable the Zigbee shell in the network coordinator sample by adding the following line to
network_coordinator/prj.conf
file:CONFIG_ZIGBEE_SHELL=y
Build the sample and program it to the development kit.
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:
Make sure that the network coordinator node device is running.
Build the extended application and program it to a compatible development kit, that is one of the development kits compatible with the template sample.
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.
Connect to the kit with a terminal emulator (for example, PuTTY). See How to connect with PuTTY for the required settings.
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:
Make sure that the network coordinator is still connected with the serial port.
Issue the
zdo help
command to print the information about thezdo
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] [...]
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:
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 and11
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).
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:
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.
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 theset_attr_value_param
field ofcb_param
, but some clusters have their data structure already defined. For example, OTA usesota_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.
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;