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:

  1. Utilizing Callbacks to create a manual bind
  2. Configuring cluster reporting and attributes
  3. 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

Hardware

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

  1. We will start by loading the out-of-box ZC light project. In CCS, go to File > Import > C/C++ > CCS Projects and under Select search-directory: select the following search path:

    C:\ti\simplelink_cc13xx_cc26xx_sdk_<version>\examples\rtos\<LaunchPad variant>\zstack\zc_light

  2. 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

  3. 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

  4. 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 the Zstackapi_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 the Zstackapi_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 the Zstackapi_sysNwkInfoReadReq API. The Zstackapi_ZdoBindReq API can now be called to create the bind.

    The first task is to set the required zdoCBReq flags to true in SetupZStackCallbacks of zcl_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

  1. 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 the ZCL_CLUSTER_ID_GENERAL_ON_OFF cluster from zclSampleLight_Attrs of zcl_samplelight_data.c contains ACCESS_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

  2. In zcl_samplelight.c, make sure that reportableChange 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 in zclSampleLight_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 the Project > Properties > Build > ARM Compiler > Predefined Symbols

  3. 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 the zcl_registerClusterOptionList API in zclSampleLight_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

  1. Repeat Step 1 of Task 1, except with the zr_sw (Zigbee Router switch) project

  2. In order to process the reports received from the ZC, define ZCL_REPORT_DESTINATION_DEVICE inside the ZR Switch Predefined Symbols. Then, add to the ZCL_CMD_REPORT case inside of zclSampleSw_ProcessIncomingMsg of zcl_samplesw.c:

    case ZCL_CMD_REPORT:
      zclSampleSw_ProcessInReportCmd( pInMsg );
      handled = TRUE;
      break;
    

    zclSampleSw_ProcessIncomingMsg

  3. Next, modify static void zclSampleSw_ProcessInReportCmd( zclIncoming_t *pInMsg) to handle pInMsg in whichever way necessary for your application.

Task 4: Programming and Viewing Results

  1. 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.

  2. 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.

  3. Run the program from inside the CCS debugger, then exit the debugger and reset the LaunchPad using the pushbutton on top.

  4. 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 of Zstackapi_ZdoBindReq to see if the zstack_ZStatusValues_ZSuccess is returned to indicate a successful bind.

  5. 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 reported ZCL_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 poll through a data request 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 from pInd->req.srcAddr of the zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE callback case and uint8_t EP is similarly from pInd->rsp.simpleDesc.endpoint of the zstackmsg_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.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.