Home arrow Blog arrow A Brief Tutorial on the ZCL with Examples from FreakZ
A Brief Tutorial on the ZCL with Examples from FreakZ | Print |
Written by Akiba   
Friday, 19 June 2009

One of the questions I get most often is about how the Zigbee Cluster Library works. If you just read the spec, then it’s not immediately obvious because many things are implied. Also, you often have to read the Zigbee spec in conjunction with the Zigbee Cluster Library spec in order to understand how things work because specific parts of the functionality are written in each. However the big thing that people have problems with is implementation. The Zigbee Cluster Library is written in the concept of object oriented programming where everything is abstracted. However actual implementations will usually be written in C which does not have these facilities. Thus there’s a significant architectural challenge to map the abstraction of the Zigbee Cluster Library into a language like C. Since I’m updating my documentation about the ZCL to reflect some recent changes I’ve made to it, I thought that some of the material might be useful in a ZCL tutorial. I’m skipping a couple of layers with this tutorial, but I figure there’s no rule that I need to go in order. Hence, with no further ado…

The Zigbee Cluster Library defines a generic interface to the Zigbee stack and consists of attributes and commands. The attributes contain data about that interface, and the commands initiate actions for it. An example is the ZCL’s level control cluster. The “current level” attribute contains the data value that represents the current level of something, ie: a dimmable light. The “move to level” command initiates an action, where the current level will transition to a new level specified in the command’s arguments. That example pretty much summarizes the functionality of the Zigbee Cluster Library.

The difficulty with the ZCL, and the part that I get a lot of questions about, is that it is a completely generic interface. All clusters have the same interface, in that they can contain both attributes and commands. The attributes are also generic, meaning that an attribute can contain any type of data. One attribute could be a character string and another attribute can be an unsigned integer. In object oriented terms, an attribute exhibits polymorphism where an unsigned char attribute can be used like a string attribute. In C, this is not so easy to accomplish.

Before we get into the details of the FreakZ implementation, it might be good to get an idea visually of what the ZCL is. Here’s a diagram that illustrates how a device profile is put together using clusters from the ZCL.

ZCL Overview

From the diagram you can see all the basic building blocks. The most basic block is the attribute. Multiple attributes are aggregated into an attribute list. Clusters consist of attribute lists and command parsers. Groups of clusters are aggregated into a cluster list. And finally, the cluster list is tied together by the simple descriptor and the frame handler to create a device. Each device is located on a separate endpoint.

To build up a Zigbee application for a standard device profile, you would need to create a file that contains the following structures:
  1. A simple descriptor – Defines the endpoint, clusters, and profile of the endpoint/device.
  2. A cluster list – Contains the clusters that the device uses.
  3. Clusters – the instantiation of the clusters in the cluster list
  4. Attribute lists – the attributes that are contained in each of the defined clusters
  5. Device initialize and receive functions

This is a bit complicated so let’s take a look at an example:

/**************************************************************************/
/*!
    Simple descriptor for this ep
*/
/**************************************************************************/
static U8 simple_desc[] = {
    TEST_ZCL_EP,                // ep
    0x04,                       // profile id
    0x01,
    TEST_ZCL_DEV_ID_ON_OFF,     // dev id
    TEST_ZCL_DEV_VER,           // dev ver
    2,                          // num in clusters
    ZCL_BASIC_CLUST_ID,         // in clusters
    ZCL_ON_OFF_CLUST_ID,
    1,                          // num out clusters
    ZCL_ON_OFF_CLUST_ID
};

This is the simple descriptor for the test ZCL endpoint in the command line application I wrote. It defines the endpoint, profile, device, and clusters. This descriptor gets registered with the application layer so that if any frames arrive for this endpoint, they will be forwarded here. Also, if other devices inquire about what services this endpoint supports, the application will reply with the info provided by this descriptor.

/**************************************************************************/
/*!
    Basic Cluster
*/
/**************************************************************************/
static zcl_basic_attrib_list_t basic_attrib_list;
static zcl_clust_t basic_clust;

There are two instantiations that are required to implement a cluster. In this case, it’s the ZCL’s basic cluster. The first instantiation is the attribute list. This is a structure defined in the basic cluster source file (zcl_basic.h) which contains all of the attributes required for the basic cluster. We will be examining the attribute list structure in more detail later. The second instantiation is for the cluster itself. We’ll also be examining the cluster structure later on.

Along with the basic cluster, other clusters are also defined and they use the same format where they require instantiating the attribute list and the cluster. Once all the clusters are defined, we get to the cluster list:


/**************************************************************************/
/*!
        Initialize the cluster list
*/
/**************************************************************************/
/// This is where the cluster list is defined
static zcl_clust_t *test_zcl_clust_list[] =
{
    &basic_clust,
    &on_off_clust,
    &id_clust,
    &level_clust,
    NULL
};

The cluster list is a pointer array which just contains pointers to each of the clusters you defined. When a frame is received, it is checked against this list and the appropriate cluster is extracted based on the cluster ID of the frame.


/**************************************************************************/
/*!
        Init the endpoint

*/
/**************************************************************************/
void test_zcl_init()
{
    // init the clusters with the proper data
    zcl_set_clust(&basic_clust, …);
    zcl_set_clust(&on_off_clust, …);
    zcl_set_clust(&id_clust, …);
    zcl_set_clust(&level_clust, …);

    // init the attributes
    zcl_basic_init(&basic_attrib_list);
    zcl_on_off_init(&on_off_attrib_list);
    zcl_id_init(&id_attrib_list);
    zcl_level_init(&level_attrib_list);

    // register the endpoint as a ZCL endpoint
    af_ep_add(TEST_ZCL_EP, simple_desc,…);
}


Finally, we get to the endpoint initialization routine. The initialization routine starts off by initializing the cluster fields such as the endpoint that this instance belongs to, the cluster’s ID, the address of its attribute list, and handlers for received frames and user actions. This needs to be performed for each cluster. One thing to also note is that all the instantiations are specific to this endpoint only. You can create a new endpoint in a separate file and have a new instantiation of the same cluster, ie: the basic cluster which will be unique to that endpoint. One of the questions I get asked often is how to have multiple instantiations of the same cluster on different endpoints. Well, there’s your answer.

After the clusters are initialized, then we need to initialize the attribute lists. Initializing the attributes consists of linking the attribute to its data container (it’s variable), setting the data type, and initializing the value to its default. Each cluster file defines an init function which handles the initialization specifically for that cluster.

And finally, we register the endpoint with the application layer, the Application Framework to be exact. Once it’s registered, the AF will forward any frames addressed to this endpoint to the rx handler in this file.

Now that we have the high level overview of how the clusters and attributes are used to define a device, let’s take a more detailed look at how the attributes and clusters are actually implemented. It’s probably best to start at the bottom with the attributes. As an example, I’ll use the ZCL’s level cluster which can be found in zcl_level.c/h.

The level cluster’s attributes are defined in the header file. At the top of the file, you can see a list of definitions for attribute IDs, command IDs, and arguments. These should be pretty self explanatory. Below that, you’ll see a structure called zcl_level_data_t:

/**************************************************************************/
/*!
       Data container for the ZCL level attribute list
*/
/**************************************************************************/
typedef struct zcl_level_data_t
{
    U8  curr_level;
    U16 on_off_trans_time;
    U8  on_level;
} zcl_level_data_t;

This is the structure that holds the containers for the attribute data. Each attribute holds data, however the attribute data type is generic so it’s not possible to define a single type for the attribute that can cover all the possibilities. Hence, the data field in a generic attribute is just a void pointer that points to a container which holds the actual data. This structure is the container and contains one variable for every attribute. In general, each cluster file will have an attribute data container called “zcl_<cluster name>_data_t”.

Before we get to the attribute list structure, I’d like to explain a bit about the generic attribute structure, since it might be useful. Here’s the definition of the attribute structure in FreakZ: zcl_attrib_t:

/**************************************************************************/
/*!
        ZCL attribute structure. Contains all fields to address the attribute.
        Also, contains an optional field in case the attribute is set to
        report.
*/
/**************************************************************************/
typedef struct _zcl_attrib_t
{
    U16         id;             ///< Attrib ID
    U8          type;           ///< Attrib data type
    U8          access;         ///< Attrib data access privileges
    void        *data;          ///< Ptr to data
    mem_ptr_t   *rpt;           ///< Placeholder in case this attrib requires a report
} zcl_attrib_t;

The generic attribute type is basically the same as the one defined in the Zigbee spec. Each attribute has an attribute ID used for identification, a data type to specify what kind of data it holds, access rights for that attribute, and a void pointer to it’s data container. I also added an additional field for the ZCL reporting purposes.

With an understanding of the attribute data container and the attribute structure, we can now take a look at how the attribute list is defined:

/**************************************************************************/
/*!

*/
/**************************************************************************/
typedef struct _zcl_level_attrib_list_t
{
    zcl_attrib_t        list[ZCL_LEVEL_ATTRIB_LIST_SZ];
    zcl_level_data_t    data;
} zcl_level_attrib_list_t;

You can see that the attribute list contains two fields. The first field is an array of generic attributes. This is the actual attribute list. The second field is the data container structure. When the attribute list is initialized, the attribute’s data pointer will point to it’s corresponding field in the data container.

