Introduction
Z-Stack is a component of the SimpleLink CC13xx / CC26xx Software Development Kit and is a complete Zigbee 3.0 software solution.
The intention of this lab is to help developers use Z-Stack features and API to implement commonly used functionality. Topics that we will cover in this lab include:
- Utilizing Callbacks to create a manual bind
- Configuring cluster reporting and attributes
- Sending raw data messages over-the-air
Note: This lab is intended to be an informative guide for starting development and as such may not offer the optimal solution for any given application.
Technical support
For any questions you may have, please refer to the TI Zigbee & Thread E2E Forum.
Prerequisites
Background
- Familiarity with the Zigbee 3.0 specification
- More information about the changes in Zigbee 3.0 compared to older specifications can be found in SWRA615
- Basic CCS knowledge
- Some basic familiarity with embedded programming
Software
- Code Composer Studio (latest version) with support for CC13xx/CC26xx devices
- SimpleLink CC13xx / CC26xx SDK (latest)
- Uniflash
- Ubiqua Protocol Analyzer (optional for packet sniffing)
- Wireshark (optional for packet sniffing)
- TI Packet Sniffer (optional for packet sniffing with CC2531EMK)
- TI Packet Sniffer 2 (optional for packet sniffing with LaunchPads)
Hardware
- 2 x Zigbee-capable SimpleLink LaunchPads for running sample applications, review the SDK Release Notes for more information.
- 1 x LaunchPad or CC2531EMK for packet sniffing (optional)
- Micro USB Cables
- 1 x LaunchPad or CC2531EMK for packet sniffing (optional)
- Micro USB cables
Recommended Reading
Part 1: Cluster Reporting and Manual Binds
Purpose
In this part of the lab, we are going to create a manual bind on the On/Off Cluster between a Light and Switch, in the Server-to-Client direction, where the Light implements the Server side of the cluster and the Switch implements the Client side of the cluster. By the ZCL Specification, the On/Off cluster Server device has a mandatorily-reportable attribute (Attribute ID 0x0000, OnOff, Table 3-45 of the ZCL v7 Spec). Zigbee 3.0 mandates that, if a bind exists on this cluster in the correct direction (Server-to-Client in this case), this device must support automatic ZCL attribute reporting. In other words, if we create the aforementioned bind, the Light will automatically start reporting its OnOff attribute's state to the Switch.
Note
A similar bind can be completed through the discovery process using the Common User Interface, however the method described below will create the bind without any additional user action.
Task 0: Erase the Flash Memory on your LaunchPads
Make sure that the flash memory has been completely erased on each of your LaunchPads before flashing any Zigbee projects. This can be done in Uniflash, here:
Task 1: Altering the Light Coordinator for a Manual Bind
We will start by loading the out-of-box ZC light project. In CCS, go to
File > Import > C/C++ > CCS Projects
and underSelect search-directory:
select the following search path:C:\ti\simplelink_cc13xx_cc26xx_sdk_<version>\examples\rtos\<LaunchPad variant>\zstack\zc_light
Since we are reporting the On/Off input (server) cluster attribute which has already been configured for the base project, no changes are required to
zcl_samplelight_data.c
There are a few global variables to define in
zcl_samplelight.c
that will help later on in the lab:zstack_LongAddr_t bindAddr; uint16_t shortAddr = 0; uint8_t EP = 0; uint8_t transID = 0; uint8_t theMessageData[]={"\0\0\30Hello World"};
zcl_samplelight.c
In order to create a bind in a Zigbee network, we need the NWK (short) address of the destination device, the IEEE (long) address of the destination device, and the destination Zigbee endpoint where the relevant cluster lives on the destination device. The
Zstackapi_ZdoBindReq
API can create this bind using the aforementioned parameters. There are various methods we can use to obtain these parameters locally on our device, but one easy way to start this process is to parse incoming Device Announce messages that are broadcasted as new devices join our Zigbee network.Once we process the Device Announce inside of the
zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE
callback and obtain the long address of our destination device, we can send another special Zigbee message, called an Active Endpoint Request, to this device to find which of its endpoints have active Zigbee applications running on them. We can use theZstackapi_ZdoActiveEndpointReq()
API to request this endpoint information.After receiving the destination device's Active Endpoint Response from the
zstackmsg_CmdIDs_ZDO_ACTIVE_EP_RSP
callback, we can use the received endpoint to send a Simple Descriptor Requests to each endpoint to find the one which contains the desired application clusters. We can use theZstackapi_ZdoSimpleDescReq
API to verify the application clusters of this endpoint.The
zstackmsg_CmdIDs_ZDO_SIMPLE_DESC_RSP
callback handles the Simple Descriptor Response from the target device and will confirm which destination endpoint inquired contains the On/Off output cluster. Since we have already received the destination address from the Device Announce and source address from the Active Endpoint Request, the only remaining parameter is the endpoint. This can be obtained by reading the local device information through theZstackapi_sysNwkInfoReadReq
API. TheZstackapi_ZdoBindReq
API can now be called to create the bind.The first task is to set the required
zdoCBReq
flags to true inSetupZStackCallbacks
ofzcl_samplelight.c
:static void SetupZStackCallbacks(void) { zstack_devZDOCBReq_t zdoCBReq = {0}; // Register for Callbacks, turn on: // Device State Change, // ZDO Match Descriptor Response, zdoCBReq.has_devStateChange = true; zdoCBReq.devStateChange = true; zdoCBReq.has_deviceAnnounce = true; zdoCBReq.deviceAnnounce = true; zdoCBReq.has_matchDescRsp = true; zdoCBReq.matchDescRsp = true; zdoCBReq.has_ieeeAddrRsp = true; zdoCBReq.ieeeAddrRsp = true; zdoCBReq.has_activeEndpointRsp = true; zdoCBReq.activeEndpointRsp = true; zdoCBReq.has_simpleDescRsp = true; zdoCBReq.simpleDescRsp = true; #if defined Z_POWER_TEST #if defined (POWER_TEST_POLL_DATA) zdoCBReq.has_deviceAnnounce = true; zdoCBReq.deviceAnnounce = true; #endif #endif // Z_POWER_TEST (void)Zstackapi_DevZDOCBReq(appServiceTaskId, &zdoCBReq); }
SetupZStackCallbacks
We can then replace the following cases inside of
zclSampleLight_processZStackMsgs
:case zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE: #if defined (Z_POWER_TEST) #if defined (POWER_TEST_POLL_DATA) { zstackmsg_zdoDeviceAnnounceInd_t *pInd; pInd = (zstackmsg_zdoDeviceAnnounceInd_t*)pMsg; // save the short address of the ZED to send ZCL test data powerTestZEDAddr = pInd->req.srcAddr; // start periodic timer for sending ZCL data to zed OsalPortTimers_startReloadTimer(appServiceTaskId, SAMPLEAPP_POWER_TEST_ZCL_DATA_EVT, Z_POWER_TEST_DATA_TX_INTERVAL); } #endif #else { zstackmsg_zdoDeviceAnnounceInd_t *pInd; pInd = (zstackmsg_zdoDeviceAnnounceInd_t*)pMsg; zstack_zdoActiveEndpointReq_t pReq; pReq.dstAddr = pInd->req.srcAddr; pReq.nwkAddrOfInterest = pInd->req.devAddr; shortAddr = pInd->req.srcAddr; OsalPort_memcpy(bindAddr, &(pInd->req.devExtAddr), Z_EXTADDR_LEN); Zstackapi_ZdoActiveEndpointReq(appServiceTaskId, &pReq); } #endif break; //... break; case zstackmsg_CmdIDs_ZDO_SIMPLE_DESC_RSP: { zstackmsg_zdoSimpleDescRspInd_t *pInd; pInd = (zstackmsg_zdoSimpleDescRspInd_t*)pMsg; if((pInd->rsp.status == zstack_ZdpStatus_SUCCESS) && (pInd->rsp.simpleDesc.n_outputClusters)) { while((*(pInd->rsp.simpleDesc.pOutputClusters) != ZCL_CLUSTER_ID_GENERAL_ON_OFF) && (pInd->rsp.simpleDesc.pOutputClusters != NULL)) { pInd->rsp.simpleDesc.pOutputClusters++; } if(pInd->rsp.simpleDesc.pOutputClusters != NULL) { zstack_sysNwkInfoReadRsp_t pRsp; Zstackapi_sysNwkInfoReadReq(appServiceTaskId, &pRsp); zstack_zdoBindReq_t pReq; pReq.nwkAddr = pRsp.nwkAddr; pReq.bindInfo.clusterID = ZCL_CLUSTER_ID_GENERAL_ON_OFF; pReq.bindInfo.srcEndpoint = SAMPLELIGHT_ENDPOINT; pReq.bindInfo.dstAddr.addrMode = (zstack_AFAddrMode)Addr64Bit; pReq.bindInfo.dstAddr.endpoint = pInd->rsp.simpleDesc.endpoint; EP = pInd->rsp.simpleDesc.endpoint; OsalPort_memcpy(pReq.bindInfo.dstAddr.addr.extAddr, &bindAddr, Z_EXTADDR_LEN); OsalPort_memcpy(pReq.bindInfo.srcAddr, &(pRsp.ieeeAddr), Z_EXTADDR_LEN); Zstackapi_ZdoBindReq(appServiceTaskId, &pReq); } } } break; case zstackmsg_CmdIDs_ZDO_ACTIVE_EP_RSP: { zstackmsg_zdoActiveEndpointsRspInd_t *pInd; pInd = (zstackmsg_zdoActiveEndpointsRspInd_t*)pMsg; zstack_zdoSimpleDescReq_t pReq; pReq.dstAddr = pInd->rsp.srcAddr; pReq.nwkAddrOfInterest = pInd->rsp.nwkAddrOfInterest; for (uint8_t i = 0; i < pInd->rsp.n_activeEPList; i++) { pReq.endpoint = pInd->rsp.pActiveEPList[i]; if (pReq.endpoint != 0xF2) Zstackapi_ZdoSimpleDescReq(appServiceTaskId, &pReq); } } break;
zclSampleLight_processZStackMsgs
Task 2: Configuring cluster reporting and attributes
Although the bind has now been created, further steps are required to report the status of the ZC Light to the ZR Switch. First make sure that the
ATTRID_ON_OFF_ON_OFF
attribute record inside theZCL_CLUSTER_ID_GENERAL_ON_OFF
cluster fromzclSampleLight_Attrs
ofzcl_samplelight_data.c
containsACCESS_REPORTABLE
:{ ZCL_CLUSTER_ID_GENERAL_ON_OFF, { // Attribute record ATTRID_ON_OFF_ON_OFF, ZCL_DATATYPE_BOOLEAN, ACCESS_CONTROL_READ | ACCESS_REPORTABLE, (void *)&zclSampleLight_OnOff } },
zclSampleLight_Attrs
In
zcl_samplelight.c
, make sure thatreportableChange
is globally defined:#ifdef BDB_REPORTING #if BDBREPORTING_MAX_ANALOG_ATTR_SIZE == 8 uint8_t reportableChange[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; #endif #if BDBREPORTING_MAX_ANALOG_ATTR_SIZE == 4 uint8_t reportableChange[] = {0x1, 0x00, 0x00, 0x00}; #endif #if BDBREPORTING_MAX_ANALOG_ATTR_SIZE == 2 uint8_t reportableChange[] = {0x01, 0x00}; #endif #endif
zcl_samplelight.c
Then confirm the
Zstackapi_bdbRepAddAttrCfgRecordDefaultToListReq
APIs inzclSampleLight_Init
#ifdef BDB_REPORTING zstack_bdbRepAddAttrCfgRecordDefaultToListReq_t Req = {0}; #endif // ... #ifdef BDB_REPORTING //Adds the default configuration values for the on/off attribute of the ZCL_CLUSTER_ID_GENERAL_ON_OFF cluster, for endpoint SAMPLELIGHT_ENDPOINT //Default maxReportingInterval value is 10 seconds //Default minReportingInterval value is 3 seconds //Default reportChange value is 300 (3 degrees) Req.attrID = ATTRID_ON_OFF_ON_OFF; Req.cluster = ZCL_CLUSTER_ID_GENERAL_ON_OFF; Req.endpoint = SAMPLELIGHT_ENDPOINT; Req.maxReportInt = 10; Req.minReportInt = 0; OsalPort_memcpy(Req.reportableChange,reportableChange,BDBREPORTING_MAX_ANALOG_ATTR_SIZE); Zstackapi_bdbRepAddAttrCfgRecordDefaultToListReq(appServiceTaskId,&Req); #ifdef ZCL_LEVEL_CTRL Req.attrID = ATTRID_LEVEL_CURRENT_LEVEL; Req.cluster = ZCL_CLUSTER_ID_GENERAL_LEVEL_CONTROL; Req.endpoint = SAMPLELIGHT_ENDPOINT; Req.maxReportInt = 10; Req.minReportInt = 0; OsalPort_memcpy(Req.reportableChange,reportableChange,BDBREPORTING_MAX_ANALOG_ATTR_SIZE); Zstackapi_bdbRepAddAttrCfgRecordDefaultToListReq(appServiceTaskId,&Req); #endif #endif
zclSampleLight_Init
Finally, make sure
BDB_REPORTING
is defined in theProject > Properties > Build > ARM Compiler > Predefined Symbols
Cluster attributes can also be configured to accept different ZCL options, such as
AF_ACK_REQUEST
. An example of this feature is demonstrated by using thezcl_registerClusterOptionList
API inzclSampleLight_Init
:static zclOptionRec_t zclSampleLight_ZCL_Options[] = {ZCL_CLUSTER_ID_GENERAL_ON_OFF, AF_ACK_REQUEST }; zcl_registerClusterOptionList( SAMPLELIGHT_ENDPOINT, 1, zclSampleLight_ZCL_Options );
zclSampleLight_Init
Task 3: Configuring the Switch Router
Repeat Step 1 of Task 1, except with the zr_sw (Zigbee Router switch) project
In order to process the reports received from the ZC, define
ZCL_REPORT_DESTINATION_DEVICE
inside the ZR Switch Predefined Symbols. Then, add to theZCL_CMD_REPORT
case inside ofzclSampleSw_ProcessIncomingMsg
ofzcl_samplesw.c
:case ZCL_CMD_REPORT: zclSampleSw_ProcessInReportCmd( pInMsg ); handled = TRUE; break;
zclSampleSw_ProcessIncomingMsg
Next, modify
static void zclSampleSw_ProcessInReportCmd( zclIncoming_t *pInMsg)
to handlepInMsg
in whichever way necessary for your application.
Task 4: Programming and Viewing Results
Choose similar network configurations for both projects (make sure that at least a common channel has been selected). Refer to the Network Configurations section of the TI Z-Stack User's Guide for more information.
For the ZR Switch project, go to
Run > Debug
to flash the program on the first LaunchPad. If you have multiple LaunchPads connected to your PC, CCS may prompt you to select one of the connected LaunchPads via the box below:The selected LaunchPad's serial number will be assigned to this project workspace so future program launches will automatically attempt to select this LaunchPad first if it is plugged in. More information about debugging with multiple probes can be found here.
Run the program from inside the CCS debugger, then exit the debugger and reset the LaunchPad using the pushbutton on top.
Now change active projects to the ZC Light and once more use
Run > Debug
to flash the program on your second LaunchPad. After running the program from the debugger you should be able to commission the network and join both devices by pressing LP-1 on the ZC Light followed by LP-1 on the ZR Switch. If successful, then an active sniffer should be able to observe the ZR Device Announce, ZC Active Endpoint Request/ZR Active Endpoint Response, and ZC Simple Descriptor Request/ZR Simple Descriptor Response.You can set a breakpoint at the end of
zstackmsg_CmdIDs_ZDO_SIMPLE_DESC_RSP
and observe the status ofZstackapi_ZdoBindReq
to see if thezstack_ZStatusValues_ZSuccess
is returned to indicate a successful bind.You should also now be able to notice the ZC Light reporting to the ZR Switch at an interval of
maxReportInt
seconds. AF acknowledgments are requested by the reportedZCL_CLUSTER_ID_GENERAL_ON_OFF
cluster.The TI Sample Switch Common User Interface will also display the light's status on the
App Info
row.Note
The ZR configuration was chosen so that the ZC would not have to wait for a ZED sleepy device to send a data request (i.e. polling) before the ZC could report its attribute, however the ZED configuration can be chosen if this delay is acceptable
Part 2: Send raw data packets over-the-air
Sometimes it is desired to send a custom, non-ZCL data packet to a target device. In these instances it is important to use a private clusterID
(defined outside of the ZCL specification e.g. 0xF00D) to send the raw data. These messages are commonly achieved by using the Zstackapi_AfDataReq
API, as shown with the ZC light sample application:
if(key == CONFIG_BTN_RIGHT)
{
zstack_getZCLFrameCounterRsp_t pRsp;
Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &pRsp);
theMessageData[1] = transID++;
zstack_afDataReq_t pReq;
pReq.dstAddr.addrMode = zstack_AFAddrMode_SHORT;
pReq.dstAddr.addr.shortAddr = shortAddr;
pReq.dstAddr.endpoint = EP;
pReq.pRelayList = NULL;
pReq.n_relayList = 0;
pReq.srcEndpoint = SAMPLELIGHT_ENDPOINT;
pReq.clusterID = 0xF00D;
pReq.transID = &(pRsp.zclFrameCounter);
pReq.options.ackRequest = FALSE;
pReq.options.apsSecurity = FALSE;
pReq.options.limitConcentrator = FALSE;
pReq.options.skipRouting = FALSE;
pReq.options.suppressRouteDisc = FALSE;
pReq.options.wildcardProfileID = FALSE;
pReq.radius = AF_DEFAULT_RADIUS;
pReq.n_payload = sizeof(theMessageData);
pReq.pPayload = theMessageData;
Zstackapi_AfDataReq(appServiceTaskId, &pReq);
}
zclSampleLight_processKey
In this example:
uint8_t theMessageData[]
is defined globally as{"\0\0\30Hello World"}
, where the first three characters fill in the ZCL header: 0x00 Frame Counter, incrementing Transaction Sequence Number, and 0x18 General Command Frame (reserved/unspecified by ZCL)zstackapi_bdbGetZCLFrameCounterReq
can be used to get the frame counter for the transID parameter- Using
zstack_AFAddrMode_SHORT
will send directly to a node uint16_t shortAddr
is frompInd->req.srcAddr
of thezstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE
callback case anduint8_t EP
is similarly frompInd->rsp.simpleDesc.endpoint
of thezstackmsg_CmdIDs_ZDO_SIMPLE_DESC_RSP
On the target device (ZR Switch) side, the incoming message can be processed inside of zclSampleSw_processAfIncomingMsgInd
Note
zstack_AFAddrMode_NONE
can be used to let the reflector (source binding) figure out the destination address without a destination endpoint or short address, but only if the correctly bound clusterID
is used and in a payload format that is expected from such a cluster. Due to the ZCL cluster ID restrictions this method is not typically recommended.
Next Recommended Zigbee SimpleLink Academy Labs
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.