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 understand how keepalives can be implemented in a Zigbee network and the use of Groups and Scenes clusters. Topics that we will cover in this lab include:

  1. Keepalives and the Poll Control Cluster
  2. Groups and Scenes Clusters

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
  • Knowledge of packet sniffing (optional)

Software

Hardware

Zigbee Documentation

  • Zigbee PRO 2017 Specification (Zigbee Spec R22) - Zigbee Document number 05-3474-22
  • Zigbee Cluster Library v7 Specification (ZCL spec) - Zigbee Document number 07-5123-07
  • Zigbee Lighting and Occupancy Device Specification (ZLO spec) - Zigbee Document number 15-0014-05
  • Zigbee Home Automation Public Application Profile (ZHA spec) - Zigbee Document number 05-3520-29

Note

Zigbee documentation can be obtained from the Zigbee Alliance website

Part 1: Keepalive and Poll Control Cluster

In this part of the lab, we are going to discuss keepalives and the Poll Control cluster. Zigbee 3.0 introduces a child aging feature, which allows parent devices to know which of its child devices are still in the network. If a parent detects prolonged absence of activity from a child end device, it will queue a leave request for that child and free the corresponding entry in its association table. The length of time for the prolonged absense may be specified by an End Device Timeout request from an end device (if no request is sent, then the parent simply uses a default value for aging out that child). This is discussed in Z-Stack Overview > Miscellaneous > Child Management section of the TI Z-Stack User's Guide.

According to the Zigbee spec (r22), parent devices may support two types of messages as a keepalives: MAC data poll and End Device timeout request (see Table 3-55 in the spec). Z-Stack currently supports MAC data poll for keepalives (as indicated by zgNwkParentInformation in Stack/sys/zglobals.h).

The Z-Stack examples by default define POLL_RATE as 3000 (determined inside the Z-Stack > Power Management > Poll Period parameter of the .syscfg project file). That means child devices will poll (send a MAC data request) every 3000 ms. Note that other mechanisms, such as commissioning, may also cause the end device to send MAC data requests.

Did you know?

In addition to serving as keepalives, the MAC data request also allows the ZED to check whether the parent has a message for it. Parent devices keep a queue of messages for its sleepy children (eg. ZEDs). Since the queue has finite size, if the ZED does not poll frequently enough, messages for it may be dropped. A parent device's queue size is defined by MAC_CFG_TX_MAX (found in Stack/Config/f8wcoord.opts or Stack/Config/f8wrouter.opts). Messages in the queue may also timeout. The timeout value is defined by NWK_INDIRECT_MSG_TIMEOUT (controlled by the Z-Stack > Advanced > Routing > Network Indirect Message Timeout parameter of the .syscfg project file).

Values related to keepalives are described in the following table.

Macro Name Description Default Value Location
END_DEV_TIMEOUT_VALUE For a parent device, this is the default time that parent expects a child device to send a keepalive. If no keepalive is received from a child, then this parent proceeds to age that child out.
For a child device, this is the time that the child device requests the parent device to wait before aging it out. If successful, the child device should wait no more than this amount of time before polling.
8 (256 minutes) default/syscfg/ti_zstack_config.h (from .syscfg: Z-Stack > Network > End Device Timeout)
NWK_END_DEVICE_LEAVE_TIMEOUT Used only by the parent device.
When a parent proceeds to age out a child, the parent queues a leave request for that child. This leave request remains queued for that child for this amount of time.
9 (512 minutes) Stack/sys/zglobals.h

Now, let's use the LaunchPads and SimpleLink SDK to evaluate these statements.

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: Parent Aging Out a Child

  1. For this task, we will use the zed_light and zc_sw examples. 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\

    Choose zc_sw and zed_light from the Discovered projects: to import.

    Note

    Typically the light would be ZC and the switch would be ZED, since the light would likely be mains powered while the switch has no need to stay on for an extended time. However, we use the light as ZED in this example because it clearly shows evidence of the differing poll rates (via latency between switch press and light toggling). This will be relevant in Task 2.

  2. Given the above discussion, in order to see the ZC send a leave request to the child after aging out that child, the child must poll at a rate slower than the "keepalive" time (END_DEV_TIMEOUT_VALUE) but at a faster rate than the NWK_END_DEVICE_LEAVE_TIMEOUT. To do this on the ZED, set END_DEV_TIMEOUT_VALUE to 0 (10 secs) and poll rate to 15 secs. No need to change NWK_END_DEVICE_LEAVE_TIMEOUT, since its default value of 9 is sufficiently large (512 minutes).

    Change the polling rate after the end device has successfully completed the commissioning process. The application layer is notified of this completion through a commissioning status message, which the example applications handle in the *_ProcessCommissioningStatus function.

    For the zed_light, locate the zclSampleLight_ProcessCommissioningStatus function inside Application/zcl_samplelight.c. Once the ZED has joined a network, the BDB commissioning process will be in the BDB_COMMISSIONING_NWK_STEERING state with a status of BDB_COMMISSIONING_SUCCESS. Add code to change the polling rate here.

        // ...
        case BDB_COMMISSIONING_NWK_STEERING:
          if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS)
          {
            //YOUR JOB:
            //We are on the nwk, what now?
    
            // ...
    
            // for SLA, added to change the default poll rate
            zstack_sysConfigWriteReq_t writeReq = { 0 };
            writeReq.has_pollRate = true;
            writeReq.pollRate = 15000; // poll every 15 secs
            writeReq.pollRateType = POLL_RATE_TYPE_DEFAULT | POLL_RATE_KEEP_ALIVE;
            Zstackapi_sysConfigWriteReq(appServiceTaskId, &writeReq);
            // end added
          }
        // ...
    

    zclSampleLight_ProcessCommissioningStatus in zcl_samplelight.c

    After flashing both devices, you should now see the leave request that the ZC sends the ZED, as in the following sniffer log:

    Notice that the ZED sends a Rejoin Request. Although the parent has already removed its association with the ZED, the ZED does not know this and polls its last parent. In this case, the Zigbee Spec defines that the parent should send this ZED a leave request with rejoin set to true. The ZED is then able to perform the rejoin sequence to any nearby routing devices.

