Introduction

Z-Stack is a component of the SimpleLink CC13x2 / CC26x2 Software Development Kit and is a complete Zigbee 3.0 software solution.

The intention of this lab is to help developers create their own custom Zigbee devices using Z-Stack. Topics that we will cover in this lab include:

  1. Choosing a certifiable Zigbee device type from the Zigbee device type specifications
  2. Gathering the required information about your chosen Zigbee device from the Zigbee documentation
  3. Modifying the sample applications provided in the SimpleLink SDK to suit the needs of your chosen Zigbee device

Note: This lab is intended to be a starting point for development, it does not go into details about how to test and verify the final product.

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

Zigbee Documentation

  • 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

Gathering information from Zigbee documentation

Before we jump into the software, our first task is to determine which type of Zigbee device we wish to create. Then we must determine which clusters that device must support and which attributes those clusters must support.

1. Choosing a Zigbee device type

There are currently two specifications we can refer to for choosing a Zigbee device type, the Zigbee Lighting and Occupancy Device Specification (ZLO spec) and the Zigbee Home Automation Public Application Profile (ZHA spec).

The first thing I would recommend doing is going to the "Device Descriptions" sections of both documents, Section 5 in the ZLO spec and Section 5.7 in the ZHA spec. At first glance you will notice that there is some overlap between these two specifications, namely Device ID 0x0100 through 0x0107. If you are creating one of these Zigbee Lighting devices, refer to the information in the ZLO spec instead of the ZHA spec. The ZLO spec is the newest device specification document that has been released by the Zigbee Alliance, so the information in this document is newer and supersedes anything regarding these devices types in the ZHA spec. For all other non-overlapping device types between these two documents, the current instructions from the Zigbee Alliance are to follow the guidelines in each corresponding document for each device type.

So, let's say we want to create a Smart Plug as our Zigbee device. In Section 5.7 of the ZHA spec, we can see in Table 5.1 below that this device has Device ID 0x0051.

2. Determining which clusters our device support

In the same document that we chose our Zigbee device out of, we can navigate to the section that defines what clusters our device supports. In our case, we need go to Section 7.4.16 of the ZHA spec for Smart Plug. From here we can determine which clusters our device supports under the subsection 'Supported Clusters'.

Here are all the important points from the images below:

  • Mandatory and Optional Clusters

    • All Zigbee devices must implement their mandatory clusters to pass Zigbee device certification. Zigbee device manufacturers may choose to implement certain optional clusters for their own application needs.

    Note

    • If you choose to implement an optional cluster, even though it is marked as optional, you must still pass certification for that cluster if you wish to include it with your device.
  • Server Side and Client Side cluster implementation
    • Implementing the Server vs. the Client side of a cluster is very important because it determines which attributes you support.

  • Common Clusters for all Zigbee devices
    • All Zigbee devices support a common set of mandatory and optional clusters, listed below in table 7.1. We can see in section 7.4.16.1 below that it mentions "In addition to those specified in Table 7.1"

Now, using the information from the tables above, let's make new table that shows what clusters our Smart Plug device is going to support. I will only select the mandatory clusters for this example.

Cluster ID Cluster Name Client/Server Side
0x0000 Basic Server
0x0003 Identify Client + Server
0x0702 Metering Server
0x0006 On/Off Server

3. Determining which attributes our clusters support

Now that we have a list of clusters, we can refer to the Zigbee Cluster Library v7 Specification (ZCL spec). The ZCL spec will tell us which attributes our selected clusters support. Each section of the ZCL spec will give us a cluster and tell us which attributes the Server Side and Client Side implementations of that cluster support.

As of the ZCL v6 spec, it is mandatory for every cluster to support the ClusterRevision attribute.

Let's start with the Basic Cluster. From the table we made in the previous section, we must support the Basic Cluster Server. Table 3-7 in Section 3.2.2.2 of the ZCL spec tells us which attributes the Basic Cluster Server supports.

Next we can find the Identify Cluster, for which we must support both Client and Server. Table 3-28 in section 3.5.2.2 of the ZCL spec tells us which attributes the Identify Cluster Server supports. Section 3.5.2.1 of the ZCL spec tells us that the Identify Cluster Client does not support any cluster specific attributes.

Next is the Metering Cluster, for which we must support the Metering Cluster Server. The Metering Cluster Server has a very elaborate list of attributes. In fact, so elaborate that it is broken up into 9 different attribute sets, which you can see in section 10.4.2.2 of the ZCL spec. For the purpose of this exercise, I am only going to include snippits from the tables in this section that include mandatory attributes for the Metering Cluster Server.

Lastly is the On/Off Cluster, for which we must support the On/Off Cluster Server. Table 3-45 in section 3.8.2.2 of the ZCL spec tells us which attributes the On/Off Cluster Server supports.