Hopefully, that wasn’t too confusing. That’s how the attribute list is generated and initialized. Now let’s take a look at a generic cluster. Here’s the definition for a cluster from the FreakZ stack:

/**************************************************************************/
/*!
        ZCL cluster structure. Each cluster contains endpoint, cluster ID, attribute list
        and callbacks to handle processing for that cluster. The RX handler is
        for incoming frames addressed to that cluster. The action handler is
        for further actions on that cluster which can be defined by the user:
        ie: Set a GPIO to turn on or off a light.
*/
/**************************************************************************/
typedef struct _zcl_clust_t
{
    U8 ep;                      ///< Endpoint that cluster belongs to
    U16 clust_id;               ///< Cluster ID
    zcl_attrib_t *attrib_list;  ///< Cluster attribute list

     void (*rx_handler)(U8 *resp, U8 *resp_len, U16 addr, U8 ep, struct _zcl_clust_t *clust, zcl_hdr_t *hdr);

    void (*action_handler)(U8 action, void *data);
} zcl_clust_t;

In the generic cluster listing above, you can see that a cluster contains the endpoint of the instance of that cluster, the cluster ID, a pointer to the attribute list, and two function pointers. The endpoint is needed because you may have multiple instances of clusters with the same ID, but on different endpoints. Hence the endpoint can be used for identification and to retrieve required info.

The attrib list pointer should now be self-explanatory, but it just gets initialized with the attribute list that we just looked at. The two function pointers are actually pretty interesting.

You can break down a Zigbee cluster’s behavior into two parts: (1) an action specified by ZCL spec (2) an action defined by the user. In the case of a command going into the on/off cluster to turn on a light, the ZCL action is that a response needs to be generated and sent back to the originating device. The user defined action is that a pin on one of the GPIO ports needs to be set high to turn on the light. Hence, the two function pointers are required.

The cluster’s rx_handler is the action defined by the ZCL spec. This function will be initialized to the matching function inside the cluster’s source file. For example, the rx_handler for an on/off cluster will be defined in the zcl_on_off source file. Basically, this is the part that I take care of.

On the other hand, the user will need to write an action_handler function of their own and this will define what user-defined action to take for that cluster.

For example, let’s take the case where a frame arrives and is addressed to the on/off cluster of endpoint 1 with a command to turn on a light. When the frame arrives at the endpoint, the endpoint will see that the frame is cluster specific with the on/off cluster ID. It will then forward the frame to the on/off cluster’s rx_handler via the cluster’s function pointer. Incidentally, this is the rx_handler in zcl_on_off.c. In the rx_handler, the command gets parsed, the on/off attribute gets set to “on” and a response is sent. After that, the control passes over to the action handler which is called via the “action_handler” function pointer. The action handler is written by the user and is just one line which sets a GPIO bit to HIGH on the microcontroller. This will turn on the light.



ZCL Processing Sequence

That’s basically how the ZCL in FreakZ works and hopefully I didn’t completely lose anyone. The ZCL is not an easy subject to conceptually get. It took me multiple attempts to come up with a workable architecture for it so don’t worry if its confusing at first. Once you see the logic behind it, you’ll probably still be confused...

Hits: 2353
Trackback(0)
Comments (15)Add Comment
Binding
written by Tally, July 15, 2009
Hello Akiba,

Thank you for all the tutorials. I am just starting out and these tutorials are helping me a lot. I wanted to know why binding is given so much importance in zigbee when compared to other technologies. Is it because binding can be performed for specific application objects or is there some other reason ? I did not know where to ask this question and hence I am asking it in this post. It would be great if you could answer. Thanks
report abuse
vote down
vote up
Votes: +0
...
written by Akiba, July 15, 2009
Binding is important because your software won't need to hard code any addresses. Since addresses can change at any time depending on whether a device is in-range, out-of-range, out of batteries, etc...Once you get deeper into using Zigbee or any other protocol, you'll find that binding simplifies the coding a lot where for example, all you do is collect data and send it out. The stack handles resolving the address (via binding) and transmitting it to the proper node correctly.
report abuse
vote down
vote up
Votes: +0
FreakLabs ZCL Implementation
written by Lucian Copat, September 23, 2009
Hi Akiba,

This is my first message on this site, so first congratulations for your project and keep up the good work. I have downloaded the source code and had a look upon, specially the ZCL implementation, since I am using Bitcloud, which doesn't have such thing yet. I have some questions to you:

1. In the frame header structure (zcl_hdr_t) are you still compatible with the ZigBee specs ? Since you have a zcl_frm_ctrl_t structure and also U8 fcf.

