nRF5 SDK for Thread and Zigbee v1.0.0
Application development

Table of Contents

ZBOSS types

The framework includes a set of platform-independent type definitions such as common C types (char, integer, void, etc.) and specific types for core and Zigbee functionality. Common C types that are recommended to be used in the application development are defined in zb_types.h. Specific types describe core ZBOSS and Zigbee primitives. All ZBOSS type names start with the prefix "zb_".

Typical application structure

Definitions used in the Zigbee ZCL specification:

A Zigbee application implements a node that can handle at least one endpoint, which implements a set of clusters. The clusters implement the device functionality using attributes to store a state and a set of commands to operate. A set of predefined Zigbee devices is included (see Implemented features for more information). Using the provided API, a custom device with a cluster set may be implemented. The Zigbee specification is designed to minimize the user application's RAM and flash consumption. An application uses the API to declare a Zigbee device and to construct and parse ZCL commands. Attributes and clusters should be declared as needed in order to save memory for other applications. The user application has the following structure:

A device declaration consists of the following:

Device context aggregates an endpoint list and all other specific data for a Zigbee device.

On startup, the application initializes the stack and registers a callback function to handle Zigbee events. The initialization section runs prior to the main application and includes the following actions:

After the application and initialization are complete, the application should call zboss_main_loop_iteration() inside the main loop. The following snippet shows the main loop of the application:

/** Start Zigbee Stack. */
zb_err_code = zboss_start();
ZB_ERROR_CHECK(zb_err_code);
while(1)
{
}

After the scheduler main loop is started, the user application functions may be processed in the context of the scheduler: either zboss_signal_handler() or another callback is called (see Zigbee event handling). For handling incoming ZCL packets, the application registers a specific callback using a call to ZB_AF_SET_ENDPOINT_HANDLER(endpoint, handler).

The handler function should comply to zb_device_handler_t type:

typedef zb_uint8_t (*zb_device_handler_t)(zb_uint8_t param);

The parameter zb_uint8_t param is a reference to the buffer containing the received ZCL command. More details on ZCL commands processing are in Processing ZCL commands.

Stack initialization

It is recommended to initialize the stack on application start. The following steps describe how to initialize the stack:

  1. Call ZB_INIT() to restore the stack and application data stored in NVRAM, or initiate the stack with default values.
  2. Define the network role and configure stack parameters (64-bit long address, security key, maximum children number, etc.).
  3. Call zboss_main_loop_iteration() inside the application main loop.

Network configuration

The stack implements all Zigbee roles: coordinator, router, and end device. Only one role may be defined in a Zigbee application. Zigbee role switch in runtime is not supported. Different libraries are provided for Coordinator/Router and End Device roles, depending on the defined Zigbee role. The application should be linked with different libraries:

Note: For more information on debugging the Zigbee stack see Debugging.

Functions used to set the device role are described in the table below.

FUNCTION PARAMETERS RETURN VALUE DESCRIPTION
zb_set_network_coordinator_role(channel_mask) zb_uint32_t channel_mask - mask used to define the primary channel set None Initiate device as a Zigbee coordinator
zb_set_network_router_role(channel_mask) zb_uint32_t channel_mask - mask used to define the primary channel set None Initiate device as a Zigbee router
zb_set_network_ed_role(channel_mask) zb_uint32_t channel_mask - mask used to define the primary channel set None Initiate device as a Zigbee End Device

Parameter channel_mask is a bit field that defines Zigbee channels to be used for an energy scan as the primary channel set. Bits from 11 to 26 are significant. Each bit enables/disables the corresponding channel. To set all channel masks (enable all channels), use macro ZB_TRANSCEIVER_ALL_CHANNELS_MASK. By default, the secondary channel mask is the same, but it is possible to define it separately using the zb_set_bdb_secondary_channel_set() call. The complete network configuration API description is in the table below.

