Skip to content

5.3 ZCL 开关命令收发实验 ​

一句话理解

本实验把 5.1 / 5.2 的"接收套路"和"发送套路"串起来:智能插座入网 → 广播地址 → 网关收到地址后周期发 On/Off → 插座收到命令执行开关。

实验简介 ​

  • 设备:一个网关(协调器) + 一个智能插座(终端或路由器)
  • 内容:智能插座加入网络后,网关自动周期性地向它发送 On / Off 指令,控制插座开关。
🔌
① 智能插座开发
入网 + 广播自己的地址 + 注册命令回调
→
🏛️
② 网关开发
接收地址 → 周期发送 On/Off 命令
→
🧪
③ 仿真调试
两块板子上电,观察命令收发

智能插座开发 ​

打开 zcl_samplesw.c,在初始化函数 zclSampleSw_Init() 中找到如下代码:

c
#ifdef ZDO_COORDINATOR
  // 协调器角色
  zclSampleSw_InitUart();
  ZDO_RegisterForZDOMsg ( zclSampleSw_TaskID, Device_annce );
  bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_FORMATION |
                          BDB_COMMISSIONING_MODE_FINDING_BINDING );
  NLME_PermitJoiningRequest(255);
#else
  // 终端/路由器角色:加入网络
  bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_STEERING |
                          BDB_COMMISSIONING_MODE_FINDING_BINDING );
  zclSampleSw_DeviceAnnce();   // ← 向整个网络广播本设备地址
#endif

zclSampleSw_DeviceAnnce() 是自定义函数,作用是向整个网络广播一个包含本设备地址的数据包,让协调器知道"我来了、我在哪"。定义如下:

c
static void zclSampleSw_DeviceAnnce( void )
{
  ZDP_DeviceAnnce(
    NLME_GetShortAddr(),    // 获取本设备网络地址(短地址)
    NLME_GetExtAddr(),      // 获取本设备物理地址(MAC 地址)
    ZDO_Config_Node_Descriptor.CapabilityFlags,  // 能力标志(暂忽略)
    0                       // 暂忽略
  );
}

它调用了协议栈 API ZDP_DeviceAnnce()——直接套用即可实现广播本设备地址。

处理指令:注册 On/Off 回调 ​

在 zcl_samplesw.c 的命令回调表 zclSampleSw_CmdCallbacks 中注册回调函数:

c
static zclGeneral_AppCallbacks_t zclSampleSw_CmdCallbacks =
{
  zclSampleSw_BasicResetCB,          // Basic Cluster Reset
  NULL,                              // Identify Trigger Effect
#ifdef ZDO_COORDINATOR
  NULL,                              // 协调器不处理 On/Off 命令
#else
  zclSampleSw_OnOffCB,               // 终端:On/Off 命令处理函数
#endif
  ...
};

zclSampleSw_OnOffCB 函数定义 ​

c
static void zclSampleSw_OnOffCB( uint8 cmd )
{
  if ( cmd == COMMAND_ON )       // 收到"打开"命令
  {
    HalLcdWriteString( "Set ON", 4 );          // 屏幕打印提示
    HalLedSet( HAL_LED_ALL, HAL_LED_MODE_ON ); // 点亮所有 LED
  }
  else if ( cmd == COMMAND_OFF ) // 收到"关闭"命令
  {
    HalLcdWriteString( "Set OFF", 4 );         // 屏幕打印提示
    HalLedSet( HAL_LED_ALL, HAL_LED_MODE_OFF );// 熄灭所有 LED
  }
}

收到命令 → 判断是 ON 还是 OFF → 打印屏幕 + 开关 LED。

网关开发 ​

网关做两件事:接收智能插座广播的地址 + 周期发送 On/Off 指令。

① 接收地址信息 ​

协调器收到插座广播(Annce)的地址消息时,产生系统事件 ZDO_CB_MSG,在事件处理函数中处理:

c
// 在 zclSampleSw_event_loop() 的 switch 中:
#ifdef ZDO_COORDINATOR
  case ZDO_CB_MSG:
    zclSampleSw_processZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
    break;
#endif

事件处理函数 zclSampleSw_processZDOMsgs 定义:

c
static void zclSampleSw_processZDOMsgs( zdoIncomingMsg_t *pMsg )
{
  switch ( pMsg->clusterID )    // 判断消息中的 Cluster ID
  {
    case Device_annce:          // 设备入网广播
    {
      // 保存目标设备(插座)的网络地址到全局变量
      zclSampleSw_OnOffTestAddr = pMsg->srcAddr.addr.shortAddr;
      // 屏幕显示节点地址
      HalLcdWriteStringValue( "Node:", pMsg->srcAddr.addr.shortAddr, 16, 3 );
      HalLcdWriteString( "On/Off Test...", 4 );

      // 周期产生发送 On/Off 指令的事件
      osal_start_timerEx( zclSampleSw_TaskID,
                          SAMPLEAPP_ONOFF_TEST_EVT,      // 事件
                          SAMPLEAPP_ONOFF_TEST_PERIOD ); // 周期
    }
    break;
    default:
      break;
  }
}

② 处理 SAMPLEAPP_ONOFF_TEST_EVT 事件 ​

在 zclSampleSw_event_loop() 中添加:

c
#ifdef ZDO_COORDINATOR
  if ( events & SAMPLEAPP_ONOFF_TEST_EVT )
  {
    zclSampleSw_OnOffTest();   // 发送 On/Off 命令给目标设备

    // 重新启动定时器,形成周期发送
    osal_start_timerEx( zclSampleSw_TaskID,
                        SAMPLEAPP_ONOFF_TEST_EVT,
                        SAMPLEAPP_ONOFF_TEST_PERIOD );
    return ( events ^ SAMPLEAPP_ONOFF_TEST_EVT );
  }
#endif

③ 发送 On/Off 指令 ​

zclSampleSw_OnOffTest() 函数定义:

c
static void zclSampleSw_OnOffTest( void )
{
  afAddrType_t destAddr;          // 目标设备地址
  static uint8 txID = 0;          // 数据包序号
  static bool on = true;          // 当前期望的开关状态

  destAddr.endPoint = SAMPLESW_ENDPOINT;       // 端点号
  destAddr.addrMode = Addr16Bit;               // 16 位网络地址(P2P)
  destAddr.addr.shortAddr = zclSampleSw_OnOffTestAddr;  // 插座网络地址

  if ( on )                      // 期望插座"开"
  {
    HalLcdWriteString( "Command: ON", 4 );
    zclGeneral_SendOnOff_CmdOn(   // 发送"打开"命令
      SAMPLESW_ENDPOINT,          // 源端点
      &destAddr,                  // 目标地址
      TRUE,                       // 簇限定命令
      txID++ );                   // 序号
  }
  else                           // 期望插座"关"
  {
    HalLcdWriteString( "Command: OFF", 4 );
    zclGeneral_SendOnOff_CmdOff(  // 发送"关闭"命令
      SAMPLESW_ENDPOINT,
      &destAddr,
      TRUE,
      txID++ );
  }

  on = !on;                      // 反转期望状态(开→关→开…)
}
⏰
定时事件
SAMPLEAPP_ONOFF_TEST_EVT 每周期触发一次
→
📡
发送命令
CmdOn / CmdOff 交替发送(on = !on 反转)
→
🔌
插座执行
OnOffCB 收到命令 → LED 亮/灭

仿真调试 ​

  1. 编译协调器工程,烧录到一块开发板(充当网关);
  2. 编译终端(或路由器)工程,烧录到另一块开发板(充当智能插座);
  3. 先后给网关和智能插座供电;
  4. 智能插座自动加入网关创建的网络,随后插座屏幕会显示网关发来的命令:
  • 网关屏幕:Command: ON → Command: OFF 交替显示(每周期反转一次);
  • 插座屏幕:Set ON(LED 全亮)→ Set OFF(LED 全灭)交替显示。

实验现象小结

设备屏幕显示现象
网关Command: ON / OFF 交替周期发送开关命令
插座Set ON / Set OFF 交替LED 同步点亮 / 熄灭

看到 LED 跟着命令"呼吸"地亮灭,就是 ZCL 命令收发成功了!

内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第四部分·5.3 ZCL 开关命令收发实验,原站链接