Once again, I will create a new table using the information from the tables above that shows us which attributes our Smart Plug device is going to support. I will only select mandatory attributes for this example.

Attribute Cluster Data Type Access Default Value
ZCLVersion Basic uint8 Read 2
PowerSource Basic enum8 Read 0
ClusterRevision Basic uint16 Read 1
IdentifyTime Identify uint16 Read/Write 0
ClusterRevision Identify uint16 Read 1
CurrentSummationDelivered Metering uint48 Read 0
Status Metering map8 Read 0
UnitofMeasure Metering enum8 Read 0
SummationFormatting Metering map8 Read 0
MeteringDeviceType Metering map8 Read 0
ClusterRevision Metering uint16 Read 1
OnOff On/Off bool Read/Reportable 0
ClusterRevision On/Off uint16 Read 1

Reportable Attributes

  • If any of the attributes you are supporting have an access type of Reportable, it is mandatory for your device to support ZCL reporting which means your device will need to include the compile flag BDB_REPORTING which enables ZCL report sending capabilities. We will revisit this again later on in the lab.

Modifying the GenericApp project

It's finally time to start modifying the software to suit our needs. The first thing you will want to do is make a copy of the GenericApp project so we can preserve the original project. You can choose to do this with either a Coordinator, Router, or End Device project. For my example I will use a Router. You can do this by simply making a copy of the the project folder at this path:

C:\ti\simplelink_cc13x2_26x2_sdk_<version>\examples\rtos\<LaunchPad>\zstack\zr_genericapp

1. Importing the project into Code Composer Studio

Now we are ready to import the project into Code Composer Studio (CCS). Open CCS and go to File > Import > C/C++ Project > CCS Project and then browse for the zr_genericapp project path under Select search-directory:

At this point it is a good idea to try building the project just to make sure all the file paths resolve correctly, this will verify that we have a good starting point for our incoming code modifications.

2. Add new compile flags to the project

All of the ZCL source code files are included in the GenericApp project by default, but the functionality contained in each file is compiled out via compile flags. Using the table below, determine which compile flags you must add to your project based on the clusters listed in SLA Table 1.