FUNCTION PARAMETERS RETURN VALUE DESCRIPTION
zb_set_bdb_primary_channel_set(channel_mask) zb_uint32_t channel_mask None Set the primary channel set for the BDB energy scan. Beacon requests are sent on these channels.
zb_get_bdb_primary_channel_set() None zb_uint32_t channel_mask Get the primary channel set.
zb_set_bdb_secondary_channel_set(channel_mask) zb_uint32_t channel_mask None Set the secondary channel set for the BDB energy scan. Beacon requests are sent on these channels if no network is found after the energy scan on the primary channel mask.
zb_get_bdb_secondary_channel_set() None zb_uint32_t channel_mask Get the secondary channel set.
zb_set_bdb_commissioning_mode(comm_mode) zb_uint8_t comm_mode None Select commissioning mode. Set 1 to the corresponding bit to enable, 0 to disable:
0: Touchlink commissioning
1: Network steering
2: Network formation
3: Finding & binding
zb_set_rx_on_when_idle(rx_on) zb_bool_t rx_on None Set TRUE if the end device should turn off the radio between transmission attempts (sleeping mode).
Note: Use only for an end device.
zboss_start() None return RET_OK on success. Start function. Typical device start: init and proceed with startup.
zboss_main_loop_iteration() None None Single iteration of the Zigbee stack main loop.
Must be called in an infinite loop after ZB_INIT() and zboss_start().
zb_set_long_address(addr) zb_ieee_addr_t* addr - long address structure None Set 64-bit long address specified with addr parameter.
zb_get_long_address(addr) zb_ieee_addr_t* addr - long address structure where the result will be stored None Get the 64-bit long address.
zb_set_max_children(max_children) zb_uint8_t max_children - maximum number of connected devices. None Set the maximum number of connected devices. Used for coordinators and routers.

Zigbee device implementation

A Zigbee device is characterized by a set of supported clusters, which may vary depending on the needs. Each cluster defines a set of attributes, some of which are mandatory and some are optional. This section describes the entire process of a Zigbee device implementation in detail. At the highest level, it consists of the following actions:

Attribute declaration

As it is defined in ZCL, there are mandatory and optional attributes for each cluster. ZCL declares attribute lists according to the clusters' mandatory attribute sets. ZCL attributes are defined in a form that allows easy modification.

Example: the macro below defines a list of attributes for a Door lock cluster:

#define ZB_ZCL_DECLARE_DOOR_LOCK_CLUSTER_ATTRIB_LIST(attr_list, \
lock_state, \
lock_type, \
actuator_enabled) \
ZB_ZCL_START_DECLARE_ATTRIB_LIST(attr_list) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_DOOR_LOCK_LOCK_STATE_ID, (lock_state)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_DOOR_LOCK_LOCK_TYPE_ID, (lock_type)) \
ZB_ZCL_SET_ATTR_DESC(ZB_ZCL_ATTR_DOOR_LOCK_ACTUATOR_ENABLED_ID, (actuator_enabled)) \
ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST

The attribute list starts with ZB_ZCL_START_DECLARE_ATTRIB_LIST() and ends with ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST. Any number of additional attributes may be added in between these lines. According to a naming conversion, each attribute list declaration API has a name corresponding to the following template: ZB_ZCL_DECLARE_<CLUSTER_NAME>_CLUSTER_ATTRIB_LIST.

The attribute declaration for the Level Control cluster is shown below:

static zb_uint8_t m_current_level = ZB_ZCL_LEVEL_CONTROL_CURRENT_LEVEL_DEFAULT_VALUE;
static zb_uint16_t m_remaining_time = ZB_ZCL_LEVEL_CONTROL_REMAINING_TIME_DEFFAULT_VALUE_ZLL;
ZB_ZCL_DECLARE_LEVEL_CONTROL_ATTRIB_LIST(level_control_attr_list,
&m_current_level,
&m_remaining_time);

The ZB_ZCL_DECLARE_LEVEL_CONTROL_ATTRIB_LIST macro is defined in the zb_zcl_level_control.h file. This cluster supports two attributes named current_level and remaining_time.