本章自测 ​

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

  1. 本实验采用的硬件设备组合是(A. 两个终端设备 B. 一个网关(协调器)+ 一个智能插座(终端或路由器) C. 两个协调器 D. 两个路由器) 答案:B

  2. 智能插座加入网络后,用于向整个网络广播自身地址的自定义函数是(A. zclSampleSw_OnOffTest() B. zclSampleSw_DeviceAnnce() C. zclSampleSw_OnOffCB() D. bdb_StartCommissioning()) 答案:B

  3. zclSampleSw_DeviceAnnce() 内部直接套用的协议栈 API 是(A. ZDP_DeviceAnnce() B. NLME_GetShortAddr() C. HalLedSet() D. osal_start_timerEx()) 答案:A

  4. 在本实验的回调表 zclSampleSw_CmdCallbacks 中,协调器角色在 On/Off 命令位置填的是(A. zclSampleSw_OnOffCB B. NULL(协调器不处理 On/Off 命令) C. zclSampleSw_BasicResetCB D. 任意处理函数均可) 答案:B

  5. zclSampleSw_OnOffCB(uint8 cmd) 收到 COMMAND_ON 时执行的动作是(A. 熄灭所有 LED B. 屏幕打印 "Set ON" 并点亮所有 LED C. 发送反转命令 D. 复位本设备) 答案:B

  6. 协调器收到插座广播的地址消息时,会产生哪个系统事件(A. ZCL_INCOMING_MSG B. ZDO_CB_MSG C. AF_INCOMING_MSG_CMD D. SYS_EVENT_MSG) 答案:B

  7. 在 zclSampleSw_processZDOMsgs() 中,协调器是根据什么判断收到的是设备入网广播的(A. pMsg->clusterID == Device_annce B. pInMsg->zclHdr.commandID C. 源端点号 D. addrMode) 答案:A

  8. 网关周期性发送 On/Off 命令依靠的机制是(A. while 死循环 B. osal_start_timerEx() 定时事件 SAMPLEAPP_ONOFF_TEST_EVT,事件处理末尾再次启动定时器形成周期发送 C. 串口接收中断 D. 无线射频中断) 答案:B

  9. zclSampleSw_OnOffTest() 中语句 on = !on; 的作用是(A. 递增数据包序号 B. 反转期望状态,使 CmdOn 与 CmdOff 交替发送 C. 清空屏幕 D. 复位目标地址) 答案:B

  10. 本实验中目标地址 destAddr.addrMode 设置为(A. Addr64Bit(64 位扩展地址) B. Addr16Bit(16 位网络地址,P2P) C. 广播地址 D. 组地址) 答案:B

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

  1. 题干:简述本实验中智能插座(终端/路由器端)需要完成的主要工作。 参考答案要点:
  • 以终端/路由器角色加入网络:bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING | BDB_COMMISSIONING_MODE_FINDING_BINDING);
  • 调用 zclSampleSw_DeviceAnnce(),内部用 ZDP_DeviceAnnce() 向全网广播本设备短地址(NLME_GetShortAddr())和扩展地址(NLME_GetExtAddr()),让协调器知道"我来了、我在哪";
  • 在回调表 zclSampleSw_CmdCallbacks 的 On/Off 位置注册 zclSampleSw_OnOffCB();
  • zclSampleSw_OnOffCB() 中判断 cmd:收到 COMMAND_ON 屏幕打印 "Set ON" 并点亮所有 LED,收到 COMMAND_OFF 打印 "Set OFF" 并熄灭所有 LED。
  1. 题干:简述网关(协调器)端从收到插座地址广播到周期性发送 On/Off 命令的完整流程。 参考答案要点:
  • 插座入网广播后,协调器产生 ZDO_CB_MSG 事件;
  • 在 zclSampleSw_processZDOMsgs() 中按 pMsg->clusterID == Device_annce 分支,把插座短地址 pMsg->srcAddr.addr.shortAddr 保存到全局变量 zclSampleSw_OnOffTestAddr,并在屏幕显示节点地址;
  • 用 osal_start_timerEx() 启动 SAMPLEAPP_ONOFF_TEST_EVT 定时事件;
  • 事件触发后调用 zclSampleSw_OnOffTest():填充目标地址(端点、Addr16Bit、插座短地址),根据静态变量 on 选择调 zclGeneral_SendOnOff_CmdOn 或 CmdOff,然后 on = !on 反转状态;
  • 事件处理末尾再次 osal_start_timerEx() 重启该事件,形成"开→关→开→关"的周期发送。
  1. 题干:本实验编译下载两块板子后,预期的仿真调试现象是什么? 参考答案要点:
  • 先后给网关和插座供电,插座自动加入网关创建的网络;
  • 网关屏幕交替显示 Command: ON 与 Command: OFF(每周期反转一次);
  • 插座屏幕交替显示 Set ON(LED 全亮)与 Set OFF(LED 全灭);
  • LED 随网关命令周期性地"呼吸"式亮灭,即说明 ZCL 命令收发成功。

aioteer · 物联网专业课程群

💬AI 伴学配置