Task 2: Poll Control Cluster

For the purposes of this task, we will use the same zed_light and zc_sw examples. Undo the lines of code from the zed_light that changed the polling rate. Also, revert the changes we made to the zglobal.h macros (ie, change END_DEV_TIMEOUT_VALUE to the default value of 8).

In the previous task, the polling rate remained constant after the ZED joins a network. A longer poll rate saves more power on the ZED. However, in use cases where responsiveness is a high priority sometimes, a shorter poll rate is required. Since a shorter poll rate reduces battery life, a balance between the two scenarios is desirable and can be achieved using the Poll Control cluster of the Zigbee Cluster Library specification (ZCL spec).

The ZCL spec defines a Poll Control cluster that may be used to dynamically change the polling rate of a ZED. The ZED will implement the Poll Control cluster server, and its parent (in this case the ZC) will implement the Poll Control cluster client. In this lab, we will go over the following: checkin interval, long poll interval, and short poll interval.

Checkin interval is the period at which the server will send a checkin command to each of its clients. This allows clients to request that the server enter fast poll mode.

Long poll interval is the period at which the server will poll when it is not in fast poll mode.

Short poll interval is the period at which the server will poll when it is in fast poll mode.

Section 3.16 of the ZCL spec describes these in more detail.

First, we need to add the Poll Control cluster into these examples.

  1. Integrate the Poll Control cluster to the projects by performing the following for both projects:

    Right click Common/zcl/zcl_poll_control.c and de-select "Exclude from Build".

    In zcl_*.c and zcl_*_data.c, make sure you have #include "zcl_poll_control.h".

    These files contain commands and definitions needed to implement the Poll Control cluster.

  2. Add Poll Control cluster into the cluster list (in zcl_*_data.c).

      const cId_t zclSampleSw_OutClusterList[] =
      {
        ZCL_CLUSTER_ID_GENERAL_IDENTIFY,
        ZCL_CLUSTER_ID_GENERAL_ON_OFF,
        ZCL_CLUSTER_ID_GENERAL_GROUPS,
      #if defined (OTA_CLIENT_INTEGRATED)
        ZCL_CLUSTER_ID_OTA
      #endif
      #if defined(ZCL_POLL_CONTROL)
        ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL,
      #endif
      };
    

    zcl_samplesw_data.c

      const cId_t zclSampleLight_InClusterList[] =
      {
        ZCL_CLUSTER_ID_GENERAL_BASIC,
        ZCL_CLUSTER_ID_GENERAL_IDENTIFY,
        ZCL_CLUSTER_ID_GENERAL_GROUPS,
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        ZCL_CLUSTER_ID_GENERAL_ON_OFF
      #ifdef ZCL_LEVEL_CTRL
        , ZCL_CLUSTER_ID_GENERAL_LEVEL_CONTROL
      #endif
      #ifdef ZCL_POLL_CONTROL
        , ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL
      #endif
      };
    

    zcl_samplelight_data.c

    Note

    Make sure to add ZCL_POLL_CONTROL to the predefined symbols for both light and switch.

  3. Add Poll Control cluster attributes to the server's attribute list.

        // Poll Control Cluster
        #ifdef ZCL_POLL_CONTROL
        uint32_t zclSampleLight_PollControlCheckInInterval    = 120;  // 120 qs == 30 s
        uint32_t zclSampleLight_PollControlLongPollInterval   = 20;   // 20 qs == 5 s
        uint16_t zclSampleLight_PollControlShortPollInterval  = 2;    // 2 qs == 0.5 s
        uint16_t zclSampleLight_PollControlFastPollTimeout    = 32;   // 32 qs == 8 s
        #endif
    

    in zcl_samplelight_data.c

      #ifdef ZCL_POLL_CONTROL
        {
          ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL,
          {
            ATTRID_POLL_CONTROL_CHECK_IN_INTERVAL,
            ZCL_DATATYPE_UINT32,
            ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE,
            (void *)&zclSampleLight_PollControlCheckInInterval
          }
        },
        {
          ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL,
          {
            ATTRID_POLL_CONTROL_LONG_POLL_INTERVAL,
            ZCL_DATATYPE_UINT32,
            ACCESS_CONTROL_READ,
            (void *)&zclSampleLight_PollControlLongPollInterval
          }
        },
        {
          ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL,
          {
            ATTRID_POLL_CONTROL_SHORT_POLL_INTERVAL,
            ZCL_DATATYPE_UINT16,
            ACCESS_CONTROL_READ,
            (void *)&zclSampleLight_PollControlShortPollInterval
          }
        },
        {
          ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL,
          {
            ATTRID_POLL_CONTROL_FAST_POLL_TIMEOUT,
            ZCL_DATATYPE_UINT16,
            ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE,
            (void *)&zclSampleLight_PollControlFastPollTimeout
          }
        },
      #endif
    

    at the end of zclSampleLight_Attrs[] in zcl_samplelight_data.c

  4. Declare and define the Poll Control callbacks struct, the callbacks themselves, and other relevant code:

      // Application Events
      #define SAMPLELIGHT_POLL_CONTROL_TIMEOUT_EVT  0x0001
      #define SAMPLELIGHT_LEVEL_CTRL_EVT            0x0002
      #define SAMPLEAPP_END_DEVICE_REJOIN_EVT       0x0004
      #define SAMPLEAPP_DISCOVERY_TIMEOUT_EVT       0x0008
      #define SAMPLEAPP_PROV_CONNECT_EVT            0x0080
      #define SAMPLEAPP_PROV_DISCONNECT_EVT         0x0010
      #define SAMPLEAPP_POLICY_UPDATE_EVT           0x0020
      #define SAMPLEAPP_SYNC_ATTR_EVT               0x0040
      #define SAMPLELIGHT_NEW_POLL_RATE_EVT         0x00010000 // added for SLA
    

    define new event in zcl_samplelight.h

      // Poll Control Attributes
      #ifdef ZCL_POLL_CONTROL
      extern uint32_t zclSampleLight_PollControlCheckInInterval;
      extern uint32_t zclSampleLight_PollControlLongPollInterval;
      extern uint16_t zclSampleLight_PollControlShortPollInterval;
      extern uint16_t zclSampleLight_PollControlFastPollTimeout;
      #endif
    

    declare external variables for poll control attributes in zcl_samplelight.h

      #ifdef ZCL_POLL_CONTROL
      static ZStatus_t zclSampleLight_PollControlCheckInRspCB( zclPollControlCheckInRsp_t *pCmd );
      static ZStatus_t zclSampleLight_PollControlFastPollStopCB();
      static ZStatus_t zclSampleLight_PollControlSetLongPollIntervalCB( zclPollControlSetLongPollInterval_t *pCmd );
      static ZStatus_t zclSampleLight_PollControlSetShortPollIntervalCB( zclPollControlSetShortPollInterval_t *pCmd );
      #endif
    
      #ifdef ZCL_POLL_CONTROL
      static uint32_t newPollRate;
      static void changePollRate(uint32_t newPollRateMs)
      {
        newPollRate = newPollRateMs;
        appServiceTaskEvents |= SAMPLELIGHT_NEW_POLL_RATE_EVT;
    
        Semaphore_post(appSemHandle);
      }
      #endif
    
      /*********************************************************************
       * ZCL Poll Control Callback table
       */
      #ifdef ZCL_POLL_CONTROL
      static zclPollControl_AppCallbacks_t zclSampleLight_PollControlCallbacks =
      {
        NULL,
        zclSampleLight_PollControlCheckInRspCB,
        zclSampleLight_PollControlFastPollStopCB,
        zclSampleLight_PollControlSetLongPollIntervalCB,
        zclSampleLight_PollControlSetShortPollIntervalCB
      };
    
      // Poll Control cluster callback implementation(s)
      static Clock_Struct pollControlFastTimeoutClkStruct;
      static Clock_Handle pollControlFastTimeoutClkHandle; // pollControlFastTimeoutClkHandle;
      static void pollControl_FastPollTimeoutCB(UArg a0)
      {
          zclSampleLight_PollControlFastPollStopCB();
      }
      static ZStatus_t zclSampleLight_PollControlCheckInRspCB( zclPollControlCheckInRsp_t *pCmd )
      {
          if (pCmd->startFastPolling)
          {
              // start timer for fast polling timeout
              uint32_t RequestedTimeoutInMs;
              if (UtilTimer_isActive(&pollControlFastTimeoutClkStruct))
                  UtilTimer_stop(&pollControlFastTimeoutClkStruct);
              if (pCmd->fastPollTimeOut == 0) // use default fast poll timeout
                  RequestedTimeoutInMs = zclSampleLight_PollControlFastPollTimeout * 250;
              else // use requested fast poll timeout
                  RequestedTimeoutInMs = pCmd->fastPollTimeOut * 250;
              UtilTimer_setTimeout(pollControlFastTimeoutClkHandle, RequestedTimeoutInMs);
              UtilTimer_start(&pollControlFastTimeoutClkStruct);
    
              // start fast polling
              uint32_t IntervalInMs = zclSampleLight_PollControlShortPollInterval * 250;
              changePollRate(IntervalInMs);
          }
          return ZSuccess;
      }
      static ZStatus_t zclSampleLight_PollControlFastPollStopCB()
      {
          // stop fast polling timer, if running
          if (UtilTimer_isActive(&pollControlFastTimeoutClkStruct))
              UtilTimer_stop(&pollControlFastTimeoutClkStruct);
    
          // stop fast polling (ie, resume long polling)
          uint32_t IntervalInMs = zclSampleLight_PollControlLongPollInterval * 250;
          changePollRate(IntervalInMs);
    
          return ZSuccess;
      }
      static ZStatus_t zclSampleLight_PollControlSetLongPollIntervalCB( zclPollControlSetLongPollInterval_t *pCmd )
      {
          uint32_t IntervalInMs = pCmd->newLongPollInterval * 250;
          zclSampleLight_PollControlLongPollInterval = IntervalInMs;
    
          return ZSuccess;
      }
      static ZStatus_t zclSampleLight_PollControlSetShortPollIntervalCB( zclPollControlSetShortPollInterval_t *pCmd )
      {
          uint32_t IntervalInMs = pCmd->newShortPollInterval * 250;
          zclSampleLight_PollControlShortPollInterval = IntervalInMs;
    
          return ZSuccess;
      }
      #endif
    

    add to zcl_samplelight.c

      // ...
    
      // Register the ZCL General Cluster Library callback functions
      zclGeneral_RegisterCmdCallbacks( SAMPLELIGHT_ENDPOINT, &zclSampleLight_CmdCallbacks );
    
      #ifdef ZCL_POLL_CONTROL
        zclPollControl_RegisterCmdCallbacks( SAMPLELIGHT_ENDPOINT, &zclSampleLight_PollControlCallbacks );
      #endif
    
      // ...
    

    register callbacks for the Poll Control cluster zclSampleLight_Init in zcl_samplelight.c

      /*********************************************************************
       * ZCL Poll Control Callback table
       */
      #ifdef ZCL_POLL_CONTROL
      // Functions to process Poll Control cluster command/response messages
      static ZStatus_t zclSampleSw_PollControlCheckInCB( zclPollControlCheckIn_t *pCmd );
    
      static zclPollControl_AppCallbacks_t zclSampleSw_PollControlCallbacks =
      {
        zclSampleSw_PollControlCheckInCB,
        NULL,
        NULL,
        NULL,
        NULL
      };
    
      // Poll Control cluster callback implementation(s)
      static uint8_t startFastPolling = TRUE;
      static uint16_t fastPollTimeout = 0;
      static uint8_t disableDefaultRsp = FALSE;
      static ZStatus_t zclSampleSw_PollControlCheckInCB( zclPollControlCheckIn_t *pCmd )
      {
          afAddrType_t dstAddr;
          dstAddr.addr.shortAddr = pCmd->srcAddr->addr.shortAddr;
          dstAddr.addrMode = (afAddrMode_t)Addr16Bit;
          dstAddr.endPoint = pCmd->srcAddr->endPoint;
    
          zstack_getZCLFrameCounterRsp_t Rsp;
          Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &Rsp);
    
          return zclPollControl_Send_CheckInRsp( SAMPLESW_ENDPOINT, &dstAddr,
                                                 startFastPolling, fastPollTimeout,
                                                 disableDefaultRsp, Rsp.zclFrameCounter);
      }
      #endif
    

    add to zcl_samplesw.c

        // ...
        // Register the ZCL General Cluster Library callback functions
        zclGeneral_RegisterCmdCallbacks( SAMPLESW_ENDPOINT, &zclSampleSw_CmdCallbacks );
    
        #ifdef ZCL_POLL_CONTROL
          zclPollControl_RegisterCmdCallbacks(SAMPLESW_ENDPOINT, &zclSampleSw_PollControlCallbacks);
        #endif
        // ...
    

    register callbacks for the Poll Control cluster zclSampleSw_Init in zcl_samplesw.c

  5. On the server side, add in code to handle events triggered by the Poll Control cluster.

  6. On the server side, add in code to initialize the Poll Control cluster clocks:

        #ifdef ZCL_POLL_CONTROL
        // initialization for Poll Control
        static Clock_Struct pollControlCheckInClkStruct;
        static Clock_Handle pollControlCheckInClkHandle;
        static void pollControl_CheckInClkCB(UArg a0)
        {
             appServiceTaskEvents |= SAMPLELIGHT_POLL_CONTROL_TIMEOUT_EVT;
             Semaphore_post(appSemHandle);
        }
        static void zclPollControl_initClocks()
        {
            // initialize timer for fast poll timeout
            uint32_t DefaultDurationInMs = zclSampleLight_PollControlFastPollTimeout * 250;
            pollControlFastTimeoutClkHandle =
                    UtilTimer_construct(&pollControlFastTimeoutClkStruct,
                                    pollControl_FastPollTimeoutCB, /* callback when timer expires */
                                    DefaultDurationInMs, /* offset duration before timer starts */
                                    0, /* period (in ms) after which the timer will expire. 0 means one-shot */
                                    0, /* don't immediately start timer*/
                                    0  /* don't care*/
                                    );
            // start timer for periodic checkin. Clock_Handle not needed because checkin interval typically doesn't change.
            DefaultDurationInMs = zclSampleLight_PollControlCheckInInterval * 250;
            UtilTimer_construct(&pollControlCheckInClkStruct,
                            pollControl_CheckInClkCB,
                            DefaultDurationInMs,
                            DefaultDurationInMs,
                            1, /* start timer immediately */
                            0
                            );
        }
        #endif
    

    add to zcl_samplelight.c

        static void zclSampleLight_initializeClocks(void)
        {
            // ...
    
            #ifdef ZCL_POLL_CONTROL
                zclPollControl_initClocks();
            #endif   
        }
    

    in zcl_samplelight.c

  7. For Type1 or Type2 clusters, Finding & Binding could be used for automatic binding. However, the Poll Control cluster is neither a Type1 nor Type2 cluster (ZCL documents it as a utility cluster), we need to manually create a bind for it. (If you recall from the Zigbee Fundamentals SLA, we discussed how to manually create a bind.) The client (sw) will send a bind request for itself to the server (light). The direction of this bind is from server to client.

    In ZC, let's add the following pieces of code.

      #ifdef ZCL_POLL_CONTROL
        static zstack_LongAddr_t PollControl_serverLongAddr;
      #endif
    

    in zcl_samplesw.c

      /*******************************************************************************
       * @fn      SetupZStackCallbacks
       *
       * @brief   Setup the Zstack Callbacks wanted
       *
       * @param   none
       *
       * @return  none
       */
      static void SetupZStackCallbacks(void)
      {
          zstack_devZDOCBReq_t zdoCBReq = {0};
    
          // ...
    
          // for SLA. let's also utilize the following callbacks
          zdoCBReq.has_deviceAnnounce = true;
          zdoCBReq.deviceAnnounce = true;
          zdoCBReq.has_simpleDescRsp = true;
          zdoCBReq.simpleDescRsp = true;
    
          (void)Zstackapi_DevZDOCBReq(appServiceTaskId, &zdoCBReq);
      }
    

    in zcl_samplesw.c

      // ...
      case zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE:
      #if defined(DMM_ZCSWITCH) && defined(NWK_TOPOLOGY_DISCOVERY)
            {
                NwkDiscovery_start();
            }
            break;
      #else
            {
                // for this SLA, we are only expecting one device (light with Poll Control cluster server)
                zstackmsg_zdoDeviceAnnounceInd_t *pInd;
                pInd = (zstackmsg_zdoDeviceAnnounceInd_t *) pMsg;
                memcpy(PollControl_serverLongAddr, pInd->req.devExtAddr, sizeof(zstack_LongAddr_t));
            }
            break;
      #endif
      // ...
    

    in function zclSampleSw_processZStackMsgs of zcl_samplesw.c

            case zstackmsg_CmdIDs_ZDO_SIMPLE_DESC_RSP:
            {
              zstackmsg_zdoSimpleDescRspInd_t *pInd;
              pInd = (zstackmsg_zdoSimpleDescRspInd_t *) pMsg;
    
              zstack_sysNwkInfoReadRsp_t myNwkInfo;
              Zstackapi_sysNwkInfoReadReq(appServiceTaskId, &myNwkInfo);
    
              zstack_zdoBindReq_t bindReq;
    
              // send this bind request to the Poll Control server
              bindReq.nwkAddr = pInd->rsp.srcAddr;
    
              // the bind source is the Poll Control server
              OsalPort_memcpy(bindReq.bindInfo.srcAddr, PollControl_serverLongAddr, sizeof (zstack_LongAddr_t));
              bindReq.bindInfo.srcEndpoint = pInd->rsp.simpleDesc.endpoint;
    
              // this bind is for the Poll Control cluster
              bindReq.bindInfo.clusterID = ZCL_CLUSTER_ID_GENERAL_POLL_CONTROL;
    
              // destination of the bind is us, the Poll Control client
              bindReq.bindInfo.dstAddr.addrMode = (zstack_AFAddrMode) zstack_AFAddrMode_EXT;
              OsalPort_memcpy(bindReq.bindInfo.dstAddr.addr.extAddr, myNwkInfo.ieeeAddr, sizeof(zstack_LongAddr_t));
              bindReq.bindInfo.dstAddr.endpoint = SAMPLESW_ENDPOINT;
    
              // check status as necessary
              zstack_ZStatusValues status =
              Zstackapi_ZdoBindReq(appServiceTaskId, &bindReq);
            }
              break;
    

    in function zclSampleSw_processZStackMsgs of zcl_samplesw.c

  8. Now we can use the Poll Control cluster. Let's evaluate the following to make sure our setup works:

    • The ZED will send a check in command (every 30 sec).
    • After the ZED sends a check in command, the ZC will send a check in response, indicating that the ZED should enter fast poll mode. Since the check in response contains a fast poll timeout of 0, the Poll Control cluster server will use the default timeout (zclSampleLight_PollControlFastPollTimeout).
    • When the ZED receives a check in response indicating "yes" for fast polling, the ZED will begin polling at a rate of 500 ms for up to 8 sec.

    While the ZED is in fast poll mode, it will be more responsive to toggle commands (since it polls every 0.5 sec). It is left as an exercise for the developer to evaluate the other Poll Control cluster commands and callbacks.