Cluster declaration

The SDK provides more than 30 implemented Zigbee clusters that are ready for the user application. To use a cluster, an application must declare it as part of a Zigbee device being implemented. A cluster description is associated with a Zigbee device and includes the following data:

Cluster description data is stored using zb_zcl_cluster_desc_t type. The SDK provides a number of APIs to fill in cluster descriptors while creating a cluster list declaration.

Cluster list declaration

A Zigbee device's functionality is defined by a set of supported clusters. The cluster list is an array of zb_zcl_cluster_desc_t type. For standard Zigbee devices, a set of APIs for cluster lists declaration is provided. This cluster list declaration API is provided in a source code so it may be modified by adding or removing clusters if needed. According to a naming conversion, each cluster list declaration API has a name corresponding to a template below:

Example of a list declaration for a dimmable light (a standard Zigbee device)

ZB_HA_DECLARE_DIMMABLE_LIGHT_CLUSTER_LIST(cluster_list_name, basic_attr_list, identify_attr_list, groups_attr_list, scenes_attr_list, on_off_attr_list, level_control_attr_list)

Note: ZB_HA_DECLARE_DIMMABLE_LIGHT_CLUSTER_LIST() is defined in zb_ha_dimmable_light.h file. As soon as the dimmable light device utilizes Basic, Identify, Groups, Scenes, On/Off, and Level Control clusters in a server role, it takes attribute lists for all of them.

Endpoint declaration

An endpoint (or a set of endpoints) fully describes a Zigbee device. Thus, endpoint declaration in a user application finalizes the logical description of a Zigbee device. A Zigbee device must have at least one endpoint, with the option to add more endpoints. A list of endpoints is declared each time for a Zigbee device, even if it only implements a single endpoint. Each endpoint description includes the following data:

Endpoint description data is stored using zb_af_endpoint_desc_t type. The SDK provides a number of APIs to fill in endpoint descriptors while performing a Zigbee device declaration for each supported Zigbee device. Additionally, the API declares a simple descriptor and a scene table if needed. The endpoint list declaration API is provided in the source code and may be modified depending on application needs. According to the naming conversion, each endpoint list declaration API has a name corresponding to the template below:

Example of an endpoint declaration for a dimmable light (a standard Zigbee device) is as follows:

ZB_HA_DECLARE_DIMMABLE_LIGHT_EP(ep_name, ep_id, cluster_list)

Note: A single endpoint is defined for the dimmable light device, the same as for other standard Zigbee devices.

If the application needs to declare a device with multiple endpoints, the following API calls should be used to declare the endpoint list:

The endpoint list is stored as an array of zb_af_endpoint_desc_t type. Each call to ZB_AF_SET_ENDPOINT_DESC() adds an item to the ep_list_name array.

Simple descriptor declaration

The simple descriptor contains information specific to each endpoint contained in a node. The simple descriptor is mandatory for each endpoint present in the node. The API declares the simple descriptor for each standard Zigbee device and embeds these calls into endpoint declaration APIs. However, if modifications to the standard device are requested, modifying the simple descriptor declaration is allowed since it is provided in the source code. According to the naming conversion, each simple descriptor declaration API has a name corresponding to the template below:

Technically, the simple descriptor is a variable of ZB_AF_SIMPLE_DESC_TYPE(in_clust_count, out_clust_count) type, a pointer to this variable is stored in the endpoint descriptor. This macro is used to create a full type name at compilation time, because due to its nature, the simple descriptor contains a list of in and out (client/server) clusters and the list size varies for different devices. In order to minimize the RAM usage, the specified values of in/out clusters are used to declare an array of a needed size to store all clusters' IDs. There is a base type declared for the simple descriptor: ZB_AF_SIMPLE_DESC_TYPE(1, 1). This type is used to cast all the user-specific simple descriptor types at run time. The API for managing simple descriptor types is:

Zigbee device context declaration

The Zigbee device context aggregates the list of endpoints and runtime data such as: storage for reporting configuration information and level control context information. The application is responsible for providing enough storage for reporting and level control context data for all the endpoints it declares. For standard Zigbee devices, it is the API that declares device context together with storage for all the context data. According to the naming conversion, each device declaration API has a name corresponding to the template below:

To modify the existing device context declaration or define a new one, the API described below is used:

Note: Reporting count reflects a number of reportable attributes for all the endpoints. Level control count reflects a number of attributes that require continuous value change (like a CurrentLevel attribute in the Level control cluster). For example, if a device supports two endpoints and there are three reportable attributes on the first endpoint and one reportable attribute on the second, reporting count should be equal to 4.

Run a Zigbee device

When the declaration procedures are finished, the device context must be registered using a call to ZB_AF_REGISTER_DEVICE_CTX(device_ctx). With this call, two ZCL callbacks may be registered in the application:

The ZCL packet handler allows application specific handling of incoming ZCL messages; it is described in more detail in the Processing ZCL commands section of this guide. The Zigbee device callback is a multipurpose callback used as a common entry point to a user specific handler implementation for different cases. It covers attributes changes, device identifying, poll control specific actions, OTA firmware upgrade, and others. The Zigbee device callback section describes it in more detail.

Processing ZCL commands

The stack implements default handling of all the ZCL commands for all supported clusters. Together with the stack, there is a mechanism that allows implementing an application specific command handling. To do this, the ZCL packet handler callback should be implemented and registered by the application. When the ZCL packet is registered, it is called for each new ZCL command received prior to the default ZCL handler being called. If the packet is handled by the application and no default processing is needed, the packet handler should return ZB_TRUE (the packet is processed). If the ZCL packet handler returns ZB_FALSE (the packet is not processed), the default ZCL handling will be finished.

Note: If the application started the packet parsing and caused packet modification (some header or tail bytes were cut or byte order was inverted), it should finalize the packet processing. Otherwise, the default handler may fail. If the application handles the received packet, it is responsible for sending a response, if needed, and managing the memory buffer that stores the original ZCL packet. The most common commands processed by the application are responses to the sent requests such as Read attribute response, Write attribute response, Configure reporting response, etc. An API is provided to handle the received packets, for example: ZB_ZCL_GENERAL_GET_NEXT_READ_ATTR_RES(), ZB_ZCL_GET_NEXT_WRITE_ATTR_RES(), ZB_ZCL_GENERAL_GET_NEXT_CONFIGURE_REPORTING_RES(), and others. Each packet parsing API call may modify the ZCL packet body by changing byte order for 2 or 4 byte fields or cutting parsed data. A parsing API may return a pointer to a data structure "in place", i.e., the data block is stored in the received ZCL packet, another API fills in a provided buffer with parsed data. To get more details on the parsing API, refer to the detailed ZCL API description in API document.

Function Parameters Return value Description
ZB_AF_SET_ENDPOINT_HANDLER(endpoint, handler) Endpoint - Endpoint number
Handler - Pointer to a function of zb_device_handler_t type.
None Register ZCL packet handler for a specific endpoint.
zb_uint8_t my_zcl_pkt_handler(zb_uint8_t param) zb_uint8_t param - reference to the buffer with the incoming ZCL command. ZB_TRUE - if the ZCL command was processed by the application.
ZB_FALSE - if the ZCL command was not processed by the user application.
ZCL packet processing callback.

Note: The device context should be registered prior to calling ZB_AF_SET_ENDPOINT_HANDLER(); refer to Run a Zigbee device section of this guide for more details.