Cluster ID Cluster Name Compile Flags
0x0000 ZCL_CLUSTER_ID_GEN_BASIC ZCL_BASIC
0x0001 ZCL_CLUSTER_ID_GEN_POWER_CFG N/A
0x0002 ZCL_CLUSTER_ID_GEN_DEVICE_TEMP_CONFIG N/A
0x0003 ZCL_CLUSTER_ID_GEN_IDENTIFY ZCL_IDENTIFY
0x0004 ZCL_CLUSTER_ID_GEN_GROUPS ZCL_GROUPS
0x0005 ZCL_CLUSTER_ID_GEN_SCENES ZCL_SCENES
0x0006 ZCL_CLUSTER_ID_GEN_ON_OFF ZCL_ON_OFF
0x0007 ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG N/A
0x0008 ZCL_CLUSTER_ID_GEN_LEVEL_CONTROL ZCL_LEVEL_CTRL
0x0009 ZCL_CLUSTER_ID_GEN_ALARMS ZCL_ALARMS
0x000A ZCL_CLUSTER_ID_GEN_TIME N/A
0x000B ZCL_CLUSTER_ID_GEN_LOCATION ZCL_LOCATION
0x000C ZCL_CLUSTER_ID_GEN_ANALOG_INPUT_BASIC N/A
0x000D ZCL_CLUSTER_ID_GEN_ANALOG_OUTPUT_BASIC N/A
0x000E ZCL_CLUSTER_ID_GEN_ANALOG_VALUE_BASIC N/A
0x000F ZCL_CLUSTER_ID_GEN_BINARY_INPUT_BASIC N/A
0x0010 ZCL_CLUSTER_ID_GEN_BINARY_OUTPUT_BASIC N/A
0x0011 ZCL_CLUSTER_ID_GEN_BINARY_VALUE_BASIC N/A
0x0012 ZCL_CLUSTER_ID_GEN_MULTISTATE_INPUT_BASIC N/A
0x0013 ZCL_CLUSTER_ID_GEN_MULTISTATE_OUTPUT_BASIC N/A
0x0014 ZCL_CLUSTER_ID_GEN_MULTISTATE_VALUE_BASIC N/A
0x0015 ZCL_CLUSTER_ID_GEN_COMMISSIONING N/A
0x0016 ZCL_CLUSTER_ID_GEN_PARTITION N/A
0x0019 ZCL_CLUSTER_ID_OTA OTA_CLIENT_CC26XX
0x001A ZCL_CLUSTER_ID_GEN_POWER_PROFILE N/A
0x001B ZCL_CLUSTER_ID_GEN_APPLIANCE_CONTROL N/A
0x0020 ZCL_CLUSTER_ID_GEN_POLL_CONTROL N/A
0x0021 ZCL_CLUSTER_ID_GREEN_POWER N/A
0x0022 ZCL_CLUSTER_ID_MOBILE_DEVICE_CONFIGURATION N/A
0x0023 ZCL_CLUSTER_ID_NEIGHBOR_CLEANING N/A
0x0024 ZCL_CLUSTER_ID_NEAREST_GATEWAY N/A
0x0100 ZCL_CLUSTER_ID_CLOSURES_SHADE_CONFIG N/A
0x0101 ZCL_CLUSTER_ID_CLOSURES_DOOR_LOCK ZCL_DOORLOCK
0x0102 ZCL_CLUSTER_ID_CLOSURES_WINDOW_COVERING ZCL_WINDOWCOVERING
0x0200 ZCL_CLUSTER_ID_HVAC_PUMP_CONFIG_CONTROL ZCL_HVAC_CLUSTER
0x0201 ZCL_CLUSTER_ID_HVAC_THERMOSTAT ZCL_HVAC_CLUSTER
0x0202 ZCL_CLUSTER_ID_HVAC_FAN_CONTROL ZCL_HVAC_CLUSTER
0x0203 ZCL_CLUSTER_ID_HVAC_DIHUMIDIFICATION_CONTROL ZCL_HVAC_CLUSTER
0x0204 ZCL_CLUSTER_ID_HVAC_USER_INTERFACE_CONFIG ZCL_HVAC_CLUSTER
0x0300 ZCL_CLUSTER_ID_LIGHTING_COLOR_CONTROL ZCL_LIGHT_LINK_ENHANCE
0x0301 ZCL_CLUSTER_ID_LIGHTING_BALLAST_CONFIG ZCL_LIGHT_LINK_ENHANCE
0x0400 ZCL_CLUSTER_ID_MS_ILLUMINANCE_MEASUREMENT N/A
0x0401 ZCL_CLUSTER_ID_MS_ILLUMINANCE_LEVEL_SENSING_CONFIG N/A
0x0402 ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT N/A
0x0403 ZCL_CLUSTER_ID_MS_PRESSURE_MEASUREMENT N/A
0x0404 ZCL_CLUSTER_ID_MS_FLOW_MEASUREMENT N/A
0x0405 ZCL_CLUSTER_ID_MS_RELATIVE_HUMIDITY N/A
0x0406 ZCL_CLUSTER_ID_MS_OCCUPANCY_SENSING N/A
0x0500 ZCL_CLUSTER_ID_SS_IAS_ZONE ZCL_ZONE
0x0501 ZCL_CLUSTER_ID_SS_IAS_ACE ZCL_ACE
0x0502 ZCL_CLUSTER_ID_SS_IAS_WD ZCL_WD
0x0600 ZCL_CLUSTER_ID_PI_GENERIC_TUNNEL N/A
0x0601 ZCL_CLUSTER_ID_PI_BACNET_PROTOCOL_TUNNEL N/A
0x0602 ZCL_CLUSTER_ID_PI_ANALOG_INPUT_BACNET_REG N/A
0x0603 ZCL_CLUSTER_ID_PI_ANALOG_INPUT_BACNET_EXT N/A
0x0604 ZCL_CLUSTER_ID_PI_ANALOG_OUTPUT_BACNET_REG N/A
0x0605 ZCL_CLUSTER_ID_PI_ANALOG_OUTPUT_BACNET_EXT N/A
0x0606 ZCL_CLUSTER_ID_PI_ANALOG_VALUE_BACNET_REG N/A
0x0607 ZCL_CLUSTER_ID_PI_ANALOG_VALUE_BACNET_EXT N/A
0x0608 ZCL_CLUSTER_ID_PI_BINARY_INPUT_BACNET_REG N/A
0x0609 ZCL_CLUSTER_ID_PI_BINARY_INPUT_BACNET_EXT N/A
0x060A ZCL_CLUSTER_ID_PI_BINARY_OUTPUT_BACNET_REG N/A
0x060B ZCL_CLUSTER_ID_PI_BINARY_OUTPUT_BACNET_EXT N/A
0x060C ZCL_CLUSTER_ID_PI_BINARY_VALUE_BACNET_REG N/A
0x060D ZCL_CLUSTER_ID_PI_BINARY_VALUE_BACNET_EXT N/A
0x060E ZCL_CLUSTER_ID_PI_MULTISTATE_INPUT_BACNET_REG N/A
0x060F ZCL_CLUSTER_ID_PI_MULTISTATE_INPUT_BACNET_EXT N/A
0x0610 ZCL_CLUSTER_ID_PI_MULTISTATE_OUTPUT_BACNET_REG N/A
0x0611 ZCL_CLUSTER_ID_PI_MULTISTATE_OUTPUT_BACNET_EXT N/A
0x0612 ZCL_CLUSTER_ID_PI_MULTISTATE_VALUE_BACNET_REG N/A
0x0613 ZCL_CLUSTER_ID_PI_MULTISTATE_VALUE_BACNET_EXT N/A
0x0614 ZCL_CLUSTER_ID_PI_11073_PROTOCOL_TUNNEL N/A
0x0615 ZCL_CLUSTER_ID_PI_ISO7818_PROTOCOL_TUNNEL N/A
0x0617 ZCL_CLUSTER_ID_PI_RETAIL_TUNNEL N/A
0x0700 ZCL_CLUSTER_ID_SE_PRICE ZCL_SE_PRICE_SERVER, ZCL_SE_PRICE_CLIENT
0x0701 ZCL_CLUSTER_ID_SE_DRLC ZCL_SE_DRLC_SERVER, ZCL_SE_DRLC_CLIENT
0x0702 ZCL_CLUSTER_ID_SE_METERING ZCL_SE_METERING_SERVER, ZCL_SE_METERING_CLIENT
0x0703 ZCL_CLUSTER_ID_SE_MESSAGING ZCL_SE_MESSAGING_SERVER, ZCL_SE_MESSAGING_CLIENT
0x0704 ZCL_CLUSTER_ID_SE_TUNNELING ZCL_SE_TUNNELING_SERVER, ZCL_SE_TUNNELING_CLIENT
0x0705 ZCL_CLUSTER_ID_SE_PREPAYMENT ZCL_SE_PREPAYMENT_SERVER, ZCL_SE_PREPAYMENT_CLIENT
0x0706 ZCL_CLUSTER_ID_SE_ENERGY_MGMT ZCL_SE_ENERGY_MGMT_SERVER, ZCL_SE_ENERGY_MGMT_CLIENT
0x0707 ZCL_CLUSTER_ID_SE_CALENDAR ZCL_SE_CALENDAR_SERVER, ZCL_SE_CALENDAR_CLIENT
0x0708 ZCL_CLUSTER_ID_SE_DEVICE_MGMT ZCL_SE_DEVICE_MGMT_SERVER, ZCL_SE_DEVICE_MGMT_CLIENT
0x0709 ZCL_CLUSTER_ID_SE_EVENTS ZCL_SE_EVENTS_SERVER, ZCL_SE_EVENTS_CLIENT
0x070A ZCL_CLUSTER_ID_SE_MDU_PAIRING ZCL_SE_MDU_PAIRING_SERVER, ZCL_SE_MDU_PAIRING_CLIENT
0x0800 ZCL_CLUSTER_ID_SE_KEY_ESTABLISHMENT ZCL_KEY_ESTABLISH
0x0900 ZCL_CLUSTER_ID_TELECOMMUNICATIONS_INFORMATION N/A
0x0904 ZCL_CLUSTER_ID_TELECOMMUNICATIONS_CHATTING N/A
0x0905 ZCL_CLUSTER_ID_TELECOMMUNICATIONS_VOICE_OVER_ZIGBEE N/A
0x0B00 ZCL_CLUSTER_ID_HA_APPLIANCE_IDENTIFICATION ZCL_APPLIANCE_IDENTIFICATION
0x0B01 ZCL_CLUSTER_ID_HA_METER_IDENTIFICATION ZCL_METER_IDENTIFICATION
0x0B02 ZCL_CLUSTER_ID_HA_APPLIANCE_EVENTS_ALERTS ZCL_APPLIANCE_EVENTS_ALERTS
0x0B03 ZCL_CLUSTER_ID_HA_APPLIANCE_STATISTICS ZCL_APPLIANCE_STATISTICS
0x0B04 ZCL_CLUSTER_ID_HA_ELECTRICAL_MEASUREMENT ZCL_ELECTRICAL_MEASUREMENT
0x0B05 ZCL_CLUSTER_ID_HA_DIAGNOSTIC ZCL_DIAGNOSTIC
0x1000 ZCL_CLUSTER_ID_TOUCHLINK N/A

