Zigbee application utilities

Zigbee application utilities library provides a set of components that are ready for use in Zigbee applications:

  • Zigbee default signal handler for handling common ZBOSS stack signals.

  • API for parsing and converting Zigbee data types. Available functions are listed in include/zigbee/zigbee_app_utils.h.

  • zigbee_led_status_update() for indicating the status of the device in a network using LEDs.

Zigbee default signal handler

The ZBOSS Zigbee stack interacts with the user application by invoking the zboss_signal_handler() function whenever a stack event, such as network steering, occurs. It is mandatory to define zboss_signal_handler() in the application.

Because most of Zigbee devices behave in a similar way, zigbee_default_signal_handler() was introduced to provide a default logic to handle stack signals.

Note

This is a Zigbee library. See Configuring Zigbee libraries in nRF Connect SDK for information about how to configure it for you Zigbee application.

Minimal zboss_signal_handler implementation

This function can be called in the application’s zboss_signal_handler() to simplify the implementation. In such case, this minimal implementation includes only a call to the default signal handler:

void zboss_signal_handler(zb_uint8_t param)
{
    /* Call default signal handler. */
    zigbee_default_signal_handler(param);

    /* Free the Zigbee stack buffer. */
    zb_buf_free(param);
}

With this call, the device will be able to join the Zigbee network or join a new network when it leaves the previous one.

In general, using the default signal handler is worth considering because of the following reasons:

  • It simplifies the application.

  • It provides a default behavior for each signal, which reduces the risk that an application will break due to an unimplemented signal handler.

  • It provides Zigbee role specific behavior (for example, finite join/rejoin time)

  • It makes the application less sensitive to changes in the Zigbee stack commissioning API.

The default signal handler also serves as a good starting point for a custom signal handler implementation.

Complete zboss_signal_handler implementation

In its complete implementation, the zboss_signal_handler allows the application to control a broader set of basic functionalities, including joining, commissioning, and network formation.

There are cases in which the default handler will not be sufficient and needs to be extended. For example, when the application wants to use the procedure of the initiator of finding & binding or use the production configuration feature.

Extending zboss_signal_handler

If you want to extend zboss_signal_handler to cover additional functionalities, write signal handler implementation for the required ZBOSS signal. For example for a device that initiates a F&B procedure, extend zboss_signal_handler with a case for ZB_BDB_SIGNAL_FINDING_AND_BINDING_INITIATOR_FINISHED:

void zboss_signal_handler(zb_bufid_t bufid)
{
    zb_zdo_app_signal_hdr_t   *sg_p  = NULL;
    zb_zdo_app_signal_type_t  sig    = zb_get_app_signal(bufid, &sg_p);
    zb_ret_t                  status = ZB_GET_APP_SIGNAL_STATUS(bufid);
    zb_nwk_device_type_t      role   = zb_get_network_role();
    zb_bool_t                 comm_status;

    switch (sig) {
    case ZB_BDB_SIGNAL_FINDING_AND_BINDING_INITIATOR_FINISHED:
        /* Check status of signal. */
        if (status == RET_OK) {
            /* This signal is received with additional data. Read additional information to get status of F&B procedure. */
            zb_zdo_signal_fb_initiator_finished_params_t *fnb_params =
                ZB_ZDO_SIGNAL_GET_PARAMS(sg_p, zb_zdo_signal_fb_initiator_finished_params_t);

            switch (fnb_params->status) {
            case ZB_ZDO_FB_INITIATOR_STATUS_SUCCESS:
                /* F&B with a Target on the Initiator side is completed with a success. */
                break;

            case ZB_ZDO_FB_INITIATOR_STATUS_CANCEL:
                /* F&B on the Initiator side is canceled. */
                break;

            case ZB_ZDO_FB_INITIATOR_STATUS_ALARM:
                /* F&B on the Initiator side is finished by timeout. */
                break;

            case ZB_ZDO_FB_INITIATOR_STATUS_ERROR:
                /* F&B on the Initiator side finished with a failure. */
                break;

            default:
                /* Unrecognised status of F&B procedure. */
                break;
            }
        } else {
            /* F&B procedure failed. */
        }
        break;

    default:
        /* All other signals are forwarded to the zigbee default signal handler. */
        ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
        break;
    }

    if (bufid) {
        zb_buf_free(bufid);
    }
}

Custom commissioning behavior