Part 2: Groups and Scenes Clusters

In this part of the lab, we are going to discuss/use the Groups and Scenes clusters of the ZCL.

The Groups cluster provides over-the-air management of groups. This is in addition to the group support handled inherently by the Application Support Layer (APS). In other words, while APS completely takes care of core group functionality (such as adding, removing, and filtering), the Groups cluster gives devices the ability to manage groups using over-the-air Groups cluster commands.

The Scenes cluster provides a way to set cluster attributes on a device(s) to specific values, as defined by a scene entry. This is useful if a set of (usually home automation related) devices need to enter a defined state. For example, consider a "night" scene. Recalling that scene may consist of turning off lights, locking doors, lowering shades, and setting the temperature. The corresponding scene entry would include an On/Off cluster with the OnOff attribute set to off, a Door Lock cluster with the LockState set to locked, etc.

Although Groups and Scenes clusters may be used individually, they may be used together for efficiently setting up common environmental settings among related devices.

Task 1: Groups Cluster

In Zigbee, a group is a collection of endpoints. Since group messages are broadcasted to non-sleepy devices (ie with network address 0xFFFD), endpoints of sleepy end devices cannot receive group messages (they can still send group messages).

Groups are differentiated by unique group IDs, similar to how devices are differentiated by unique network addresses. Commands sent to a group are received by all endpoints within that group.