In addition to the compile flags in the table above, we must also consider if any of our attributes have an access type of 'Reportable', which is information that we recorded in SLA Table 2. If we have an attribute that is 'Reportable', we must include the compile flag BDB_REPORTING in our project.

For our Smart Plug device, we will need to add the compile flags ZCL_ON_OFF, ZCL_SE, ZCL_SE_METERING_SERVER, and BDB_REPORTING. We can add them to the compilation by selecting our project in CCS and going to Project > Properties > Build > ARM Compiler > Predefined Symbols > Pre-define NAME and adding the the flags, like in the image below:

ZCL_ON_OFF
ZCL_SE
ZCL_SE_METERING_SERVER
BDB_REPORTING

zrgenericapp[DEVICE]_LAUNCHXL_tirtos_ccs.projectspec

3. Making code changes to suit our selected Zigbee device

Here is a summarized list of code changes that we need to make to GenericApp to have it suit our needs:

  1. Add ZCL header file(s) to zcl_genericapp_data.c and zcl_genericapp.c

  2. Include ZCL Smart Energy files in the project

  3. Add attributes to zclGenericApp_Attrs in zcl_genericapp_data.c

  4. Add Server Side clusters to zclGenericApp_InClusterList in zcl_genericapp_data.c

  5. Add Client Side clusters to zclGenericApp_OutClusterList in zcl_genericapp_data.c

  6. Update the Simple Descriptor with the appropriate Zigbee device type in zcl_genericapp_data.c

  7. Update the attribute reset function zclGenericApp_ResetAttributesToDefaultValues to include the new attributes in zcl_genericapp_data.c

  8. Register for, declare, and implement ZCL command callback functions in zcl_genericapp.c