For the application to use a custom commissioning behavior, the default rejoin_procedure should be overwritten by writing a custom signal handler implementation for the following signals:

  • ZB_BDB_SIGNAL_DEVICE_FIRST_START

  • ZB_BDB_SIGNAL_DEVICE_REBOOT

  • ZB_BDB_SIGNAL_STEERING

  • ZB_BDB_SIGNAL_FORMATION

  • ZB_ZDO_SIGNAL_LEAVE

Use the following code as reference:

void zboss_signal_handler(zb_bufid_t bufid)
{
    zb_zdo_app_signal_hdr_t   *sg_p  = NULL;
    zb_zdo_app_signal_type_t  sig    = zb_get_app_signal(bufid, &sg_p);
    zb_ret_t                  status = ZB_GET_APP_SIGNAL_STATUS(bufid);
    zb_nwk_device_type_t      role   = zb_get_network_role();
    zb_bool_t                 comm_status;

    switch (sig) {
    case ZB_BDB_SIGNAL_DEVICE_FIRST_START:
        if (status == RET_OK) {
            if (role != ZB_NWK_DEVICE_TYPE_COORDINATOR) {
                /* If device is Router or End Device, start network steering. */
                comm_status = bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING);
            } else {
                /* If device is Coordinator, start network formation. */
                comm_status = bdb_start_top_level_commissioning(ZB_BDB_NETWORK_FORMATION);
            }
        } else {
            /* Failed to initialize Zigbee stack. */
        }
        break;

    case ZB_BDB_SIGNAL_DEVICE_REBOOT:
        /* fall-through */
    case ZB_BDB_SIGNAL_STEERING:
        if (status == RET_OK) {
            /* Joined network successfully. */
            /* TODO: Start application-specific logic that requires the device to be connected to a Zigbee network. */
        } else {
            /* Unable to join the network. Restart network steering. */
            comm_status = bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING);
            ZB_COMM_STATUS_CHECK(comm_status);
        }
        break;

    case ZB_ZDO_SIGNAL_LEAVE:
        if (status == RET_OK) {
            /* Device has left the network. */
            /* TODO: Start application-specific logic or start network steering to join a new network. */

            /* This signal comes with additional data in which type of leave is stored. */
            zb_zdo_signal_leave_params_t *leave_params = ZB_ZDO_SIGNAL_GET_PARAMS(sg_p, zb_zdo_signal_leave_params_t);

            switch (leave_params->leave_type) {
            case ZB_NWK_LEAVE_TYPE_RESET:
                /* Device left network without rejoining. */
                break;

            case ZB_NWK_LEAVE_TYPE_REJOIN:
                /* Device left network with rejoin. */
                break;

            default:
                /* Unrecognised leave type. */
                break;
            }
        } else {
            /* Device was unable to leave network. */
        }
        break;

    case ZB_BDB_SIGNAL_FORMATION:
        if (status == RET_OK) {
            /* Network formed successfully, start network steering. */
            comm_status = bdb_start_top_level_commissioning(ZB_BDB_NETWORK_STEERING);
        } else {
            /* Network formation failed, restart. */
            ret_code = ZB_SCHEDULE_APP_ALARM((zb_callback_t)bdb_start_top_level_commissioning, ZB_BDB_NETWORK_FORMATION, ZB_TIME_ONE_SECOND);
        }
        break;

    default:
        /* Call default signal handler. */
        ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
        break;
    }

    if (bufid) {
        zb_buf_free(bufid);
    }
}

Behavior on stack start

When the stack is started through zigbee_enable(), the stack generates the following signals:

  • ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY – indicating that the stack attempted to load application-specific production configuration from flash memory.

  • ZB_ZDO_SIGNAL_SKIP_STARTUP – indicating that the stack has initialized all internal structures and the Zigbee scheduler has started.

The reception of these signals determines the behavior of the default signal handler:

  • Upon reception of ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY, the default signal handler will print out a log with the signal status and exit.

ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY signal handler

ZB_ZDO_SIGNAL_PRODUCTION_CONFIG_READY signal handler

  • Upon reception of ZB_ZDO_SIGNAL_SKIP_STARTUP signal, the default signal handler will perform the BDB initialization procedure, and then exit.

ZB_ZDO_SIGNAL_SKIP_STARTUP signal handler

ZB_ZDO_SIGNAL_SKIP_STARTUP signal handler

Note

If you want to perform some actions before the stack attempts to join or rejoin the Zigbee network, you can overwrite this behavior by providing a custom ZB_ZDO_SIGNAL_SKIP_STARTUP signal handler implementation.

