6.1 ZCL 属性读写 API

添加属性

待开发设备所需要使用的属性均需要在属性列表中定义。如前面章节所述,打开zcl_samplesw_data.c文件,可以找到属性列表的代码定义:
第6章:基于ZCL的属性读写 - 图1

在ZCL内容详解章节已经讲解过这个数组了,此处更进一步讲解一下。属性ATTRID_ON_OFF_SWITCH_ACTIONS为例讲解,这个属性:

  • 是一个8位的属性(ZCL_DATATYPE_ENUM8)
  • 可以看到这个属性是可被读取和写入写的(ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE)。
  • 这个属性被添加进Cluster ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG 中

开发者可以套用上述代码来往指定的Cluster中添加指定的属性。

属性读写命令API

可以调用属性读写命令API对指定的属性进行读取或写入。打开zcl.h文件,可以找到属性读写命令API的定义,代码如下:

  1. #ifdef ZCL_READ
  2. /*
  3. * 发送一个读取属性命令
  4. */
  5. extern ZStatus_t zcl_SendRead(
  6. uint8 srcEP, // 源端点号
  7. afAddrType_t *dstAddr, // 目标设备地址信息
  8. uint16 realClusterID, // Cluster ID
  9. zclReadCmd_t *readCmd, // “读”信息
  10. uint8 direction, // 通信方向
  11. uint8 disableDefaultRsp, //是否关闭默认响应(目标设备的响应)
  12. uint8 seqNum); // 数据包标号,由开发者自定义
  13. /*
  14. * 省略部分代码
  15. */
  16. #endif // ZCL_READ
  17. #ifdef ZCL_WRITE
  18. /*
  19. * 发送一个写入属性命令 ZCL_CMD_WRITE
  20. * 按以下方式调用:
  21. * ZStatus_t zcl_SendWrite(
  22. * uint8 srcEP, // 源应用端点
  23. * afAddrType_t *dstAddr, // 目标设备地址信息
  24. * uint16 realClusterID, // Cluster ID
  25. * zclWriteCmd_t *writeCmd, // “写”信息
  26. * uint8 direction, // 通信方向
  27. * uint8 disableDefaultRsp, //是否关闭默认响应(目标设备的响应)
  28. * uint8 seqNum); // 数据包标号,由开发者自定义
  29. */
  30. #define zcl_SendWrite(a,b,c,d,e,f,g) (zcl_SendWriteRequest( (a), (b), (c), (d), ZCL_CMD_WRITE, (e), (f), (g) ))
  31. /*
  32. * 省略部分代码
  33. */
  34. #endif // ZCL_WRITE

命令处理

客户端设备在向服务端设备发送上述命令后,会接收到由服务端的返回来的响应信息并且产生系统事件ZCL_INCOMING_MSG。因此,需要处理这个事件。打开zcl_samplesw.c文件中的zclSampleSw_event_loop函数,添加对应的事件处理函数zclSampleSw_ProcessIncomingMsg,代码如下:
第6章:基于ZCL的属性读写 - 图2

zclSampleSw_ProcessIncomingMsg是由开发者自定义的函数,代码如下:

  1. 1.static void zclSampleSw_ProcessIncomingMsg(zclIncomingMsg_t *pInMsg)
  2. 2.{
  3. 3. switch ( pInMsg->zclHdr.commandID )
  4. 4. {
  5. 5.#ifdef ZCL_READ
  6. 6. case ZCL_CMD_READ_RSP: // 读命令响应信息
  7. 7. zclSampleSw_ProcessInReadRspCmd( pInMsg );//读响应信息处理函数
  8. 8. break;
  9. 9.#endif
  10. 10.#ifdef ZCL_WRITE
  11. 11. case ZCL_CMD_WRITE_RSP: // 写命令响应信息
  12. 12. zclSampleSw_ProcessInWriteRspCmd( pInMsg ); //写命令响应信息处理函数
  13. 13. break;
  14. 14.#endif
  15. 15.#ifdef ZCL_REPORT
  16. 16. ...... // 暂时不展开
  17. 17.#endif
  18. 18.
  19. 19. case ZCL_CMD_DEFAULT_RSP: // 默认响应信息
  20. 20. zclSampleSw_ProcessInDefaultRspCmd( pInMsg );//默认响应信息处理函数
  21. 21. break;
  22. 22.
  23. 23.#ifdef ZCL_DISCOVER
  24. 24. ...... // 暂时不展开
  25. 25.#endif
  26. 26.
  27. 27. default:
  28. 28. break;
  29. 29. }
  30. 30.
  31. 31. if ( pInMsg->attrCmd )
  32. 32. osal_mem_free( pInMsg->attrCmd );
  33. 33.}

