Skip to content

6.2 ZCL 属性读写实验 ​

一句话理解

协调器向终端先写后读:写入一个值 → 读回来 → 屏幕对比,验证"写进去、读出来"是否一致。

实验简介 ​

  • 设备:一个协调器 + 一个终端
  • 内容:协调器向终端发写命令(写入属性值),再发读命令(读回该属性),最后比较写入信息和读到的信息是否一致。
✍️
写命令事件
WriteTest():写入属性 ATTRID_ON_OFF_SWITCH_ACTIONS
→
📖
读命令事件
ReadTest():读回同一属性
→
🔍
比对
屏幕显示 Write: N 与 Read: N,验证一致

定义读写事件 ​

在 zcl_samplesw.h 中定义读事件和写事件:

c
#ifdef ZDO_COORDINATOR
  // Read test event
  #define SAMPLEAPP_READ_EVT   0x0001
  #define SAMPLEAPP_READ_PERIOD 1000

  // Write test event
  #define SAMPLEAPP_WRITE_EVT   0x0002
  #define SAMPLEAPP_WRITE_PERIOD 1000
#endif

协调器收到终端广播(Annce)后,启动读命令事件(zclSampleSw_processZDOMsgs 中):

c
case Device_annce:
  zclSampleSw_TestAddr = pMsg->srcAddr.addr.shortAddr;   // 记录终端地址
  HalLcdWriteStringValue( "Node:", pMsg->srcAddr.addr.shortAddr, 16, 3 );
  HalLcdWriteString( "Test...", 4 );

  osal_start_timerEx( zclSampleSw_TaskID,
                      SAMPLEAPP_READ_EVT,
                      SAMPLEAPP_READ_PERIOD );   // 启动读事件
  break;

读命令事件处理 ​

在 zclSampleSw_event_loop() 中:

c
if ( events & SAMPLEAPP_READ_EVT )   // 读命令事件
{
  zclSampleSw_ReadTest();            // 读命令处理函数

  // 读完之后启动写命令事件(交替执行)
  osal_start_timerEx( zclSampleSw_TaskID,
                      SAMPLEAPP_WRITE_EVT,
                      SAMPLEAPP_WRITE_PERIOD );
  return ( events ^ SAMPLEAPP_READ_EVT );
}

zclSampleSw_ReadTest() 定义:

c
static void zclSampleSw_ReadTest( void )
{
  afAddrType_t destAddr;
  zclReadCmd_t *readCmd;
  static uint8 txID = 0;

  destAddr.endPoint = SAMPLESW_ENDPOINT;
  destAddr.addrMode = afAddr16Bit;
  destAddr.addr.shortAddr = zclSampleSw_TestAddr;   // 终端地址

  // 申请动态内存(1 个属性 + 属性 ID 空间)
  readCmd = (zclReadCmd_t *)osal_mem_alloc( sizeof(zclReadCmd_t) +
                                            sizeof(uint16) );
  if ( readCmd == NULL ) return;

  readCmd->numAttr = 1;                    // 待读属性数量 = 1
  readCmd->attrID[0] = ATTRID_ON_OFF_SWITCH_ACTIONS;  // 待读属性 ID

  // 发送读命令
  zcl_SendRead( SAMPLESW_ENDPOINT,
                &destAddr,
                ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG, // Cluster ID
                readCmd,
                ZCL_FRAME_CLIENT_SERVER_DIR,  // 方向:客户端 → 服务端
                TRUE,
                txID++ );
  osal_mem_free( readCmd );   // 释放内存
}

写命令事件处理 ​

读事件处理下方是写事件处理:

c
if ( events & SAMPLEAPP_WRITE_EVT )   // 写命令事件
{
  zclSampleSw_WriteTest();            // 写命令处理函数

  // 写完之后启动读命令事件(交替执行)
  osal_start_timerEx( zclSampleSw_TaskID,
                      SAMPLEAPP_READ_EVT,
                      SAMPLEAPP_READ_PERIOD );
  return ( events ^ SAMPLEAPP_WRITE_EVT );
}

zclSampleSw_WriteTest() 定义:

c
static void zclSampleSw_WriteTest( void )
{
  afAddrType_t destAddr;
  zclWriteCmd_t *writeCmd;
  static uint8 txID = 0;

  destAddr.endPoint = SAMPLESW_ENDPOINT;
  destAddr.addrMode = afAddr16Bit;
  destAddr.addr.shortAddr = zclSampleSw_TestAddr;

  // 申请动态内存(写命令 + 写记录)
  writeCmd = (zclWriteCmd_t *)osal_mem_alloc( sizeof(zclWriteCmd_t) +
                                              sizeof(zclWriteRec_t) );
  if ( writeCmd == NULL ) return;

  writeCmd->attrList[0].attrData = (uint8 *)osal_mem_alloc( sizeof(uint8) );
  if ( writeCmd->attrList[0].attrData == NULL ) return;

  writeCmd->numAttr = 1;                                   // 写 1 个属性
  writeCmd->attrList[0].attrID = ATTRID_ON_OFF_SWITCH_ACTIONS;  // 属性 ID
  writeCmd->attrList[0].dataType = ZCL_DATATYPE_ENUM8;    // 数据类型
  *(writeCmd->attrList[0].attrData) = txID;               // 写入值 = txID

  HalLcdWriteStringValue( "Write:", txID, 10, 4 );        // 屏幕显示写入值

  zcl_SendWrite( SAMPLESW_ENDPOINT,
                 &destAddr,
                 ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG, // Cluster ID
                 writeCmd,
                 ZCL_FRAME_CLIENT_SERVER_DIR,
                 TRUE,
                 txID++ );
  osal_mem_free( writeCmd->attrList[0].attrData );        // 释放内存
  osal_mem_free( writeCmd );
}

交替节奏

协调器读 → 写 → 读 → 写循环交替:每次写完启动读事件,每次读完启动写事件。屏幕会看到 Write: N 与 Read: N 交替刷新。

读响应处理 ​

终端返回读响应后,协调器在 zclSampleSw_ProcessInReadRspCmd() 中取出属性值:

c
#ifdef ZCL_READ
static uint8 zclSampleSw_ProcessInReadRspCmd( zclIncomingMsg_t *pInMsg )
{
  zclReadRspCmd_t *readRspCmd;
  uint8 i;

  readRspCmd = (zclReadRspCmd_t *)pInMsg->attrCmd;

  for ( i = 0; i < readRspCmd->numAttr; i++ )   // 遍历返回的属性
  {
    if ( pInMsg->clusterId == ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG &&
         readRspCmd->attrList[i].attrID == ATTRID_ON_OFF_SWITCH_ACTIONS )
    {
      uint8 val;
      val = *(readRspCmd->attrList[i].data);    // 取出属性值
      HalLcdWriteStringValue( "Read:", val, 10, 4 );  // 屏幕显示读取值
    }
  }
  return TRUE;
}
#endif // ZCL_READ

仿真调试 ​

  1. 分别编译协调器和终端(路由器)工程,下载到两块开发板;
  2. 终端设备加入 ZigBee 网络后,观察协调器屏幕:
  • 屏幕交替显示 Write: 6(写入值)和 Read: 6(读回值);
  • 数值会随 txID 递增(7、8、9…),但 Write 和 Read 始终一致。

实验现象小结

屏幕提示含义
Write: N刚写入属性值 N
Read: N刚读回属性值 N

写进去和读出来完全一致,说明属性读写链路打通了!

内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第四部分·6.2 ZCL 属性读写实验,原站链接


本章自测 ​

