Introducing the EmberZNet PRO API#
This section introduces the EmberZNet PRO API. The EmberZNet PRO API controls the EmberZNet PRO stack library and provides function calls and callbacks related to the network formation, discovery, joining, and messaging capabilities. For full reference documentation of the functions and their parameters, see the API documentation for your Silicon Labs product at silabs.com.
Silicon Labs recommends that software engineers new to EmberZNet PRO or those who are looking to refresh their understanding of the different components of the API read this section. You will see how the API can help you to quickly develop applications.
API Organization#
The EmberZNet PRO API is broken into 16 functional sections. This section provides a detailed introduction to six of the fundamental API sections:
Network Formation
Packet Buffers
Sending and Receiving Messages
End Devices
Security and Trust Center
Event Scheduling
The other functional sections are:
Stack Information
Common Data Types
Binding Table
Configuration
Status Codes
Stack Tokens
Zigbee Device Object (ZDO)
Bootloader
Manufacturing and Functional Test Library
Debugging Utilities
Naming Conventions#
All functions that are part of the public EmberZNet PRO API begin with the prefix ember. Silicon Labs strongly recommends that you maintain this convention when writing custom software so that it is easy to find information and documentation pertaining to a function.
API Files and Directory Structure#
The following list describes files within the stack that contain useful information.
\<stack\>/config/config.h: This file contains the stack build revision and can be used when communicating with technical support or when verifying that the stack version used is correct. The format of the version number is described in the file.
\<stack\>/config/ember-configuration-defaults.h: This file describes compile-time configurable options that affect the behavior of the EmberZNet PRO stack. These should be set in the CONFIGURATION_HEADER or in the Project so that the values are properly set in all files.
\<stack\>/include: This directory contains all the API header files. The correct ones for the application are included in ember.h, so the application usually only needs to include ember.h. The files can be useful as a reference source for advanced developers. The API reference documentation is generated from these header files.
Network Formation#
Silicon Labs provides a set of APIs you can use to find, form, join, and leave Zigbee networks.
Stack Initialization#
The EmberZNet PRO stack is initialized by calling emberInit() in the main() function. It may be passed a value for the reset code that can be used for debugging if the device is attached to a Debug Adapter with Simplicity Studio.
status = emberInit(reset);Note:
emberInit()must be called before any other stack APIs are used, or the results will be undefined.
For more information about debugging, see Testing and Debugging Applications for the Silicon Labs EFR32MG Platforms.
Calling emberNetworkInit() causes the device to rejoin the network that it was joined to before it rebooted. This maintains as many of the previous network settings as possible (for example, the network address is maintained if possible).
if (emberNetworkInit() == EMBER_SUCCESS)) {
// Successfully rejoined previous network
} else {
// No previous network or could not successfully rejoin
}Note: On development systems or systems that change device type (both ZR and ZED, for example), the application should verify if the cached device type is the desired device type. This behavior is shown in the sample applications later in this book.
Network Operation#
Proper operation of the network is facilitated by calling emberTick() regularly in your program loop. The watchdog should also be re-set:
while(TRUE) {
halResetWatchdog();
emberTick();
// Application-specific functions here
}Network Formation#
Functions for creating, joining, and leaving a network have descriptive names: emberFormNetwork(), emberPermitJoining(), emberJoinNetwork(), emberFindAndRejoinNetwork(), and emberLeaveNetwork().
Functions for finding a network or determining background energy levels include: emberStartScan(), emberStopScan(), emberScanCompleteHandler(), emberEnergyScanResultHandler(), and emberNetworkFoundHandler().
Note: EmberZNet PRO does not have different stack libraries for Zigbee controller (ZC) and Zigbee Router (ZR) devices, so any device that calls emberFormNetwork() creates the network and becomes the ZC. As such, only the device starting the network should call emberFormNetwork(), and other devices should call emberJoinNetwork(), which is described below.
The ZC can then use emberPermitJoining() to allow joining, subject to the configured security settings:
emberPermitJoining(60); // Permit joining for 60 seconds
emberPermitJoining(0xFF); // Permit joining until turned off
emberPermitJoining(0); // Do not permit joiningFor more information on security settings and authorization, please refer to UG103.05: IoT Endpoint Security Fundamentals.
Joining a Network#
Joining a network is accomplished with the emberJoinNetwork() API:
status = emberJoinNetwork(EMBER_ROUTER, &networkParams); // To join as a ZR
status = emberJoinNetwork(EMBER_SLEEPY_END_DEVICE, &networkParams); // To join as a Sleepy ZED
status = emberJoinNetwork(EMBER_MOBILE_END_DEVICE, &networkParams); // To join as a Mobile ZEDThe networkParams variable is a structure of type EmberNetworkParameters and configures the PAN-ID, extended PAN-ID (or 0 for any), channel of the network to join, and the desired TX power with which to join the network.
Silicon Labs also provides a utility function that uses emberStartScan(), emberStopScan(), and emberScanCompleteHandler() to discover networks that match the provided options and to join the first one that it finds:
// Use a function from app/util/common/form-and-join.c
// that scans and selects a beacon that has:
// 1) allow join=TRUE
// 2) matches the stack profile that the app is using
// 3) matches the extended PAN ID passed in unless "0" is passed
// Once a beacon match is found, emberJoinNetwork is called.
joinZigbeeNetwork(EMBER_ROUTER, EMBER_ALL_802_15_4_CHANNELS_MASK,
-1, (int8u*) extendedPanId);The utility emberFindandRejoinNetwork() is used on devices that have lost contact with their network and need to scan and rejoin.
Packet Buffers#
The EmberZNet PRO stack provides a full set of functions for managing memory. This memory is statically allocated at link time, but dynamically used during run time. This is a valuable mechanism because it allows you to use statically linked, buffers for variable-length messages. This also gives you a better idea of how much RAM your software will require during run time. Procedures differ depending on the SDK version.
SDK version 6.7.x and earlier#
Common functions include allocating buffers with predefined content, copying to/from existing buffers, and freeing allocated buffers. A typical procedure to complete buffer usage is:
Allocate a new buffer large enough for length bytes, copy length bytes from dataArray, and check to see that the allocation succeeded:
buffer = emberFillLinkedBuffers(dataArray, length); if (buffer == EMBER_NULL_MESSAGE_BUFFER) { // the allocation failed! Do not proceed! }Copy length bytes from buffer into dataArray, starting at index 0:
emberCopyFromLinkedBuffers(buffer, 0, dataArray, length);Return all memory used by buffer so it can be re-used:
emberReleaseMessageBuffer(buffer);Many functions are available for copying or appending data between packet buffers and arrays. Stack buffers, linked buffers, and message buffers all refer to the same type of data structure. The naming varies depending on the expected usage of the individual functions. See the packet buffer API documentation at stack/include/packet-buffer.h for a full listing as well as details about each function.
SDK version 6.8.0 and later#
The system used to manage EmberZNet PRO buffer memory was changed in SDK version 6.8.0. In the new system, a "Buffer" allocates variable sized contiguous blocks which are managed by a mark/sweep garbage collector. Any consumer holding a reference to a Buffer must provide a marking function which will call the emMarkBuffer() callback on each buffer reference. Any buffers not marked by any marker function will be released. Note that the buffers are compacted during garbage collection so the consumer must not access the buffer by any pointer outside of the same scope in which emGetBufferPointer() is called. Also note that extending any buffer other than the most recently allocated can also cause the buffer to move. Buffers must be passed by reference to any function which may call emberAppendToLinkedBuffers().
Typical EmberZNet PRO applications do not use the buffer system directly so no changes should be needed. For applications that do use buffers directly, the only required porting should be to add the necessary marking function. All of the EmberMessageBuffer APIs are supported through the legacy-packet-buffer.h header file. For detailed information on the new functions, refer to util/silicon_labs/ silabs_core/buffer_manager/buffer-management.h.
Address Table or Binding Table Management#
The address table is maintained by the network stack and contains IEEE addresses and network short addresses of other devices in the network. Messages can be sent using the address table by specifying the type as EMBER_OUTGOING_VIA_ADDRESS_TABLE in commands such as emberSendUnicast(). More details on the address table are in message.h.
The binding table can also be used for sending messages. The binding code is within a library, so flash space is not used if the application does not use binding. Refer to binding-table.h for more details.
Sending and Receiving Messages#
Refer to message.h for more details on sending or receiving messages.
Sending Messages#
Sending messages is simple:
// To send to a device previously entered in the address table:
status = emberSendUnicast(EMBER_OUTGOING_VIA_ADDRESS_TABLE,
destinationAddressTableIndex,
&apsFrame,
buffer, &sequenceNum);
// To send to a device via its 16-bit address (if known):
status = emberSendUnicast(EMBER_OUTGOING_DIRECT,
destinationId,
&apsFrame,
buffer, &sequenceNum);
In both cases the apsFrame contains the unicast message options, such as retry or enable route discovery, the buffer contains the message, and the sequence number argument provides a pointer to the APS sequence number returned by the stack when the mes- sage is queued. In the case of EMBER_OUTGOING_VIA_ADDRESS_TABLE, the destinationAddressTableIndex should contain the index of the previously stored address table entry.
Broadcast messages are sent in a similar way:
// To send a broadcast message:
status = emberSendBroadcast(DESTINATION //one of 3 ZigBee broadcast addresses
&apsFrame,
radius, // 0 for EMBER_MAX_HOPS
buffer, &sequenceNum);The return code should always be checked to see if the stack will attempt delivery.
Note: An EMBER_SUCCESS return code does NOT mean that the message was successfully delivered; it only means that the EmberZNet PRO stack has accepted the message for delivery. If RETRY is specified on a unicast message,
emberMessageSentHandler()will be called to inform the application about the delivery results.
Receiving Messages#
Incoming messages are received through the emberIncomingMessageHandler(), a handler function that is called by the EmberZNet PRO stack and implemented by the application. The parameters passed to the function are:
Message Type: for example, UNICAST, BROADCAST
APS Frame
Message buffer containing the data contents of the message
Several functions are only available within the context of the emberIncomingMessageHandler() function:
emberGetLastHopLqi(): returns the incoming LQI of the last hop transmission of this messageemberGetLastHopRssi(): returns the incoming RSSI of the last hop transmission of this messageemberGetSender(): gets the sender’s 16-bit network addressemberGetSenderEui64(): gets the sender’s 64-bit IEEE address
Note: This is available only if the sender included the 64-bit address—see the API reference for more information.
emberSendReply(): allows a message to be sent in reply to an incoming unicast message.
Source Routes and Large Networks#
Aggregation routes (also called “many-to-one routes”) are used to efficiently create network-wide routes to the gateway device(s). Source routes are then used from these gateway devices to send messages back to devices in the network. The source route is specified in the message network header, reducing the route-related memory requirements on intermediate devices. The functions emberSendManyToOneRouteRequest(), emberAppendSourceRouteHandler(), emberIncomingRouteRecordHandler(), emberIncomingManyToOneRouteRequestHandler(), emberIncomingRouteErrorHandler() are all used during source routing.
Key Aggregation-Related APIs
The application of the concentrator uses the following new API call to establish the inbound routes, typically on a periodic basis:
EmberStatus emberSendManyToOneRouteRequest(int16u concentratorType,
int8u radius);
The concentratorType is EMBER_HIGH_RAM_CONCENTRATOR or EMBER_LOW_RAM_CONCENTRATOR.
For a High Ram Concentrator, nodes send in route records only until they hear a source routed message from the concentrator, or until a new many-to-one discovery happens.
For a Low Ram Concentrator, route records are sent before every APS message.
Devices wishing to communicate with the concentrator should create an address table entry including the short address of the concentrator. The application should avoid initiating address discovery or other kinds of broadcasts to the concentrator for scalability. Instead, the necessary information should be obtained through broadcasts or multicasts from the concentrator. Also, when sending APS unicasts to the concentrator, the discover route option should be off. If using the binding table rather than the address table, the binding should be of type EMBER_AGGREGATION_BINDING, which tells the stack not to initiate route or address discovery for that binding.
From the application's point of view, one of the key aspects of the API is the need to manage the source route information on the concentrator. By defining EMBER_APPLICATION_USES_SOURCE_ROUTING in the configuration header, the following two callbacks (normally stubbed out when this define is absent) are exposed to the application:
/** @description Reports the arrival of a route record command frame
* to the application. The application must
* define EMBER_APPLICATION_USES_SOURCE_ROUTING in its
* configuration header to use this.
*/
void emberIncomingRouteRecordHandler(EmberNodeId source,
int8u relayCount,
EmberMessageBuffer header
int8u relayListIndex);
/** @description The application can implement this callback to
* supply source routes to outgoing messages. The application
* must define EMBER_APPLICATION_USES_SOURCE_ROUTING in its
* configuration header to use this. It uses the supplied
* destination to look up a source route. If available, it
* appends the source route to the supplied header using the
* proper frame format, as described in section 3.4.1.9
* "Source Route Subframe Field" of the ZigBee specification.
*
* @param destination: The network destination of the message.
* @param header: The message buffer containing the partially
* complete packet header. The application appends the source
* route frame to this header.
*/
void emberAppendSourceRouteHandler(EmberNodeId destination,
EmberMessageBuffer header);
The first callback supplies the recorded routes, which can be stored in a table. The second callback is invoked by the network layer for every outgoing unicast (including APS acknowledgements), and it is up to the application to supply a source route or not. The source route adds (#relays + 1) * 2 bytes to the network header frame, which therefore reduces the maximum application payload available for that packet.
The files app/util/source-route.c and app/util/source-route.h implements these callbacks and can be used as-is by node applications wishing to be a concentrator.
For EZSP host applications, EZSP library calls pass incoming route records to the host through the incomingRouteRecordHandler frame. Supplying a source route for outgoing messages works a little bit differently. The host needs to call the setSourceRoute command immediately prior to sending the unicast.
End Devices#
EmberZNet PRO provides two types of end devices, Sleepy End Devices (Sleepy ZED) and Mobile End Devices (Mobile ZED). Mobile ZEDs are expected to move, so information on these devices is not saved in parent devices. Sleepy ZEDs are expected to maintain the same parent device except in cases where the parent is lost.
For ZEDs, the APIs provide sleep and wake, parent polling, and parent status functions. For parent routers (including the coordinator), the APIs provide child polling event notification and child management functionality.
Refer to child.h for more details on these functions.
Security and Trust Center#
Security policies for the network are established by the trust center when the network is formed. Devices joining a network must use the existing security policies or they will not be allowed to join. See UG103.2: Zigbee Fundamentals and UG103.5: Security Fundamentals for detailed discussions of Zigbee and EmberZNet PRO security settings, respectively. Details are also included in /stack/include/security.h.
Event Scheduling#
The Event Scheduling macros implement an event abstraction that allows the application to schedule code to run after some specified time interval. Events are also useful for when an ISR needs to initiate an action that should run outside of the ISR context.
While custom event-handling code can be written by the application, Silicon Labs recommends that developers consider using this system first before consuming additional flash and RAM, which duplicates its functionality. Refer to event.h for more details.