上述代码用到了默认响应(ZCL_CMD_DEFAULT_RSP)、读响应(ZCL_CMD_READ_RSP)和写响应(ZCL_CMD_WRITE_RSP),分别展开讲解一下。

  • 默认响应
    客户端给服务端发送命令时可以指定服务器端是否需要返回一个默认的响应信息。客户端可以据此来确定服务器端是否确实接收到这个命令。
  • 读响应
    客户端给服务端发送读取属性命令后,服务端会返回一个读响应。这个读响应中包含了属性是否读取成功和属性值等内容。
  • 写响应
    客户端给服务端发送写命令后,服务端会返回一个写响应。这个写响应中包含属性值是否写入成功等信息。

6.2 ZCL 属性读写实验

本节课将以实验的方式讲解如何使用ZCL读写命令API,实验设备包含一个协调器和一个终端,内容是协调器向终端发送写命令,然后在再发送读命令,最后比较一下写入的信息和读取到的信息是否一致。

定义读写事件

在zcl_samplesw.h文件中分别定义一个读命令事件和写命令事件,代码如下:
第6章:基于ZCL的属性读写 - 图3

协调器接收到终端设备广播(Annce)的信息后,启动一个读命令事件,代码如下:
第6章:基于ZCL的属性读写 - 图4

在zcl_samplews.c文件的zclSample_event_loop事件处理函数中,可以找到读命令事件的处理代码,如下:

  1. //
  2. if ( events & SAMPLEAPP_READ_EVT )//如是读命令事件
  3. {
  4. zclSampleSw_ReadTest();//读命令事件处理函数
  5. //启动一个写命令事件
  6. osal_start_timerEx(zclSampleSw_TaskID,
  7. SAMPLEAPP_WRITE_EVT,
  8. SAMPLEAPP_WRITE_PERIOD);
  9. return ( events ^ SAMPLEAPP_READ_EVT );
  10. }

读命令事件处理函数zclSampleSw_ReadTest的代码定义如下:

  1. 1.static void zclSampleSw_ReadTest(void)
  2. 2.{
  3. 3. afAddrType_t destAddr;
  4. 4. zclReadCmd_t *readCmd;
  5. 5. static uint8 txID = 0;
  6. 6.
  7. 7. destAddr.endPoint = SAMPLESW_ENDPOINT;
  8. 8. destAddr.addrMode = afAddr16Bit;
  9. 9. destAddr.addr.shortAddr = zclSampleSw_TestAddr;
  10. 10.
  11. //申请一个动态内存
  12. 11. readCmd = (zclReadCmd_t *)osal_mem_alloc(sizeof(zclReadCmd_t) + sizeof(uint16));
  13. 12.
  14. 13. if(readCmd == NULL)//判断是否成功申请到内存
  15. 14. return;
  16. 15. readCmd->numAttr = 1;//待读取的属性数量为1
  17. 16. readCmd->attrID[0] = ATTRID_ON_OFF_SWITCH_ACTIONS;//待读取的属性ID
  18. 17.
  19. 18. zcl_SendRead(SAMPLESW_ENDPOINT,
  20. 19. &destAddr,
  21. 20. ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG,//Cluster ID
  22. 21. readCmd,
  23. 22. ZCL_FRAME_CLIENT_SERVER_DIR,//通信方向是由客户端到服务器端
  24. 23. TRUE,
  25. 24. txID++);
  26. 25. osal_mem_free(readCmd);//释放内存
  27. 26.}