Add ZCL header files

Add the header files for the ZCL files that we added in the step 1 of this section to both zcl_genericapp_data.c and zcl_genericapp.c

#include "zcl_se.h"

zcl_genericapp_data.c and zcl_genericapp.c

Include ZCL Smart Energy files

Inside the Common/zcl folder, right-click on zcl_se.c/h and uncheck the Exclude from build option to add the Smart Energy Zigbee Cluster Library to your project.

Add attributes

By default, all of the mandatory attributes for the Basic Server and Identify Server are included, and a number of optional attributes for the Basic Server are included as well. In this section we will only need to address the attributes for the Metering Server and On/Off Server.

First we will want to make new global variables for each of our attributes. Using SLA Table 2, we can make a new global variable in zcl_genericapp_data.c for each of these attributes. Use your best judgement for the data types of each variable. For instance, if an attribute is a ZCL uint48, obviously this is not a real data type on our embedded system so we will need to round up to a uint64_t.

Note

  • Since ClusterRevision is a global cluster attribute, we only need one instance of the global attribute variable that every cluster can share.
#ifdef ZCL_SE_METERING_SERVER
uint64_t zclGenericApp_CurrentSummationDelivered;
uint8_t zclGenericApp_Metering_Status;
uint8_t zclGenericApp_UnitofMeasure;
uint8_t zclGenericApp_SummationFormatting;
uint8_t zclGenericApp_MeteringDeviceType;
#endif // ZCL_SE_METERING_SERVER
#ifdef ZCL_ON_OFF
uint8_t zclGenericApp_OnOff;
#endif // ZCL_ON_OFF

The attribute table zclGenericApp_Attrs is located in zcl_genericapp_data.c. Each attribute entry in zclGenericApp_Attrs is formatted as such:

  {
    ZCL_CLUSTER_ID_GEN_BASIC,             // Cluster IDs - defined in the foundation (ie. zcl.h)
    {  // Attribute record
      ATTRID_BASIC_HW_VERSION,            // Attribute ID - Found in Cluster Library header (ie. zcl_general.h)
      ZCL_DATATYPE_UINT8,                 // Data Type - found in zcl.h
      ACCESS_CONTROL_READ,                // Variable access control - found in zcl.h
      (void *)&zclGenericApp_HWRevision   // Pointer to attribute variable
    }
  },

zclGenericApp_Attrs[]

Using the information in SLA Table 2, we can create our own attribute entries in the attribute table. For this step, you will need to look through all the corresponding ZCL header files to locate the correct Cluster ID, Attribute ID, Data Type, and Variable Access Control Type for each attribute. The ZCL header files should already have all of this information defined, you just need to find it and utilize it.

const uint16_t zclSampleLight_metering_clusterRevision = 0x0001;
const uint16_t zclSampleLight_onoff_clusterRevision = 0x0001;