一、单项选择题(每题 4 分,共 40 分) ​

  1. 本实验的核心思路是(A. 协调器只发读命令 B. 协调器向终端先写后读:写入一个值 → 读回来 → 屏幕对比是否一致 C. 终端主动上报属性 D. 只做广播通信) 答案:B

  2. 本实验中被读写的属性是(A. ATTRID_ON_OFF B. ATTRID_ON_OFF_SWITCH_ACTIONS C. ATTRID_BASIC_SW_BUILD_ID D. 自定义厂商属性) 答案:B

  3. 在 zcl_samplesw.h 中,SAMPLEAPP_READ_EVT 与 SAMPLEAPP_WRITE_EVT 的事件值分别定义为(A. 0x0001 和 0x0002 B. 0x0003 和 0x0004 C. 0x0000 和 0xFFFF D. 由协议栈自动分配) 答案:A

  4. 协调器是在什么时机启动读命令事件的(A. 上电立即启动 B. 收到终端 Device_annce 后记录终端地址,并用 osal_start_timerEx() 启动 SAMPLEAPP_READ_EVT C. 串口收到字符后 D. 收到写响应后) 答案:B

  5. 读事件与写事件的交替关系是(A. 读完之后启动写事件,写完之后再启动读事件,循环交替 B. 整个流程只执行一次 C. 写事件先启动 D. 两个事件同时并行触发) 答案:A

  6. zclSampleSw_ReadTest() 中用 osal_mem_alloc() 申请的动态内存大小是(A. sizeof(zclReadCmd_t) B. sizeof(zclReadCmd_t) + sizeof(uint16)(1 个属性 + 属性 ID 空间) C. 固定 10 字节 D. 不需要申请动态内存) 答案:B

  7. ReadTest() 中 readCmd->numAttr 与 readCmd->attrID[0] 分别设置为(A. 0,任意属性 ID B. 1,ATTRID_ON_OFF_SWITCH_ACTIONS C. 2,两个属性 D. 由目标设备决定) 答案:B

  8. WriteTest() 中实际写入终端属性的值来自(A. 固定常量 0 B. 静态变量 txID(*(writeCmd->attrList[0].attrData) = txID;,随后 txID++) C. 终端属性当前值 D. 终端短地址) 答案:B

  9. 协调器在哪个函数中遍历读响应、取出属性值并在屏幕显示 "Read:"(A. zclSampleSw_ProcessIncomingMsg() B. zclSampleSw_ProcessInReadRspCmd() C. zclSampleSw_processZDOMsgs() D. zclSampleSw_OnOffCB()) 答案:B

  10. 本实验预期的屏幕现象是(A. 只显示 Write: N B. Write: N 与 Read: N 交替刷新,数值随 txID 递增(7、8、9…),但 Write 与 Read 始终一致 C. 屏幕固定显示 0 D. 只显示 Read: N) 答案:B

二、问答题(每题 20 分,共 60 分) ​

  1. 题干:简述本实验中协调器"读—写"交替的事件节奏。 参考答案要点:
  • 终端入网广播后,协调器在 zclSampleSw_processZDOMsgs() 的 Device_annce 分支中记录终端短地址,并启动 SAMPLEAPP_READ_EVT;
  • 读事件触发 zclSampleSw_ReadTest(),读完后用 osal_start_timerEx() 启动 SAMPLEAPP_WRITE_EVT;
  • 写事件触发 zclSampleSw_WriteTest(),写完后再启动 SAMPLEAPP_READ_EVT;
  • 如此"读 → 写 → 读 → 写"循环交替,屏幕上 Write: N 与 Read: N 交替刷新。
  1. 题干:zclSampleSw_WriteTest() 中是如何构造并发送一条写命令的? 参考答案要点:
  • 填充目标地址:端点 SAMPLESW_ENDPOINT、addrMode = afAddr16Bit、短地址为终端地址 zclSampleSw_TestAddr;
  • 用 osal_mem_alloc() 申请 sizeof(zclWriteCmd_t) + sizeof(zclWriteRec_t) 空间,再为 attrList[0].attrData 申请 1 字节内存;
  • 设置 numAttr = 1,attrList[0].attrID = ATTRID_ON_OFF_SWITCH_ACTIONS,dataType = ZCL_DATATYPE_ENUM8,*attrData = txID(写入值);
  • 屏幕打印 Write: N,调用 zcl_SendWrite() 发送(方向 Client→Server,序号 txID++);
  • 发送后依次 osal_mem_free() 释放 attrData 和 writeCmd 动态内存。
  1. 题干:本实验是如何验证"属性读写链路打通"的? 参考答案要点:
  • 写命令把 txID 值写入终端的 ATTRID_ON_OFF_SWITCH_ACTIONS 属性;
  • 读命令再把同一属性读回;
  • 终端返回读响应后,协调器在 zclSampleSw_ProcessInReadRspCmd() 中遍历 readRspCmd->attrList,匹配簇 ID 与属性 ID 后用 *(readRspCmd->attrList[i].data) 取出属性值,屏幕打印 Read: val;
  • 观察到 Write: N 与 Read: N 数值始终一致(数值随 txID 递增但两边同步),即说明"写进去、读出来"一致,属性读写链路打通。

aioteer · 物联网专业课程群

💬AI 伴学配置