Zigbee Base Device Behavior initialization

Once the BDB initialization procedure is finished, depending on the data stored inside the Zigbee persistent storage, the stack will complete one of the following scenarios:

  • New devices - Generate the ZB_BDB_SIGNAL_DEVICE_FIRST_START signal for factory new devices.

  • Commissioned devices - Perform a single attempt to rejoin the Zigbee network based on NVRAM contents and then generate the ZB_BDB_SIGNAL_DEVICE_REBOOT signal.

Both scenarios will cause different behavior of the the default signal handler.

New device scenario

For factory new devices, the default signal handler will:

  • Start the BDB network formation on coordinator devices. Once finished, the stack will generate ZB_BDB_SIGNAL_FORMATION signal, and continue to Zigbee network formation and commissioning.

  • Call start_network_rejoin() to start the Zigbee network rejoining on routers and end devices. Once the procedure is started, the device tries to join the network until cancellation. Each try takes place after a longer period of waiting time, for a total maximum of 15 minutes. Devices may behave differently because the implementation of start_network_rejoin() is different for different Zigbee roles. See Zigbee network rejoining for more information.

Once handling of the signal is finished, the stack will generate the ZB_BDB_SIGNAL_STEERING signal, and will continue to Zigbee network formation and commissioning.

Scenario for factory new devices (ZB_BDB_SIGNAL_DEVICE_FIRST_START)

Scenario for factory new devices (ZB_BDB_SIGNAL_DEVICE_FIRST_START)

Commissioned device scenario

For devices that have been already commissioned, the default handler will:

  • Not perform additional actions if the device implements a coordinator role.

    • This will keep the network closed for new Zigbee devices even if the coordinator is reset.

  • Not perform additional actions if the device successfully rejoins Zigbee network.

    • This will not open the network for new devices if one of existing devices is reset.

    • In case of the Zigbee network rejoining is running, it will be cancelled.

  • For routers and end devices, if they did not join the Zigbee network successfully, Zigbee network rejoining is started by calling start_network_rejoin().

Once finished, the stack will generate the ZB_BDB_SIGNAL_STEERING signal, and continue to Zigbee network formation and commissioning.

Scenario for already commissioned devices (ZB_BDB_SIGNAL_DEVICE_REBOOT)

Scenario for already commissioned devices (ZB_BDB_SIGNAL_DEVICE_REBOOT)

Zigbee network formation and commissioning

According to the logic implemented inside the default signal handler, the devices can either form a network or join an existing network:

  1. Coordinators will first form a network. Attempts to form the network will continue infinitely, with a one-second delay between each attempt.

    Forming a network following the generation of ZB_BDB_SIGNAL_FORMATION

    Forming a network following the generation of ZB_BDB_SIGNAL_FORMATION

    By default, after the successful network formation on the coordinator node, a single-permit join period of 180 seconds will be started, which will allow new Zigbee devices to join the network.

  2. Other devices will then join an existing network during this join period.

    • When a device has joined and Zigbee network rejoining is running, the procedure is cancelled.

    • If no device has joined and the procedure is not running, the procedure will be started.

    Forming a network following the generation of ZB_BDB_SIGNAL_STEERING

    Forming a network following the generation of ZB_BDB_SIGNAL_STEERING

Zigbee network leaving

The default signal handler implements the same behavior for handling ZB_ZDO_SIGNAL_LEAVE for both routers and end devices. When leaving the network, the default handler calls start_network_rejoin() to start Zigbee network rejoining to join a new network.

Once start_network_rejoin() is called, the stack will generate the ZB_BDB_SIGNAL_STEERING signal and will continue to Zigbee network formation and commissioning.

Leaving the network following ZB_ZDO_SIGNAL_LEAVE

Leaving the network following ZB_ZDO_SIGNAL_LEAVE

Zigbee network rejoining

The Zigee network rejoin procedure is a mechanism that is similar to the ZDO rejoin back-off procedure. It is implemented to work with both routers and end devices and simplify handling of cases such as device joining, rejoining, or leaving the network. It is used in default_signal_handler() by default.

If the network is left by a router or an end device, the device will try to join any open network.

  • The router will use the default signal handler to try to join or rejoin the network until it succeeds.

  • The end device will use the default signal handler to try to join or rejoin the network for a finite period of time, because the end devices are often powered by batteries.

    • The procedure to join or rejoin the network is restarted after the device reset or power cycle.

    • The procedure to join or rejoin the network can be restarted by calling user_input_indicate(), but it needs to be implemented in the application (for example, by calling user_input_indicate() when a button is pressed). The procedure will be restarted only if the device does not join and the procedure is not running.