CONST zclAttrRec_t zclGenericApp_Attrs[] =
{
  ...
  // new clusters go after what's already on the list
#ifdef ZCL_SE_METERING_SERVER
  // *** Smart Energy Metering Server Cluster Attributes ***
  {
   ZCL_CLUSTER_ID_SE_METERING,
    { // Attribute record
      ATTRID_SE_METERING_CURR_SUMM_DLVD,
      ZCL_DATATYPE_UINT48,
      ACCESS_CONTROL_READ,
      (void *)&zclGenericApp_CurrentSummationDelivered
    }
  },
  {
   ZCL_CLUSTER_ID_SE_METERING,
    { // Attribute record
      ATTRID_SE_METERING_STATUS,
      ZCL_DATATYPE_BITMAP8,
      ACCESS_CONTROL_READ,
      (void *)&zclGenericApp_Metering_Status
    }
  },
  {
   ZCL_CLUSTER_ID_SE_METERING,
    { // Attribute record
      ATTRID_SE_METERING_UOM,
      ZCL_DATATYPE_ENUM8,
      ACCESS_CONTROL_READ,
      (void *)&zclGenericApp_UnitofMeasure
    }
  },
  {
   ZCL_CLUSTER_ID_SE_METERING,
    { // Attribute record
      ATTRID_SE_METERING_SUMM_FMTG,
      ZCL_DATATYPE_BITMAP8,
      ACCESS_CONTROL_READ,
      (void *)&zclGenericApp_SummationFormatting
    }
  },
  {
   ZCL_CLUSTER_ID_SE_METERING,
    { // Attribute record
      ATTRID_SE_METERING_DEVICE_TYPE,
      ZCL_DATATYPE_BITMAP8,
      ACCESS_CONTROL_READ,
      (void *)&zclGenericApp_MeteringDeviceType
    }
  },
  {
   ZCL_CLUSTER_ID_SE_METERING,
    {  // Attribute record
      ATTRID_CLUSTER_REVISION,
      ZCL_DATATYPE_UINT16,
      ACCESS_CONTROL_READ,
      (void *)&zclSampleLight_metering_clusterRevision
    }
  },
#endif // ZCL_SE_METERING_SERVER
#ifdef ZCL_ON_OFF
  // *** On/Off Cluster Attributes ***
  {
    ZCL_CLUSTER_ID_GEN_ON_OFF,
    { // Attribute record
      ATTRID_ON_OFF,
      ZCL_DATATYPE_BOOLEAN,
      ACCESS_CONTROL_READ | ACCESS_REPORTABLE,
      (void *)&zclGenericApp_OnOff
    }
  },
  {
    ZCL_CLUSTER_ID_GEN_ON_OFF,
    {  // Attribute record
      ATTRID_CLUSTER_REVISION,
      ZCL_DATATYPE_UINT16,
      ACCESS_CONTROL_READ,
      (void *)&zclSampleLight_onoff_clusterRevision
    }
  },
#endif // ZCL_ON_OFF
};

zcl_genericapp_data.c

Add Server Side clusters

Next we must add the Server Side clusters we are implementing to the list zclGenericApp_InClusterList. For our Smart Plug example, we must add the Metering cluster and the On/Off cluster.

const cId_t zclGenericApp_InClusterList[] =
{
  ZCL_CLUSTER_ID_GEN_BASIC,
  ZCL_CLUSTER_ID_GEN_IDENTIFY,
  ZCL_CLUSTER_ID_SE_METERING,
  ZCL_CLUSTER_ID_GEN_ON_OFF
};

zclGenericApp_InClusterList[]

Add Client Side clusters

Next we must add the Client Side clusters we are implementing to the list zclGenericApp_OutClusterList. For our Smart Plug example, we will not need to add anything to this list since Basic and Identify are populated by default.

const cId_t zclGenericApp_OutClusterList[] =
{
  ZCL_CLUSTER_ID_GEN_BASIC,
  ZCL_CLUSTER_ID_GEN_IDENTIFY,
};

zclGenericApp_OutClusterList[]

Update the Simple Descriptor

The Simple Descriptor is used during network device commissioning to let other devices know what type of Zigbee device you are. You will need to update the Simple Descriptor zclGenericApp_SimpleDesc in zcl_genericapp_data.c with the appropriate Zigbee Device type.

SimpleDescriptionFormat_t zclGenericApp_SimpleDesc =
{
  GENERICAPP_ENDPOINT,                  //  int Endpoint;
  ZCL_HA_PROFILE_ID,                    //  uint16_t AppProfId;
  // Replaced ZCL_HA_DEVICEID_TEST_DEVICE with application specific device ID
  ZCL_HA_DEVICEID_SMART_PLUG,           //  uint16_t AppDeviceId; 
  GENERICAPP_DEVICE_VERSION,            //  int   AppDevVer:4;
  GENERICAPP_FLAGS,                     //  int   AppFlags:4;
  ZCLGENERICAPP_MAX_INCLUSTERS,         //  byte  AppNumInClusters;
  (cId_t *)zclGenericApp_InClusterList, //  byte *pAppInClusterList;
  ZCLGENERICAPP_MAX_OUTCLUSTERS,        //  byte  AppNumInClusters;
  (cId_t *)zclGenericApp_OutClusterList //  byte *pAppInClusterList;
};

zclGenericApp_SimpleDesc

Update the attribute reset function to include the new attributes

zclGenericApp_ResetAttributesToDefaultValues is responsible for initializing and resetting all the applications attributes to their default values. The default values can be obtained from SLA Table 2.