For that reason, it's possible to evaluate the Groups cluster and group addressing among slightly differing setups. As an example, consider two endpoints EP1 and EP2 that are in the same group.

  1. EP1 and EP2 may reside on one device (eg. both on one LaunchPad)
  2. EP1 and EP2 may reside on different devices (eg. one on LaunchPad 1 and the other on LaunchPad 2)

Because the behaviors of setup 1 and setup 2 are practically indistinguishable for this task, we will aribitrarily use setup 2.

For this task, we will use the zc_sw and zr_light examples. The zc_sw will request On/Off cluster server(s) to add themselves to the same group. In this way, one group addressed toggle command will be received by both lights.

  1. Import the zc_sw and zr_light examples. Use the same method as in Part 1 Task 1. (If you are using the same workspace as in Part 1, please rename the zc_sw project used in Part 1.)

    • zc_sw is already configured as a Groups cluster client
    • zr_light is already configured as a Groups cluster server
  2. Add code to the zc_sw (Groups cluster client).

      // the first byte of GROUP_NAME is the length of this group's name, "my_group"
      static uint8_t GROUP_NAME[] = { 0x08,
                              'm', 'y', '_', 'g', 'r', 'o', 'u', 'p' };
      #define GROUP_ID 0x0001
    

    in zcl_samplesw.c

    Since group addressed commands will be used in this task, Finding & Binding is unnecessary. Instead, we can utilize the Match Descriptor request to find endpoints with specific clusters. For our case, the ZED needs to find endpoints containing both the On/Off and Groups cluster servers.

      zdoCBReq.has_deviceAnnounce = true;
      zdoCBReq.deviceAnnounce = true;
      zdoCBReq.has_matchDescRsp = true;
      zdoCBReq.matchDescRsp = true;
    

    in SetupZStackCallbacks of zcl_samplesw.c

      case zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE:
      // ...
      {
        // added for SLA
        zstackmsg_zdoDeviceAnnounceInd_t *pInd;
        pInd = (zstackmsg_zdoDeviceAnnounceInd_t *) pMsg;
    
        // send a Match Descriptor request
        zstack_zdoMatchDescReq_t req;
        memset(&req, 0x00, sizeof(req));
    
        uint16_t inClusterIDs[] = {ZCL_CLUSTER_ID_GENERAL_ON_OFF, ZCL_CLUSTER_ID_GENERAL_GROUPS};
    
        req.dstAddr = pInd->req.srcAddr;
        req.nwkAddrOfInterest = pInd->req.devAddr;
        req.n_inputClusters = 2;
        req.pInputClusters = inClusterIDs;
        req.n_outputClusters = 0;
        req.profileID = ZCL_HA_PROFILE_ID;
    
        Zstackapi_ZdoMatchDescReq(appServiceTaskId, &req);
        // end added
      }
      break;
    

    in zclSampleSw_ProcessCommissioningStatus of zcl_samplesw.c

      case zstackmsg_CmdIDs_ZDO_MATCH_DESC_RSP:
      // ...
      {
          zstackmsg_zdoMatchDescRspInd_t  *pInd = (zstackmsg_zdoMatchDescRspInd_t *) pMsg;
    
          afAddrType_t dstAddr;
          memset(&dstAddr, 0x00, sizeof(afAddrType_t));
          dstAddr.addrMode = afAddr16Bit;
          dstAddr.addr.shortAddr = pInd->rsp.nwkAddrOfInterest;
          uint8_t i;
          for (i = 0; i < pInd->rsp.n_matchList; i++)
          {
              dstAddr.endPoint = pInd->rsp.pMatchList[i];
    
              zstack_getZCLFrameCounterRsp_t Rsp;
              Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &Rsp);
    
              zclGeneral_SendGroupAdd(SAMPLESW_ENDPOINT, &dstAddr, GROUP_ID, GROUP_NAME, false, Rsp.zclFrameCounter);
          }
      }
        break;
    

    in zclSampleSw_processZStackMsgs of zcl_samplesw.c

    Note

    For the previous two code additions, you may see code in various #if defined blocks. That code is provided to enable other Zigbee features and will not be used for this SLA.

      // set up default application BDB commissioning modes based on build type
      #if ZG_BUILD_COORDINATOR_TYPE
      //By default, Coordiantor has Formation selected in the CUI menu
      //#define DEFAULT_COMISSIONING_MODE (BDB_COMMISSIONING_MODE_NWK_STEERING | BDB_COMMISSIONING_MODE_NWK_FORMATION | BDB_COMMISSIONING_MODE_FINDING_BINDING)
      #define DEFAULT_COMISSIONING_MODE (BDB_COMMISSIONING_MODE_NWK_STEERING | BDB_COMMISSIONING_MODE_NWK_FORMATION) // changed for SLA
      #else
      //By default, joining devices such as Router and ZED do not have formation selected. It can be enabled in the Common User Interface (CUI) if needed.
      #define DEFAULT_COMISSIONING_MODE (BDB_COMMISSIONING_MODE_NWK_STEERING | BDB_COMMISSIONING_MODE_FINDING_BINDING)
      #endif
    

    in zstack.h

      if(key == CONFIG_BTN_RIGHT)
      {
          zstack_getZCLFrameCounterRsp_t rsp;
    
          Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &rsp);
          // zclGeneral_SendOnOff_CmdToggle( SAMPLESW_ENDPOINT, &zclSampleSw_DstAddr, FALSE, rsp.zclFrameCounter );
    
          // added for SLA
    
          // send a toggle
          afAddrType_t lightGroupAddr;
          lightGroupAddr.addrMode = (afAddrMode_t)AddrGroup;
          lightGroupAddr.addr.shortAddr = GROUP_ID;
          zclGeneral_SendOnOff_CmdToggle( SAMPLESW_ENDPOINT, &lightGroupAddr, FALSE, rsp.zclFrameCounter );
    
          // end added
      }
    

    in zclSampleSw_processKey of zcl_samplesw.c

  3. Build both projects, then flash the LaunchPads. One LaunchPad will run the zc_sw, and the other two will run the zr_light.

    From the sniffer logs, you should see the group commands and the group addressed toggle commands, similar to the following:

Task 2: Using the Scenes Cluster with the Groups Cluster

In Zigbee, a scene is a collection of stored values for attributes, as defined by a scene table entry. The Scenes cluster offers commands for adding and recalling scenes (among others).

Since the Scenes cluster is typically used alongside the Groups cluster, we will continue development from Task 1.

  1. Modify the zc_sw.

       const cId_t zclSampleSw_OutClusterList[] =
       {
         ZCL_CLUSTER_ID_GENERAL_IDENTIFY,
         ZCL_CLUSTER_ID_GENERAL_ON_OFF,
         ZCL_CLUSTER_ID_GENERAL_GROUPS,
         ZCL_CLUSTER_ID_GENERAL_SCENES, // added for SLA
       #if defined (OTA_CLIENT_INTEGRATED)
         ZCL_CLUSTER_ID_OTA
       #endif  
       };
    

    in zcl_samplesw_data.c

    Also, confirm that ZCL_SCENES is in the predefined symbols for the zed_sw.

       #define SCENE_ID   0x0007
       static zclGeneral_Scene_t nightScene =
       {
        .groupID = GROUP_ID,
        .ID = SCENE_ID,
        .name = { 0x08, 'm', 'y', '_', 's', 'c', 'e', 'n', 'e' },
        .extLen = 4,
        .extField = {
                     // extension field set 1
                     // cluster ID
                     (ZCL_CLUSTER_ID_GENERAL_ON_OFF & 0xFF), ((ZCL_CLUSTER_ID_GENERAL_ON_OFF >> 8) & 0xFF),
                     // length of extension field
                     1,
                     // attribute value (eg. for OnOff attribute of On/Off cluster)
                     0,
                    },
       };
    

    in zcl_samplesw.c

       case zstackmsg_CmdIDs_ZDO_MATCH_DESC_RSP:
       // ...
       {
           zstackmsg_zdoMatchDescRspInd_t  *pInd = (zstackmsg_zdoMatchDescRspInd_t *) pMsg;
    
           afAddrType_t dstAddr;
           memset(&dstAddr, 0x00, sizeof(afAddrType_t));
           dstAddr.addrMode = afAddr16Bit;
           dstAddr.addr.shortAddr = pInd->rsp.nwkAddrOfInterest;
           uint8_t i;
           for (i = 0; i < pInd->rsp.n_matchList; i++)
           {
               dstAddr.endPoint = pInd->rsp.pMatchList[i];
    
               zstack_getZCLFrameCounterRsp_t Rsp;
               Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &Rsp);
    
               zclGeneral_SendGroupAdd(SAMPLESW_ENDPOINT, &dstAddr, GROUP_ID, GROUP_NAME, false, Rsp.zclFrameCounter);
    
               Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &Rsp);
               zclGeneral_SendAddScene(SAMPLESW_ENDPOINT, &dstAddr, &nightScene, FALSE, Rsp.zclFrameCounter);
           }
       }
           break;
    

    in zclSampleSw_processZStackMsgs of zcl_samplesw.c

     if(key == CONFIG_BTN_RIGHT)
     {
         zstack_getZCLFrameCounterRsp_t rsp;
         Zstackapi_getZCLFrameCounterReq(appServiceTaskId, &rsp);
    
         // send a group addressed scene recall command
         afAddrType_t lightGroupAddr;
         lightGroupAddr.addrMode = (afAddrMode_t)AddrGroup;
         lightGroupAddr.addr.shortAddr = GROUP_ID;
         zclGeneral_SendSceneRecall(SAMPLESW_ENDPOINT, &lightGroupAddr, GROUP_ID, SCENE_ID, TRUE, rsp.zclFrameCounter);
     }
    

    in zclSampleSw_processKey of zcl_samplesw.c

  2. In the zc_light:

    The Scenes Recall callback is used to notify the application that a Scene Recall command has been received. The application can then set the attributes to the values contained in that Scene. Additionally, Scene attributes indicating the recently recalled scene are updated.

    Scenes cluster callbacks:

      #ifdef ZCL_SCENES
        NULL,                                  // Scene Store Request command
        zclSampleLight_SceneRecallCB,          // Scene Recall Request command
        NULL,                                  // Scene Response command
      #endif
    

    in zclSampleLight_CmdCallbacks zcl_samplelight.c


    Handles the Scene Recall callback:

      static void zclSampleLight_SceneRecallCB( zclSceneReq_t *pReq )
      {
          zclGeneral_Scene_extField_t extField;
          uint8_t *pBuf;
          uint8_t extLen = 0;
    
          pBuf = pReq->scene->extField;
          extField.AttrLen = pBuf[2];
    
           while(extLen < ZCL_GENERAL_SCENE_EXT_LEN)
           {
               //Parse ExtField
               extField.ClusterID = BUILD_UINT16(pBuf[0],pBuf[1]);
               extField.AttrLen = pBuf[2];
               extField.AttrBuf = &pBuf[3];
    
               if(extField.AttrLen == 0xFF || extField.ClusterID == 0xFFFF)
               {
                   break;
               }
    
               //If On/Off then retrieve the attribute
               if(extField.ClusterID == ZCL_CLUSTER_ID_GENERAL_ON_OFF)
               {
                   uint8_t tempState = *extField.AttrBuf;
                   zclSampleLight_updateOnOffAttribute(tempState);
               }
      #ifdef ZCL_LVL_CTRL
               //If Level Control then retrieve the attribute
               else if(extField.ClusterID == ZCL_CLUSTER_ID_GENERAL_LEVEL_CONTROL)
               {
                   uint8_t tempState = *extField.AttrBuf;
                   zclSampleLight_updateCurrentLevelAttribute(tempState);
               }
      #endif
               //Move to the next extension field (increase pointer by clusterId, Attribute len field, and attribute)
               pBuf += sizeof(uint16_t) + sizeof(uint8_t) + extField.AttrLen;
               extLen += sizeof(uint16_t) + sizeof(uint8_t) + extField.AttrLen;  //increase ExtField
           }
    
          //Update scene attributes
          zclSampleLight_ScenesValid = TRUE;
          zclSampleLight_ScenesCurrentGroup = pReq->scene->groupID;
          zclSampleLight_ScenesCurrentScene = pReq->scene->ID;
    
          zclSampleLight_UpdateLedState();
    
      }
    

    in zcl_samplelight.c

      // Scenes Cluster
      uint8_t  zclSampleLight_ScenesCurrentScene = 0;
      uint16_t zclSampleLight_ScenesCurrentGroup = 0;
      uint8_t  zclSampleLight_ScenesValid = 0;
      uint8_t  zclSampleLight_ScenesNameSupport = 1;
    

    in zcl_samplelight_data.c

      // *** Scene Cluster Attributes ***
      {
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        { // Attribute record
          ATTRID_SCENES_COUNT,
          ZCL_DATATYPE_UINT8,
          ACCESS_CONTROL_READ,
          NULL // Use application's callback to Read this attribute
        }
      },
      {
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        { // Attribute record
          ATTRID_SCENES_CURRENT_SCENE,
          ZCL_DATATYPE_UINT8,
          ACCESS_CONTROL_READ,
          (void *)&zclSampleLight_ScenesCurrentScene
        }
      },
      {
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        { // Attribute record
          ATTRID_SCENES_CURRENT_GROUP,
          ZCL_DATATYPE_UINT16,
          ACCESS_CONTROL_READ,
          (void *)&zclSampleLight_ScenesCurrentGroup
        }
      },
      {
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        { // Attribute record
          ATTRID_SCENES_SCENE_VALID,
          ZCL_DATATYPE_BOOLEAN,
          ACCESS_CONTROL_READ,
          (void *)&zclSampleLight_ScenesValid
        }
      },
      {
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        { // Attribute record
          ATTRID_SCENES_NAME_SUPPORT,
          ZCL_DATATYPE_BITMAP8,
          ACCESS_CONTROL_READ,
          (void *)&zclSampleLight_ScenesNameSupport
        }
      },
      {
        ZCL_CLUSTER_ID_GENERAL_SCENES,
        {  // Attribute record
          ATTRID_CLUSTER_REVISION,
          ZCL_DATATYPE_UINT16,
          ACCESS_CONTROL_READ,
          (void *)&zclSampleLight_scenes_clusterRevision
        }
      },
    

    in zclSampleLight_Attrs of zcl_samplelight_data.c

      // Scenes attributes
      extern uint8_t  zclSampleLight_ScenesCurrentScene;
      extern uint16_t zclSampleLight_ScenesCurrentGroup;
      extern uint8_t  zclSampleLight_ScenesValid;
      extern uint8_t  zclSampleLight_ScenesNameSupport;
    

    in zcl_samplight.h

      if(key == CONFIG_BTN_RIGHT)
      {
        zclSampleLight_updateOnOffAttribute(LIGHT_ON);
        zclSampleLight_UpdateLedState();
      }
    

    in zclSampleLight_processKey of zcl_samplelight.c

  3. Flash these projects on the respective LaunchPads.

    • Press BTN-1 (the left button) on the zc_sw to form/open the network.
    • Press BTN-1 on each zr_sw to join the network.
    • Press BTN-2 (the right button) on the zc_sw to send a group addressed Scene Recall command.
    • Press BTN-2 on each zr_light to turn on their red LED. From the sniffer logs, you should see the Scene commands, similar to the following:

Please refer to the Groups and Scenes clusters in the Zigbee Cluster Library documentation for further information.

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