在读命令处理中代码中还启动一个写命令事件,在读命令事件处理代码的下方可以找到写命令事件的处理代码,如下:

  1. if ( events & SAMPLEAPP_WRITE_EVT )//如果是写命令事件
  2. {
  3. zclSampleSw_WriteTest();//写命令处理函数
  4. osal_start_timerEx(zclSampleSw_TaskID,//启动一个读命令事件
  5. SAMPLEAPP_READ_EVT,
  6. SAMPLEAPP_READ_PERIOD);
  7. return ( events ^ SAMPLEAPP_WRITE_EVT );
  8. }

写命令事件处理函数zclSampleSw_WriteTest的代码定义如下:

  1. static void zclSampleSw_WriteTest(void)
  2. {
  3. afAddrType_t destAddr;
  4. zclWriteCmd_t *writeCmd;
  5. static uint8 txID = 0;
  6. destAddr.endPoint = SAMPLESW_ENDPOINT;
  7. destAddr.addrMode = afAddr16Bit;
  8. destAddr.addr.shortAddr = zclSampleSw_TestAddr;
  9. writeCmd=(zclWriteCmd_t *)osal_mem_alloc(sizeof(zclWriteCmd_t) + sizeof(zclWriteRec_t));//申请一个动态内存
  10. if(writeCmd == NULL)//判断动态内存是否申请成功
  11. return;
  12. writeCmd->attrList[0].attrData=(uint8*)osal_mem_alloc(sizeof(uint8));//申请一个动态内存
  13. if(writeCmd->attrList[0].attrData == NULL)//判断动态内存是否申请成功
  14. return;
  15. writeCmd->numAttr = 1;//待写入的属性数量
  16. writeCmd->attrList[0].attrID =ATTRID_ON_OFF_SWITCH_ACTIONS;待写入的属性的ID
  17. writeCmd->attrList[0].dataType = ZCL_DATATYPE_ENUM8;//属性值的类型
  18. *(writeCmd->attrList[0].attrData) = txID;//属性值
  19. HalLcdWriteStringValue("Write:", txID, 10, 4);
  20. zcl_SendWrite(SAMPLESW_ENDPOINT,
  21. &destAddr,
  22. ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG,//Cluster ID
  23. writeCmd,
  24. ZCL_FRAME_CLIENT_SERVER_DIR,//通信方向是由客户端到服务器端
  25. TRUE,
  26. txID++);
  27. osal_mem_free(writeCmd->attrList[0].attrData); // 释放内存
  28. osal_mem_free(writeCmd); // 释放内存
  29. }

读响应处理

在客户端中处理从服务器端中读取到的信息,即编写读命令响应信息处理函数,代码如下:

  1. #ifdef ZCL_READ
  2. /*********************************************************************
  3. * @fn zclSampleSw_ProcessInReadRspCmd
  4. *
  5. * @brief 读响应处理函数
  6. *
  7. * @param pInMsg - 待处理的消息
  8. *
  9. * @return
  10. */
  11. static uint8 zclSampleSw_ProcessInReadRspCmd( zclIncomingMsg_t *pInMsg )
  12. {
  13. zclReadRspCmd_t *readRspCmd;
  14. uint8 i;
  15. readRspCmd = (zclReadRspCmd_t *)pInMsg->attrCmd;
  16. for (i = 0; i < readRspCmd->numAttr; i++)//readRspCmd->numAttr为属性的数量
  17. {
  18. if( pInMsg->clusterId == ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG &&//如果该消息是关于指定的Cluster
  19. readRspCmd->attrList[i].attrID == ATTRID_ON_OFF_SWITCH_ACTIONS )//如果该属性的ID是指定的属性ID
  20. {
  21. uint8 val;
  22. val = *(readRspCmd->attrList[i].data);//读取属性值
  23. HalLcdWriteStringValue("Read:", val, 10, 4);//显示信息到屏幕中
  24. }
  25. }
  26. return TRUE;
  27. }
  28. #endif // ZCL_READ

仿真调试

  • 分别编译协调器和终端(路由器)工程,然后分别下载到两个开发板中。
  • 终端(路由器)设备加入到ZigBee网络中后,可以看到协调器屏幕显示如下提示信息。
    第6章:基于ZCL的属性读写 - 图5
    第6章:基于ZCL的属性读写 - 图6
    可以观察到读和写的数据是一样的。