#define DEFAULT_ON_OFF_STATE           0x00
#define DEFAULT_CURR_SUMM_DLVD_VALUE   0
#define DEFAULT_METERING_STATUS        0
#define DEFAULT_UOM_VALUE              0
#define DEFAULT_SUMM_FMTG_VALUE        0
#define DEFAULT_METERING_DEVICE_TYPE   0

void zclGenericApp_ResetAttributesToDefaultValues(void)
{
  ...
#ifdef ZCL_SE_METERING_SERVER
  zclGenericApp_CurrentSummationDelivered = DEFAULT_CURR_SUMM_DLVD_VALUE;
  zclGenericApp_Metering_Status = DEFAULT_METERING_STATUS;
  zclGenericApp_UnitofMeasure = DEFAULT_UOM_VALUE;
  zclGenericApp_SummationFormatting = DEFAULT_SUMM_FMTG_VALUE;
  zclGenericApp_MeteringDeviceType = DEFAULT_METERING_DEVICE_TYPE;
#endif
#ifdef ZCL_ON_OFF
  zclGenericApp_OnOff = DEFAULT_ON_OFF_STATE;
#endif
}

zclGenericApp_ResetAttributesToDefaultValues()

Register for ZCL command callback functions

ZCL command callback functions are where your hardware-specific actions are performed in relation to the state of your cluster attributes. For instance, if you receive a Zigbee command from another device that changes the state of your On/Off Cluster "OnOff" attribute, you can receive a callback to your application saying that something happened, and at this point you would update your global attribute variable with the value passed into the callback function as a parameter and perform your hardware-specific action such as toggling a GPIO pin to reflect the new state of your attribute.

To receive these callback functions in your application, you must register a list of function pointers with each corresponding ZCL module. By default, we already do this with the ZCL General module in zclGenericApp_Init of zcl_genericapp.c, as shown below:

static void zclGenericApp_Init( void )
{
  ...

  // Register the ZCL General Cluster Library callback functions
  zclGeneral_RegisterCmdCallbacks( GENERICAPP_ENDPOINT, &zclGenericApp_CmdCallbacks );

  ...
}

zclGenericApp_Init()

The second parameter we pass into this function is the list of function pointers that we wish to register with the ZCL general module. For every function present on this list we will receive a callback in our application when the corresponding action has been triggered, and we can set any callback we do not wish to receive to NULL. By default, the Basic Cluster Reset callback function is registered with the ZCL general module with the function zclGenericApp_BasicResetCB.

To know which callback functions are available, you must look into the corresponding zcl<module>_AppCallbacks_t struct in the ZCL header files. Since the Basic, Identify, and On/Off clusters are all part of the ZCL General group (i.e. check Chapter 3 of the ZCL spec), all of their callback function descriptions are in the struct zclGeneral_AppCallbacks_t in zcl_general.h. For the Metering cluster, this is part of the ZCL Smart Energy group, so it is in the struct zclSE_AppCallbacks_t in zcl_se.h. Each function in these structs has a typedef associated with it so you can know how to implement the function prototype and definition in your application. For instance, for the Basic Cluster Reset callback, it has the type zclGCB_BasicReset_t in the struct zclGeneral_AppCallbacks_t, and we can see the typedef definition is as below:

typedef void (*zclGCB_BasicReset_t)( void );

And the in our application we can implement this function as such:

void zclGenericApp_BasicResetCB( void )

For the purpose of this lab, I am going to implement two callback functions in my application to show an example of how to do it. I will implement the On/Off cluster "OnOff" callback function and the Metering cluster "Get Profile" callback function.

As stated before, the On/Off cluster is part of the ZCL General group, so its callback is part of the zclGeneral_AppCallbacks_t zclGenericApp_CmdCallbacks, which I mentioned before is already defined in our application for the Basic Cluster Reset callback. To support the On/Off cluster "OnOff" callback:

#ifdef ZCL_ON_OFF
static void zclGenericApp_OnOffCB( uint8_t cmd );
#endif

static zclGeneral_AppCallbacks_t zclGenericApp_CmdCallbacks =
{
  zclGenericApp_BasicResetCB,             // Basic Cluster Reset command
  NULL,                                   // Identfiy cmd
  NULL,                                   // Identify Query command
  NULL,                                   // Identify Query Response command
  NULL,                                   // Identify Trigger Effect command
#ifdef ZCL_ON_OFF
  zclGenericApp_OnOffCB,                  // On/Off cluster commands
  NULL,                                   // On/Off cluster enhanced command Off with Effect
  NULL,                                   // On/Off cluster enhanced command On with Recall Global Scene
  NULL,                                   // On/Off cluster enhanced command On with Timed Off
#endif
#ifdef ZCL_LEVEL_CTRL
  NULL,                                   // Level Control Move to Level command
  NULL,                                   // Level Control Move command
  NULL,                                   // Level Control Step command
  NULL,                                   // Level Control Stop command
#endif
#ifdef ZCL_GROUPS
  NULL,                                   // Group Response commands
#endif
#ifdef ZCL_SCENES
  NULL,                                  // Scene Store Request command
  NULL,                                  // Scene Recall Request command
  NULL,                                  // Scene Response command
#endif
#ifdef ZCL_ALARMS
  NULL,                                  // Alarm (Response) commands
#endif
#ifdef SE_UK_EXT
  NULL,                                  // Get Event Log command
  NULL,                                  // Publish Event Log command
#endif
  NULL,                                  // RSSI Location command
  NULL                                   // RSSI Location Response command
};