The Zigbee rejoin procedure retries to join a network with each try after a specified amount of time: 2^n seconds, where n is the number of retries.

The period is limited to 15 minutes if the result is higher than that.

  • When start_network_rejoin() is called, the rejoin procedure is started, and depending on the device role:

    • For the end device, the application alarm is scheduled with stop_network_rejoin(ZB_TRUE), to be called after the amount of time specified in ZB_DEV_REJOIN_TIMEOUT_MS. Once called, the alarm stops the rejoin.

  • When stop_network_rejoin(was_scheduled) is called, the network rejoin is canceled and the alarms scheduled by start_network_rejoin() are canceled.

    • Additionally for the end device, if stop_network_rejoin() is called with was_scheduled set to ZB_TRUE, user_input_indicate() can restart the rejoin procedure.

  • For end devices only, user_input_indicate() restarts the rejoin procedure if the device did not join the network and is not trying to join a network. It is safe to call this function from an interrupt and to call it multiple times.

Note

The Zigbee network rejoin procedure is managed from multiple signals in default_signal_handler(). If the application controls the network joining, rejoining, or leaving, each signal in which the Zigbee network rejoin procedure is managed should be handled in the application. In this case, user_input_indicate() must not be called.

Zigbee stack sleep routines

For all device types, the Zigbee stack informs the application about periods of inactivity by generating a ZB_COMMON_SIGNAL_CAN_SLEEP signal.

The minimal inactivity duration that causes the signal to be generated is defined by sleep_threshold. By default, the inactivity duration equals approximately 15 ms. The value can be modified by the zb_sleep_set_threshold API.

Generation of the ZB_COMMON_SIGNAL_CAN_SLEEP signal

Generation of the ZB_COMMON_SIGNAL_CAN_SLEEP signal

The signal can be used to suspend the Zigbee task for the inactivity period. This allows the Zephyr kernel to switch to other tasks with lower priority. Additionally, it allows to implement a Zigbee Sleepy End Device. For more information about the power optimization of the Zigbee stack, see Sleepy End Device behavior.

The inactivity signal can be handled using the Zigbee default signal handler. If so, it will allow the Zigbee stack to enter the sleep state and suspend the Zigbee task by calling zigbee_event_poll() function.

If the default behavior is not applicable for the application, you can customize the sleep functionality by overwriting the zb_osif_sleep() weak function and implementing a custom logic for handling the stack sleep state.

Implementing a custom logic for putting the stack into the sleep mode

Implementing a custom logic for putting the stack into the sleep mode

Configuration

To enable the Zigbee application utilities library, set the CONFIG_ZIGBEE_APP_UTILS Kconfig option.

To configure the logging level of the library, use the CONFIG_ZIGBEE_APP_UTILS_LOG_LEVEL Kconfig option.

For detailed steps about configuring the library in a Zigbee sample or application, see Configuring Zigbee application utilities.

API documentation

Header file: include/zigbee/zigbee_app_utils.h
Source file: subsys/zigbee/lib/zigbee_app_utils/zigbee_app_utils.c
group zigbee_app_utils

Library with helper functions and routines.

Provides Zigbee default handler, helper functions for parsing and converting Zigbee data, indicating status of the device at a network using onboard LEDs.

Enums

enum addr_type_t

Address type.

ADDR_SHORT and ADDR_LONG correspond to APS addressing mode constants and must not be changed.

Values:

enumerator ADDR_INVALID
enumerator ADDR_ANY
enumerator ADDR_SHORT
enumerator ADDR_LONG

Functions

void zigbee_erase_persistent_storage(zb_bool_t erase)

Function for setting the Erase persistent storage, depending on the erase pin.

If the erase pin (1.39 by default, defined in zigbee_app_utils.c) is shortened to the ground, then the persistent storage is erased. Otherwise, whether the storage is erased is decided upon the input parameter ‘erase’. This behaviour is only valid if PCA10056 is used.

Parameters
  • [in] erase: Whether to erase the persistent storage in case the erase pin is not shortened to the ground.

int to_hex_str(char *out, uint16_t out_size, const uint8_t *in, uint8_t in_size, bool reverse)

Function for converting an input buffer to a hex string.