To process a received ZCL command, the algorithm described below is implemented in the ZCL packet handler callback:

  1. Extract incoming ZCL command information from the incoming buffer:
    zb_buf_t *zcl_cmd_buf = (zb_buf_t *)ZB_BUF_FROM_REF(param);
  1. Check command direction (cmd_direction field of the zb_zcl_parsed_hdr_t structure). Direction can be "towards a server" (ZB_ZCL_FRAME_DIRECTION_TO_SRV) or "towards a client" (ZB_ZCL_FRAME_DIRECTION_TO_CLI).
  2. Check if the command is general or cluster specific (is_common_command field of the zb_zcl_parsed_hdr_t structure is set to 1 for a general command frame).
  3. Check the designated cluster ID (cmd_info -> cluster_id field of the zb_zcl_parsed_hdr_t structure).
  4. Check the command ID (cmd_info -> cmd_id field of the zb_zcl_parsed_hdr_t structure). The general ZCL commands identifiers are listed in the zb_zcl_cmd_t enumeration. Cluster specific command identifiers are defined in the corresponding cluster headers.
  5. If the incoming command was processed by the application, then the buffer must be released (if it was not reused) and the callback must return ZB_TRUE. Otherwise, return ZB_FALSE to perform the default command processing.

Zigbee device callback

Zigbee device callback is an optional multipurpose callback used as a common entry point to a user specific handler implementation for different cases. A call to ZB_ZCL_REGISTER_DEVICE_CB() may register a callback function that takes a reference to a buffer as an input parameter by the application. This buffer carries parameter zb_zcl_device_callback_id_t (see Zigbee stack memory management subsystem for details on how to get the parameter from a buffer). To check what action has triggered the callback, refer to the device_cb_id field of the received parameter. All the cases are listed in the zb_zcl_device_callback_id_t enumeration. It is not recommended to perform any blocking operations during the Zigbee device callback implementation . The memory buffer passed as a parameter to this callback should not be released or reused by the application because it is being managed by the ZCL subsystem.

ZCL commands sending

An API is provided for sending and parsing ZCL packets for general command frames and cluster specific commands. Together with this API, there is a low level API that helps to compose and parse new commands.

The naming convention for the ZCL API is not strict. Usually it has a cluster name as part of the name (for general commands, the word GENERAL is used instead of the cluster name), the command name itself, and a verb describing an action. For example, ZB_ZCL_ON_OFF_SEND_TOGGLE_REQ() sends a Toggle command for On/Off cluster; ZB_ZCL_IAS_ZONE_SEND_STATUS_CHANGE_NOTIFICATION_REQ() sends a zone notification for IAS zone cluster. API calls ZB_ZCL_GENERAL_INIT_READ_ATTR_REQ(), ZB_ZCL_GENERAL_ADD_ID_READ_ATTR_REQ(), ZB_ZCL_GENERAL_SEND_READ_ATTR_REQ() are used to initialize, compose, and send a Read Attribute general command.

If a ZCL command has a predefined size like the Toggle command, it is composed and sent with one API call. If a command has a variable size like the Read attribute, it is composed dynamically at run time and an application may call ZB_ZCL_GENERAL_ADD_ID_READ_ATTR_REQ() several times to add more attribute IDs to read.

To construct a manufacturer specific command (or a standard command that is not supported by the API yet), a low-level API is provided:

ZCL command parsing

Similar to ZCL commands sending, an API for parsing received ZCL packets for general command frames and cluster specific commands is provided.

The naming convention for the API is similar to the commands sending API. Usually it has a cluster name or "GENERAL" as part of the name, the command name itself, and a verb describing an action. For example, ZB_ZCL_IAS_ZONE_GET_STATUS_CHANGE_NOTIFICATION_REQ() parses received IAS Zone notification and fills in a variable of zb_zcl_ias_zone_status_change_not_t type. The API to parse the Read attribute response is ZB_ZCL_GENERAL_GET_NEXT_READ_ATTR_RES(). For trivial commands without a payload (like Toggle command in On/Off cluster), the parsing API is not provided. If a received packet is parsed manually, ZB_ZCL_FIX_ENDIAN() API is helpful to keep the right endianness in the packet.


Documentation feedback | Developer Zone | Subscribe | Updated