2. General commands are not implemented, right ? Or I haven't look very carefully.
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 23, 2009
My mistake, I have found the implementation for commands in zcl.c. Anyway, it seems that not all the commands listed in ZCL Specs are implemented ? Some of them are optional ?
report abuse
vote down
vote up
Votes: +0
...
written by Akiba, September 24, 2009
Not sure which version of the stack you downloaded, but ZCL general commands are implemented. They are in zcl_gen.c. Also, the zcl_hdr_t and the fcf are required. If you look in the ZCL spec in section 2.3, you will see that the hdr is required to communicate the sequence number and the command ID. The fcf is required for the direction, frame type, and to indicate whether the ZCL data is manufacturer specific or not.

It's true that not all commands are implemented. I implemented the most commonly used commands. In fact, some of the ones that I put in there should be removed because they're very rarely used by Zigbee (if at all) and other companies are not implementing them to save on code space.
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 24, 2009
Yes, but from what I understand, the frame control field is included in the header and has only 8 bits, not 24 as in your implementation (assuming that zcl_frm_ctrl_t will be packed in 16 bits). And further, the manufacturer code could be missing, so the mapping will be different.
report abuse
vote down
vote up
Votes: +0
...
written by Akiba, September 24, 2009
I think you might be looking at the wrong code for the fcf. Actually, I don't think any of my fcfs are 24 bits. Also, the code is working and my Daintree protocol analyzer doesn't have any problems decoding the ZCL general and Home Automation commands.
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 24, 2009
I am looking at zcl_hdr_t in zcl.h. It could be something I don't get...
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 24, 2009
This is the structure:
/ ******************************************************* *******************/
/*!
ZCL frame header structure.
*/
/ ******************************************************* *******************/
typedef struct _zcl_hdr_t
{
zcl_frm_ctrl_t frm_ctrl; ///< Frame control field structure
U8 fcf; ///< Frame control field - condensed into single byte
U16 manuf_code; ///< Manufacturer code
U8 seq_num; ///< Sequence number - used to identify response frame
U8 cmd; ///< Command ID
U8 *payload; ///< Payload
U8 payload_len; ///< Payload length
} zcl_hdr_t;

The fcf is 8 bits, but before it has zcl_frm_ctrl_t structure, which is 16 bits.
report abuse
vote down
vote up
Votes: +0
...
written by Akiba, September 24, 2009
Aaah...I see where you got the idea about 24 bits from. I think you need to read through the code rather than just looking at the structs. I don't do any bitslicing because code isn't portable that way. Hence the struct fields always use a minimum size of 1 byte, even for boolean values. The savings in RAM isn't worth the portability issues. If you check out this function:

U8 zcl_gen_fcf(zcl_hdr_t *hdr)

You will see that I take in the hdr struct as an arg which includes the frm_ctrl struct. The frm ctrl struct will take the values in the fields and generate an 8-bit frame control field.

If you also take a look at this function:

U8 zcl_gen_hdr(U8 *data, zcl_hdr_t *hdr)

you will see that I will only generate the manufacturing code if the manuf_specific flag is set.
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 24, 2009
Thank you for the clarification. I was looking at the structures because I have thought myself about developing my own ZCL for BitCloud and I was curious how you did it. I found no solution yet on how to include/exclude the manufacturer specific code, using unions or structs. But I guess your solution is the best.
report abuse
vote down
vote up
Votes: +0
...
written by Akiba, September 24, 2009
Well, a solution using struct pointer overlays (ie: direct mapping from struct fields to frame buffer) is not really practical because Zigbee and 802.15.4 have variable length fields depending on conditions in the header. This pretty much means that you have to manually assemble the headers. If you could just do a pointer overlay, then the code could be optimized much more. That's one of the optimizations explored by TCP/IP stack writers since the headers are all fixed position/length.
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 24, 2009
You are right, and I see now where you are generating the header from the fields. I saw for some functions the comment "TODO: Rewrite this function to make it more concise" - what does this mean ? Could the code be more compact or ?
report abuse
vote down
vote up
Votes: +0
...
written by Akiba, September 24, 2009
There are some parts of the code that I feel could be made better. I'm leaving it to refactoring though because I'm trying to get a functional stack first. Optimization will come in later, or else the stack would never get finished.
report abuse
vote down
vote up
Votes: +0
...
written by Lucian Copat, September 24, 2009
I will take a closer look over your ZCL implementation in the days to come and with your permission I will inspire from it for BitCloud. I will let you know if I find some possible optimizations. I am not familiar with open-source development, but are there other contributors to the stack or are you considering this ?
report abuse
vote down
vote up
Votes: +0

Write comment

busy
  No Comments.

Discuss...
< Prev   Next >