Return

snprintf-compatible value. Less than zero means encoding error. Non-negative value is the number of characters that would have been written if the supplied buffer had been large enough. Value greater than or equal to buf_len means that the supplied buffer was too small.

Note

Null terminator is written if buf_len is large enough, but does not count for the return value.

Parameters
  • [out] out: Pointer to the output buffer.

  • [in] out_size: Size of the output buffer.

  • [in] in: Pointer to the input buffer.

  • [in] in_size: Size of the input buffer.

  • [in] reverse: If true, data output happens in the reverse order.

bool parse_hex_str(char const *in_str, uint8_t in_str_len, uint8_t *out_buff, uint8_t out_buff_size, bool reverse)

Read array of uint8_t from hex string.

Parameters
  • in_str: Pointer to the input hex string.

  • in_str_len: Length, in characters, of the input string.

  • out_buff: Pointer to the output uint8_t array.

  • out_buff_size: Size, in bytes, of the output uint8_t array.

  • reverse: If true then parse from end to start.

Return Value
  • true: if the conversion succeed

  • false: if the conversion failed

static inline bool parse_hex_u8(char const *s, uint8_t *value)

Parse a hex string to uint8_t.

The function verifies if input is valid, i.e., if all input characters are valid hex digits. If an invalid character is found then function fails.

Parameters
  • s: Pointer to input string.

  • value: Pointer to output value.

Return Value
  • true: if the conversion succeed

  • false: if the conversion failed

static inline bool parse_hex_u16(char const *s, uint16_t *value)

Parse a hex string to uint16_t.

The function verifies if input is valid, i.e., if all input characters are valid hex digits. If an invalid character is found then function fails.

Parameters
  • s: Pointer to input string.

  • value: Pointer to output value.

Return Value
  • true: if the conversion succeed

  • false: if the conversion failed

int ieee_addr_to_str(char *str_buf, uint16_t buf_len, const zb_ieee_addr_t in)

Function for converting 64-bit address to hex string.

Return

snprintf-compatible value. Less than zero means encoding error. Non-negative value is the number of characters that would have been written if the supplied buffer had been large enough. Value greater than or equal to buf_len means that the supplied buffer was too small.

Note

Null terminator is written if buf_len is large enough, but does not count for the return value.

Parameters
  • [out] str_buf: Pointer to output buffer.

  • [in] buf_len: Length of the buffer pointed by str_buf.

  • [in] in: Zigbee IEEE address to be converted to string.

addr_type_t parse_address(const char *input, zb_addr_u *output, addr_type_t addr_type)

Function for parsing a null-terminated string of hex characters into 64-bit or 16-bit address.

The function will skip 0x suffix from input if present.

Return

Conversion result.

Parameters
  • input: Pointer to the input string string representing the address in big endian.

  • output: Pointer to the resulting zb_addr_u variable.

  • addr_type: Expected address type.

static inline bool parse_long_address(const char *input, zb_ieee_addr_t addr)

Function for parsing a null-terminated string of hex characters into a 64-bit address.

The function will skip 0x suffix from input if present.

Parameters
  • input: Pointer to the input string representing the address in big endian.

  • addr: Variable where the address will be placed.

Return Value
  • true: if the conversion succeed

  • false: if the conversion failed

static inline bool parse_short_address(const char *input, zb_uint16_t *addr)

Function for parsing a null-terminated string of hex characters into 16-bit address.

The function will skip 0x suffix from input if present.

Parameters
  • input: Pointer to the input string representing the address in big endian.

  • addr: Pointer to the a variable where address will be placed.

Return Value
  • true: if the conversion succeed

  • false: if the conversion failed

zb_ret_t zigbee_default_signal_handler(zb_bufid_t bufid)

Function for passing signals to the default Zigbee stack event handler.

Note

This function does not free the Zigbee buffer.

Return

RET_OK on success or error code on failure.

Parameters
  • [in] bufid: Reference to the Zigbee stack buffer used to pass signal.

void zigbee_led_status_update(zb_bufid_t bufid, uint32_t led_idx)

Function for indicating the Zigbee network connection status on LED.

If the device is successfully commissioned, the LED is turned on. If the device is not commissioned or has left the network, the LED is turned off.

Note

This function does not free the Zigbee buffer.

Parameters
  • [in] bufid: Reference to the Zigbee stack buffer used to pass signal.

  • [in] led_idx: LED index, as defined in the board-specific BSP header. The index starts from 0.