Developing with ZBOSS for Zigbee
|
The ZBOSS stack provides the following services for the Zigbee application developer:
The SDK provides header files and libraries for C programming language. This means that applications must be implemented using C or C++.
The typical application source must:
zigbee_enable
.The stack uses a set of platform-independent type definitions such as:
All ZBOSS type names start with the prefix "zb_"
.
The application must include the header file zboss_api.h
, and do so before any other ZBOSS header files. This file contains most of the required routines to start the application.
The stack uses a cooperative multitasking model. The main component that provides multitasking capabilities is the scheduler.
There is no "task" abstraction in the scheduler; it uses "callback" instead. The callback has to match one of the following types:
typedef void (* zb_callback_t)(zb_uint8_t param);
typedef void (* zb_callback2_t)(zb_uint8_t param, zb_uint16_t cb_param);
Running a task in the scheduler simply executes a callback function.
Each callback function can schedule one or more callback functions for execution through the scheduler. The scheduled callbacks are stored in the internal scheduler queue. This queue is handled in the scheduler main loop: the callbacks are executed one-by-one in the FIFO order. If there is currently no callback to execute, the scheduler can put the device into sleep mode (if so configured).
Callbacks are scheduled for execution with:
A common practice is to specify a buffer ID as the first (or as the single) parameter for the callback. Scheduling a callback does not block the currently running function, as it is a fast non-blocking operation. More than one callback can be scheduled in succession. If there is no other callback in the queue, the scheduled callback is executed immediately after the current callback is finished.
The timer functionality uses the scheduler infrastructure and is implemented with alarms. An alarm is a callback that is scheduled for execution with a defined time delay. The time delay is specified during the scheduling of the alarm and guarantees that the alarm callback function is called not earlier than specified by the delay parameter.
There are API primitives provided to work with the alarms:
The following table lists the Scheduler API macros:
Macro | Parameters | Return value | Description |
---|---|---|---|
ZB_SCHEDULE_APP_CALLBACK(func, param) | zb_callback_t func – function to execute.zb_uint8_t param – callback parameter – usually a buffer ID. | RET_OK or RET_OVERFLOW . | Schedule a callback function with one parameter for execution. |
ZB_SCHEDULE_APP_CALLBACK2(func, param, user_param) | zb_callback_t func – function to execute.zb_uint8_t param – callback parameter. zb_uint16_t user_param – user parameter. | RET_OK or RET_OVERFLOW . | Schedule a callback function with two parameters for execution. |
ZB_SCHEDULE_APP_ALARM(func, param, timeout_bi) | zb_callback_t func – function to execute.zb_uint8_t param – callback parameter. zb_time_t timeout_bi – timeout, in beacon intervals. | RET_OK or RET_OVERFLOW . | Schedule an alarm for execution in the main scheduler loop: a callback with one parameter to be executed after a delay defined by the timeout value. |
ZB_SCHEDULE_APP_ALARM_CANCEL(func, param) | zb_callback_t func – scheduled function to cancel.zb_uint8_t param – callback parameter. | RET_OK or RET_OVERFLOW . | Cancel previously scheduled alarm. |
ZB_SCHEDULE_GET_ALARM_TIME(func, param, timeout_bi) | zb_callback_t func – function to execute.zb_uint8_t param – callback parameter zb_time_t timeout_bi – time left for an execution, in beacon intervals. | RET_OK or RET_NOT_FOUND . | Check whether an alarm was scheduled and return time left for the callback execution. |
nrf/subsys/zigbee/osif/zb_nrf_platform.h
.The following examples demonstrate the usage of Zigbee multitasking with two functions executing each other in an infinite loop. The first function executes immediately. The second function executes after a 10-second delay.
The Zigbee stack uses static memory allocation for its internal data structures (the memory is not allocated dynamically at runtime). The memory management system provides predictable memory usage and minimizes data copying at runtime when possible. The internal stack data structures are usually invisible to the application.
The main data structure visible for the application is the memory buffer, which transfers packets and parameters between the application and stack layers.
The stack uses a predefined pool of memory buffers of a fixed size and has a number of default presets to define the size of the memory buffer pool. You can configure the pool by using one of the following header files:
A memory buffer consists of a header and a data buffer. The memory buffer size equals 128 bytes + buffer tail with a set of parameters. This is enough to hold a single Zigbee packet or an APS fragment. When the APS fragmentation is required on rare occasions, the ZBOSS stack uses extended buffers.
The application or the stack function can allocate or release a single memory buffer from the pool on demand. Memory buffer allocation is a blocking operation: ZBOSS stack calls the user's callback when the buffer becomes available.
The application must use zb_buf_get_out_delayed() or zb_buf_get_in_delayed() calls to allocate a new buffer. When a buffer becomes available, the delayed buffer allocation makes a callback call through the scheduler.
Logically, the memory buffers are divided into two types:
This separation prevents allocating all memory buffers to only one type of buffer. For example, if all buffers are only TX buffers and all are in use waiting for an ACK packet, no ACK packet will be received because no RX buffer is available. To prevent this situation, the stack stops allocating TX buffers when half of the buffer pool is allocated to TX buffers. The same rule applies to allocating RX buffers.
Most of the stack functions require some parameters and some data (usually Zigbee packet or its fragment). Both parameters and data are passed to the API in a single memory buffer. Call parameters are stored in the tail of the buffer, while the data section is stored at the head of the buffer. Both the caller and the called functions must be aware of the parameter types.
For setting parameters and getting them from the buffer, ZB_BUF_GET_PARAM() is used.
The following buffer operations are available in a Zigbee application:
To reserve a space in the memory buffer, use the following methods:
The following table lists the available memory management API macros.
Macro | Parameters | Return value | Description |
---|---|---|---|
zb_buf_begin(bufid) | zb_bufid_t bufid – memory buffer ID. | void *data – data begin. | Get a pointer to the first allocated data element in the data buffer. |
zb_buf_len(bufid) | zb_bufid_t bufid – memory buffer ID. | zb_uint_t length – data length. | Get the allocated data size. |
zb_buf_initial_alloc(bufid, size) | zb_bufid_t bufid – memory buffer ID.zb_uint8_t size – size to allocate. | void *data – data begin. | Allocate a buffer in the memory buffer. If possible, the buffer is allocated starting with an offset in the data buffer. The existing buffer content is lost. |
zb_buf_alloc_left(bufid, size) | zb_bufid_t bufid – memory buffer ID.zb_uint8_t size – size to allocate. | void *data – data begin. | Extend the currently allocated buffer to the left. Commonly used for allocating a buffer for a packet header. |
zb_buf_alloc_right(bufid, size) | zb_bufid_t bufid – memory buffer ID.zb_uint8_t size – size to allocate. | void *data – data begin. | Extend the currently allocated buffer to the right. Commonly used for allocating a buffer for a packet tail. |
zb_buf_cut_left(bufid, size) | zb_bufid_t bufid – memory buffer ID.zb_uint8_t size – size to cut. | void *data – data begin. | Cut a buffer beginning. This macro is normally used for cutting a packet header. |
zb_buf_cut_right(bufid, size) | zb_bufid_t bufid – memory buffer ID.zb_uint8_t size – size to cut. | None. | Cut a buffer ending. This macro is usually used for cutting a packet tail. |
ZB_BUF_GET_PARAM(bufid, type) | zb_bufid_t bufid – memory buffer ID.type – requested parameter type. | type *ptr – pointer to the parameters. | Get a pointer to the structured parameters. The parameters are stored at the tail of the data buffer. |
zb_buf_reuse(bufid) | zb_bufid_t bufid – memory buffer ID. | None. | Get the specified IN or OUT buffer, clear all the data, and prepare the buffer for further usage. The buffer does not change its type: if it was an IN buffer, it remains an IN buffer after the procedure. |
zb_buf_free(bufid) | zb_bufid_t bufid – memory buffer ID. | None. | Release a memory buffer. |
zb_buf_get() | None. | zb_bufid_t bufid – memory buffer ID | Get an IN buffer from the buffer pool. If no buffer is available, return NULL . If possible, use the zb_buf_get_in_delayed(callback) instead. |
zb_buf_get_in_delayed(callback) | zb_callback_t callback – pointer to a callback function. | RET_OK or error code. | Get an IN buffer from the buffer pool; call the specified callback when the buffer is available. If the buffer is available, schedule the callback for execution immediately. Otherwise, wait until a buffer is available and then execute the callback. |
zb_buf_get_out_delayed(callback) | zb_callback_t callback – pointer to a callback function. | RET_OK or error code. | Get an OUT buffer from the buffer pool; call the specified callback when the buffer is available. If the buffer is available, schedule the callback for execution immediately. Otherwise, wait until a buffer is available and then execute the callback. |
zb_buf_get_out_delayed_ext(callback, arg, max_size) | zb_callback2_t callback – pointer to a callback function.zb_uint16_t arg – to be passed to callback.zb_uint_t max_size – required maximum buffer payload size | RET_OK or error code | Get an OUT buffer from the buffer pool. The behavior is the same as with zb_buf_get_out_delayed(), but a user parameter is passed to the callback as the second argument alongside the allocated buffer. Call this macro to use extended buffers. |
zb_buf_get_in_delayed_ext(callback, arg, max_size) | zb_callback_t callback – pointer to a callback function.zb_uint16_t arg – to be passed to callback.zb_uint_t max_size – required maximum buffer payload size | RET_OK or error code. | Get an IN buffer from the buffer pool; call the specified callback when the buffer is available. If the buffer is available, schedule the callback for execution immediately. Otherwise, wait until a buffer is available and then execute the callback. Call this macro to use extended buffers. |
The time subsystem is based on the hardware timer. It uses the special type zb_time_t, defined in the stack API. The time value is stored in ticks. Each tick is equal to one "beacon interval" (15.36 ms). The size of the zb_time_t type depends on the platform. The overflow is handled by the Time API.
The Time API provides the ability to:
The following table lists the available time API macros.
Macro | Parameters | Return value | Description |
---|---|---|---|
ZB_TIMER_GET() | None. | zb_time_t time | Get current timer value in beacon intervals. |
ZB_TIME_SUBTRACT(a, b) | zb_time_t a – time value.zb_time_t b – time value to subtract. | zb_time_t time – subtraction result. | Subtract time value: a - b . |
ZB_TIME_ADD(a, b) | zb_time_t a – time value.zb_time_t b – time value. | zb_ret_t time – addition result. | Add time values: a + b . |
ZB_TIME_GE(a, b) | zb_callback_t func – first time value to compare.zb_uint8_t param – second time value to compare. | ZB_TRUE if a ≥ b , ZB_FALSE otherwise. | Compare time value a and value b , check if a ≥ b . |
ZB_TIME_ONE_SECOND | None. | zb_time_t time – one second timeout. | Constant: one second in beacon intervals. |
ZB_MILLISECONDS_TO_BEACON_INTERVAL(ms) | zb_uint16_t ms – number of milliseconds to convert. | zb_time_t time – timeout in ms. | Convert time from milliseconds to beacon intervals. |
The Zigbee stack provides API for tracing capabilities for customer support purposes. The stack outputs a hex-encoded binary trace through the Logger module. The binary trace log cannot be decoded with the provided SDK. However, the Nordic support team can use it to find stack-level issues .
You can enable or disable tracing for a particular subsystem, and define the level of tracing. In addition, the stack is able to trace Zigbee input and output traffic (in/out) on the MAC level.
The trace mask enables or disables trace messages for the whole subsystem:
Macro | Value | Description |
---|---|---|
TRACE_SUBSYSTEM_APP | 0x0800 | Application. |
TRACE_SUBSYSTEM_ZDO | 0x0040 | ZDO subsystem. |
TRACE_SUBSYSTEM_ZCL | 0x0010 | APS subsystem. |
TRACE_SUBSYSTEM_NWK | 0x0008 | NWK subsystem. |
Tracing is controlled by the trace level and the trace mask. The trace level defines the importance of the message from 1 to 4: 1 is the most important, 4 is the least important. Setting the trace level to 1 blocks all the messages except those with the highest priority. If an application sets the trace level to 4, all trace messages are allowed to print.
The provided tracing subsystem is disabled by default. Use the following tracing API to manage tracing.
Macro | Parameters | Return value | Description |
---|---|---|---|
ZB_SET_TRACE_LEVEL(level) | zb_uint8_t level – trace level. | None. | Set trace level. |
ZB_SET_TRACE_MASK(mask) | zb_uint8_t mask – trace mask. | None. | Set trace mask. |
ZB_SET_TRACE_ON() | None. | None. | Enable trace. |
ZB_SET_TRACE_OFF() | None. | None. | Disable trace. |
ZB_SET_TRAF_DUMP_ON() | None. | None. | Enable dump. |
ZB_SET_TRAF_DUMP_OFF() | None. | None. | Disable dump. |
The ZBOSS stack informs the application about how long its scheduler is empty by using the signal handler's ZB_COMMON_SIGNAL_CAN_SLEEP.
The mechanism starts with the scheduler that tracks the callback queue (see Zigbee stack multitasking (scheduler)). If there is no immediate callback for the execution and there is no outgoing packet on the MAC level, a special signal ZB_COMMON_SIGNAL_CAN_SLEEP is sent to the application. This signal is sent when the ZBOSS scheduler is empty for some time, and it is sent regardless of the device role. It has a sleep time parameter that specifies the allowed sleep duration in milliseconds.
Upon receiving the signal, the application checks its internal state and decides whether to enter the sleep mode or not. To switch to the sleep mode, the application must call the zb_sleep_now() function.
After calling zb_sleep_now(), the stack prepares its peripherals for sleep, and informs the application that it is ready to go to sleep with a call to zb_osif_sleep()
. The sleep duration value is based on the time remaining before the first alarm is called, and it is longer for the sleepy end device.
The application can use zb_sleep_set_threshold() to set the minimum sleep duration value, which reduces the number of the triggered ZB_COMMON_SIGNAL_CAN_SLEEP signals. If the sleep duration is lower than the specified threshold, the signal is not triggered. By default, the value of the sleep duration threshold ZB_SCHED_SLEEP_THRESHOLD_MS
is set to 20 ms. The upper limit for this value is 86400000 ms (one day).
Macro | Parameters | Return value | Description |
---|---|---|---|
zb_ret_t zb_sleep_set_threshold(zb_uint32_t threshold_ms) | threshold_ms -– sleep threshold value in milliseconds. | RET_OK – If the interval is successfully set. RET_ERROR – Otherwise. | Set threshold value. By default, set to ZB_SCHED_SLEEP_THRESHOLD_MS (20 ms). |
zb_uint32_t zb_get_sleep_threshold() | None. | Currently used threshold value. | |
zb_sleep_now() | None. | None. | Immediately put the device into sleep mode. This call is permitted only in signal handler when application receives the ZB_COMMON_SIGNAL_CAN_SLEEP signal. |
To enable the sleepy behavior on an end device, make sure your application calls the zb_set_rx_on_when_idle() function with the ZB_FALSE
parameter, but before starting the ZBOSS stack. This function specifies whether the device should have the radio disabled between the polls to parent.
As a result, during the preparation of peripherals for sleep, the stack will additionally turn off Sleepy End device radio and its own timer.
All end devices (including RxOnWhenIdle=TRUE
) that have received an End Device Timeout Response Command with a status SUCCESS can periodically send keep alive messages to their router parent to ensure they remain in the router’s neighbor table.
You can configure the end device timeout period with zb_set_ed_timeout. The default timeout that is set in NWK_ED_DEVICE_TIMEOUT_DEFAULT
equals 256 minutes. If the parent does not receive any keep alive messages from the child during this period, it deletes the child from the neighbor table.
The period for sending the keep alive to the router parent must be determined by the manufacturer of the device and is not specified by the standard. It is recommended to set this period in such a way that the end device sends by default 3 keep alive messages during the End Device Timeout period. The application can use zb_set_keepalive_timeout to configure how often the keep alive message is sent.
The keep alive method that the end device uses depends on the support of the router parent. It can be MAC data poll keep alive (which is the most common option), or End Device Timeout Request keep alive.
If the router parent supports the MAC data poll keep alive, the keep alive configuration may conflict with the Long Poll configuration. This happens when the application changes the Long Poll interval using the Poll Control Cluster API or using zb_zdo_pim_set_long_poll_interval. It is recommended to set the Long Poll interval to a value smaller than ((End Device Timeout period) / 3) to avoid unexpected device aging.
If the router parent supports the End Device Timeout Request keep alive, then the device sends an End Device Timeout Request command as a unicast and waits for an End Device Timeout Response from its parent, according to the configured Keep Alive Timeout.
The polling intervals can be configured by the Poll control ZCL cluster commands or by zb_zdo_pim_set_long_poll_interval.
There are two standard polling presets:
The device always uses the long poll preset, unless there is an ongoing communication that requires a higher packet exchange rate.
The turbo poll is a ZBOSS extension that allows to temporary increase the poll rate when ZED is awaiting an answer from the parent. This is useful for actions when you know either the exact number of packets to be received or the time period when these packets are expected to be received.
The extension implements an adaptive algorithm that lowers the poll interval to the configured value and raises it gradually up to the long poll interval when the parent is not responding. The turbo poll can be enabled and disabled by zb_zdo_pim_permit_turbo_poll.
When the turbo poll is enabled, it starts automatically for the specified number of packets when one of the following conditions is met:
In each of these cases, the turbo poll uses the adaptive algorithm until the expected number of packets is received.
The application can start the turbo poll for the specified number of packets by calling zb_zdo_pim_start_turbo_poll_packets.
The turbo poll can also be forced for a certain period of time by calling zb_zdo_pim_start_turbo_poll_continuous, and stopped by calling zb_zdo_pim_turbo_poll_continuous_leave when the application expects multiple packets during this period. In this case, the turbo poll uses the adaptive algorithm until the timeout expiration. For instance, the application may enable the turbo poll during the initial configuration of Sleepy End Device to speed up the communication process.
Zigbee stack includes a production configuration block feature. This block is useful for per-device customization and is supposed to be written at device production time. The size of the production block must not exceed 128 bytes.
The production configuration block:
build/zephyr/include/generated/pm_config.h
file. To place the production config partition at a specific address, use static configuration. For more information, see Partition Manager in the nRF Connect SDK documentation.The block is loaded at the very start of zboss_main_loop_iteration(), so it rewrites all the corresponding data from NVRAM.
The production configuration block has a system part and an optional application-specific part:
Data that can be placed into the system part of the production configuration block includes:
Moreover, the data specific to your application can be placed in the application part of the production configuration block.