4.1 ZCL 简介
一句话理解
ZCL 是夹在应用层和 AF 层之间的"通用语言层"。它的使命只有一个:让不同厂商的 ZigBee 设备能互相听懂。
为什么需要 ZCL?
前面第3章我们直接用 AF 层 API 发数据——这没问题,但有个大坑:不同公司开发的设备难以互联互通。A 公司把"开灯"编成 0x01,B 公司编成 0x02,网关收到数据一脸懵。
ZigBee 联盟的解决办法:在 AF 层之上加一个 ZCL 层,把设备功能统一标准化。
通俗类比
想象全世界的餐厅都使用同一套菜单模板:菜名编号、口味标准、价格单位都统一。客人不管走进哪家店都能点菜,厨师不管在哪家店都能做菜——ZCL 就是物联网世界的"统一菜单模板"。
ZCL 层的架构
ZCL 的大部分内容位于 Z-Stack 3.0 的以下目录中:
...\Z-Stack 3.0.1\Components\stack\zcl该目录下有大量以 zcl_ 开头的文件,每个文件对应一类标准功能(如 zcl_general.c 通用功能、zcl_lighting.c 照明、zcl_hvac.c 暖通空调等)。
一个设备一般只会用到 ZCL 中的部分内容。例如本书一直用的 SampleSwitch 工程,只引用了这些 ZCL 文件:
代码分析:ZCL 怎么发数据?
ZCL 层的数据发送,最终还是会调用 AF 层的 AF_DataRequest()——ZCL 的底层就是 AF 层。
打开 zcl.h,找到 ZCL 层数据发送函数 zcl_SendCommand():
ZStatus_t zcl_SendCommand( uint8 srcEP, afAddrType_t *destAddr,
uint16 clusterID, uint8 cmd, uint8 specific, uint8 direction,
uint8 disableDefaultRsp, uint16 manuCode, uint8 seqNum,
uint16 cmdFormatLen, uint8 *cmdFormat )
{
endPointDesc_t *epDesc;
zclFrameHdr_t hdr;
uint8 *msgBuf;
uint16 msgLen;
uint8 *pBuf;
uint8 options;
ZStatus_t status;
epDesc = afFindEndPointDesc( srcEP );
if ( epDesc == NULL )
{
return ( ZInvalidParameter );
}
options = zclGetClusterOption( srcEP, clusterID );
...
// 组装 ZCL 报文头
zcl_memset( &hdr, 0, sizeof( zclFrameHdr_t ) );
hdr.fc.type = ...; // 命令类型:通用命令 / 簇限定命令
hdr.fc.direction = ...; // 通信方向:Client→Server 或反向
hdr.transSeqNum = seqNum; // 事务序号
hdr.commandID = cmd; // 命令 ID
// 计算并分配发送缓冲区
msgLen = zclCalcHdrSize( &hdr );
msgLen += cmdFormatLen;
msgBuf = zcl_mem_alloc( msgLen );
...
pBuf = zclBuildHdr( &hdr, msgBuf ); // 填 ZCL 头
zcl_memcpy( pBuf, cmdFormat, cmdFormatLen ); // 填命令数据
// ← 关键:最终还是调用 AF 层发送函数
status = AF_DataRequest( destAddr, epDesc, clusterID, msgLen, msgBuf,
&zcl_TransID, options, AF_DEFAULT_RADIUS );
...
return ( status );
}可以看到 zcl_SendCommand() 内部最终调用的是 AF_DataRequest() ——印证了 ZCL 层是"站在 AF 层肩膀上"的。
代码分析:ZCL 怎么收数据?
在 zcl.c 中有 zcl_event_loop() 函数,负责 ZCL 层的数据接收和处理:
uint16 zcl_event_loop( uint8 task_id, uint16 events )
{
uint8 *msgPtr;
...
if ( events & SYS_EVENT_MSG )
{
msgPtr = osal_msg_receive( zcl_TaskID );
while ( msgPtr != NULL )
{
if ( *msgPtr == AF_INCOMING_MSG_CMD ) // AF 层数据到达
{
zcl_ProcessMessageMSG( (afIncomingMSGPacket_t *)msgPtr ); // ZCL 层处理
}
else
{
uint8 taskID = zcl_getExternalFoundationHandler(
(afIncomingMSGPacket_t *)msgPtr );
if ( taskID != TASK_NO_TASK )
{
// 交给其他任务处理
}
}
...
}
}
...
}同样的套路:AF 层收到数据 → 产生 AF_INCOMING_MSG_CMD 事件 → ZCL 层的 event_loop 接住并分发。
小结
ZCL 不是替代 AF,而是在 AF 之上加一层标准:
- 发数据:
zcl_SendCommand()→ 内部调用AF_DataRequest() - 收数据:
zcl_event_loop()→ 接住 AF 层事件并处理
理解了这条链,下一节我们就可以看 ZCL 具体定义了哪些"标准零件"。
内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第四部分·4.1 ZCL 简介,原站链接
本章自测
一、单项选择题(每题 4 分,共 40 分)
ZCL 在 ZigBee 协议栈中的位置是(A. 在 AF 层之下、APS 之上 B. 夹在应用层和 AF 层之间,是"通用语言层" C. 等同于 PHY 层 D. 替代 AF 层) 答案:B
引入 ZCL 的最主要目的是(A. 提高无线发射功率 B. 让不同厂商的 ZigBee 设备能互相听懂、实现互联互通 C. 减少设备功耗 D. 加快组网速度) 答案:B
书中用"全世界餐厅使用同一套菜单模板"来类比 ZCL,这一比喻主要说明 ZCL 的什么特性(A. ZCL 规定了设备功能的统一标准描述 B. ZCL 负责无线电收发 C. ZCL 是一种编程语言 D. ZCL 只适用于餐饮行业) 答案:A
ZCL 的大部分内容在 Z-Stack 3.0 中的存放目录是(A.
Components\stack\zclB.ProfileC.AppD.Tools) 答案:A下列文件中,负责 On/Off、Level Control 等通用簇的是(A. zcl_ota.c B. zcl_green_power.c C. zcl_general.c D. zcl_diagnostic.c) 答案:C
关于 ZCL 层的数据发送,下列说法正确的是(A. ZCL 绕开 AF,直接操作无线电 B.
zcl_SendCommand()内部最终还是调用 AF 层的AF_DataRequest()C. ZCL 不能发送数据 D. ZCL 发送时不需要 Cluster ID) 答案:B在
zcl_SendCommand()组装 ZCL 报文头时,hdr.fc.direction字段表示(A. 事务序号 B. 通信方向(Client→Server 或反向) C. 命令 ID D. 缓冲区长度) 答案:BZCL 层接收数据时,AF 层数据到达会产生的事件是(A.
AF_INCOMING_MSG_CMDB.SAMPLEAPP_P2P_EVTC.KEY_EVTD.SYS_EVENT_MSG中的自定义事件) 答案:Azcl_event_loop()收到AF_INCOMING_MSG_CMD后,主要调用哪个函数进行 ZCL 层处理(A.AF_DataRequest()B.zcl_ProcessMessageMSG()C.afRegister()D.bdb_StartCommissioning()) 答案:B关于 ZCL 与 AF 的关系,下列说法正确的是(A. ZCL 完全替代了 AF B. ZCL 不是替代 AF,而是在 AF 之上加一层标准,发数据走
zcl_SendCommand()→AF_DataRequest(),收数据由zcl_event_loop()接住 AF 事件 C. ZCL 与 AF 互不通信 D. AF 位于 ZCL 之上) 答案:B
二、问答题(每题 20 分,共 60 分)
- 题干:为什么直接用 AF 层 API 发数据会有"不同公司设备难以互联互通"的问题?ZCL 是如何解决的? 参考答案要点:
- 直接用 AF 发数据时,"开灯"等业务含义由各公司自行编码(如 A 公司编 0x01、B 公司编 0x02),网关收到数据无法理解。
- ZigBee 联盟在 AF 层之上加一个 ZCL 层,把设备功能统一标准化(统一 Profile / Device / Cluster / 属性 / 命令的编号和含义)。
- 这样不同厂商按同一套标准描述功能,设备之间就能互相听懂。
- 题干:一个设备通常不会用到 ZCL 的全部内容。请列举书中 SampleSwitch 工程引用的几类 ZCL 文件及其作用。 参考答案要点:
- zcl.c / zcl.h:ZCL 核心,负责命令收发、事件处理。
- zcl_general.c / .h:通用簇(On/Off、Level Control 等)。
- zcl_ha.c / .h:ZHA 家居自动化 Profile 定义。
- 此外还可能用到 zcl_diagnostic(诊断簇)、zcl_green_power(绿色能源)、zcl_ota(在线升级)等。
- 题干:简述 ZCL 层"发数据"和"收数据"的完整调用链。 参考答案要点:
- 发送:应用调用
zcl_SendCommand()→ 组装 ZCL 报文头(命令类型、方向、事务序号、命令 ID)→ 分配缓冲区并填入头和命令数据 → 最终调用 AF 层AF_DataRequest()发出。 - 接收:AF 层收到数据 → 产生
AF_INCOMING_MSG_CMD事件 → ZCL 层zcl_event_loop()接住 → 调用zcl_ProcessMessageMSG()处理,无法处理的可转交给其他任务。 - 一句话:ZCL 站在 AF 层肩膀上,发收两端最终都落到 AF 的收发机制上。