#ifdef ZCL_ON_OFF
static void zclGenericApp_OnOffCB( uint8_t cmd )
{
  // do some stuff
}
#endif // ZCL_ON_OFF

The Metering cluster is part of the ZCL Smart Energy group, so we must define a new callback struct in our application with the type of zclSE_AppCallbacks_t. The ZCL Smart Energy cluster group is extremely elaborate, and because of this the zclSE_AppCallbacks_t is a bit different than the other "AppCallbacks" structs. Looking at its implementation, it is actually a struct of more structs, and each one of those other structs is full of callback functions for which we can register for. In addition to implementing these new structs, we must also register with the ZCL Smart Energy module in our initialization function (as we did with the ZCL General module). So, to do all of this, complete the following:

#ifdef ZCL_SE_METERING_SERVER
static void zclGenericApp_MeteringGetProfileCB( zclIncoming_t *pInMsg,
                                    zclSE_MeteringGetProfile_t *pCmd );
#endif

static const zclSE_MeteringServerCBs_t zclGenericApp_MeteringServerCBs =
{
  zclGenericApp_MeteringGetProfileCB,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL
};

static zclSE_AppCallbacks_t zclGenericApp_SECmdCallbacks =
{
  NULL,               // zclSE_DRLC_ServerCBs_t
  NULL,               // zclSE_DRLC_ClientCBs_t
  &zclGenericApp_MeteringServerCBs,               // zclSE_MeteringServerCBs_t
  NULL,               // zclSE_MeteringClientCBs_t
  NULL,               // zclSE_PriceServerCBs_t
  NULL,               // zclSE_PriceClientCBs_t
  NULL,               // zclSE_MessagingServerCBs_t
  NULL,               // zclSE_MessagingClientCBs_t
  NULL,               // zclSE_TunnelingServerCBs_t
  NULL,               // zclSE_TunnelingClientCBs_t
  NULL,               // zclSE_PrepaymentServerCBs_t
  NULL,               // zclSE_PrepaymentClientCBs_t
  NULL,               // zclSE_EnergyMgmtServerCBs_t
  NULL,               // zclSE_EnergyMgmtClientCBs_t
  NULL,               // zclSE_CalendarServerCBs_t
  NULL,               // zclSE_CalendarClientCBs_t
  NULL,               // zclSE_DeviceMgmtServerCBs_t
  NULL,               // zclSE_DeviceMgmtClientCBs_t
  NULL,               // zclSE_EventsServerCBs_t
  NULL,               // zclSE_EventsClientCBs_t
  NULL,               // zclSE_MDUPairingServerCBs_t
  NULL                // zclSE_MDUPairingClientCBs_t
};

static void zclGenericApp_Init( void )
{
 ...

#ifdef ZCL_SE_METERING_SERVER
  zclSE_RegisterCmdCallbacks( GENERICAPP_ENDPOINT, &zclGenericApp_SECmdCallbacks );
#endif

 ...
}

#ifdef ZCL_SE_METERING_SERVER
static void zclGenericApp_MeteringGetProfileCB( zclIncoming_t *pInMsg,
                                      zclSE_MeteringGetProfile_t *pCmd )
{
  // do some stuff
}
#endif // ZCL_SE_METERING_SERVER

Implement the ZCL command callback functions

As mentioned before, the ZCL command callback functions are where you, the developer, must implement your hardware-specific functionality in your application code based on the high-level intended action of each ZCL command. This is the part of the code where, aside from provided HAL drivers, there is no specific framework for how you should implement your actions since it is highly dependent on your custom hardware design. One thing you must do, however, is update the state of your global attribute(s) in these callback functions (if applicable).

As an example, in the On/Off cluster "OnOff" Callback, you would do at least 2 things:

  1. Update the state of your global "OnOff" attribute based on the function parameter of the callback function.
  2. Perform a hardware-specific action that actually changes the state of your hardware light, such as toggling a GPIO pin.

References

What's New in Zigbee 3.0: http://ti.com/lit/pdf/swra615

CC13X2/CC26X2 TRM: http://ti.com/lit/pdf/swcu185

Zigbee Alliance website: http://zigbee.org/

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