# sample light/switch code review 对于抓包详细走读 sample_light/switch 工程。 ## osal osal 是ti cc25x0 系列用以实现ble、zigbee 复杂协议栈的一个操作系统抽象层,算不上一个完整的操作系统,但是也完成操作系统内核的部分功能,可以总结为一个基于事件驱动的优先级任务管理,同时实现了任务间通信的基本事件、消息机制,并且实现动态内存管理。同时整个osal还维护一个时间节拍。 osal每一个完整任务由task_init和task_event_loop组成,前者完成任务初始化,后面用以处理任务通过事件被触发后的事件处理函数。每一个事件处理函数(task_event_loop)包含一个多个事件处理,其中一个事件包含消息处理,消息头会携带消息id。 如下框图所示,sample_light 应用任务包含`zclSampleLight_Init`和`zclSampleLight_event_loop` ,后者为处理任务事件,其中任务事件中有个`SYS_EVENT_MSG`特殊事件,用以处理任务消息。同样地,zcl任务也包含相同任务结构。 值得一提是,每一个任务都是《[zigbee 协议概述](http://notes.leconiot.com/zigbee_protocol_overview.html)》中的zigbee协议规范框架中的功能分层,熟悉的mac、nwk、zdo、af、zcl、bdb都会根据功能独立实现一个任务。不同层任务之间的需要通过层接口(api、回调函数)和数据接口(消息、事件)完成分层之间通信。 ![osal 任务间通信](images/osal_task_inter_comnunication.png) ## data link 在《[基于zstack 的zigbee3.0 第一个例程](http://notes.leconiot.com/zigbee_fisrt_example.html)》已经分别介绍了sample_light和sample_switch 程序功能,如下通过抓包走读代码梳理数据链路。 ### sample light 建立网络 ![协调器建立网络](images/coordinator_setup_network.png) 1. active scan; 2. 开放网络,允许设备加入; **bdb** 开始commissioning [zcl_sampleapps_ui.c#L713](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/Source/zcl_sampleapps_ui.c#L713) ```c //zcl_sampleapps_ui.c,function uiActionStartComissioning,line 694 static void uiActionStartComissioning(uint16 keys) { //.... bdb_StartCommissioning(uiSelectedBdbComissioningModes); } ``` 在`bdb_StartCommissioning` 中做了些逻辑判断后直接给`bdb_event_loop` 事件触发`BDB_CHANGE_COMMISSIONING_STATE`。 [bdb.c#L894](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L894) ```c //bdb.c,function bdb_StartCommissioning,line 894 //Start the commissioning process bdbCommissioningProcedureState.bdbCommissioningState = BDB_COMMISSIONING_STATE_START_RESUME; osal_set_event( bdb_TaskID, BDB_CHANGE_COMMISSIONING_STATE ); ``` `bdb_event_loop`事件循环中通过`BDB_CHANGE_COMMISSIONING_STATE` 并且配合状态机**`bdbCommissioningProcedureState.bdbCommissioningState` **进入`bdb_startResumeCommissioningProcess`。 [bdb.c#L2222](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L2222) ```c //bdb.c,function bdb_event_loop,line 2222 UINT16 bdb_event_loop(byte task_id, UINT16 events) { (void)task_id; // Intentionally unreferenced parameter #if (BDB_FINDING_BINDING_CAPABILITY_ENABLED==1) endPointDesc_t * bdb_CurrEpDescriptor; #endif if(events & BDB_CHANGE_COMMISSIONING_STATE) { switch(bdbCommissioningProcedureState.bdbCommissioningState) { case BDB_COMMISSIONING_STATE_START_RESUME: bdb_startResumeCommissioningProcess(); break; ``` `bdb_startResumeCommissioningProcess` 又通过**`bdbAttributes.bdbCommissioningMode`** 依次进行处理。单次进入`bdb_startResumeCommissioningProcess` 进入单次只会处理一个模式,之后通过`bdb_reportCommissioningState` 再次进入`bdb_startResumeCommissioningProcess` 处理。也就是例如默认的CommissioningMode为如下三个集合。实际程序执行会分三次进入`bdb_startResumeCommissioningProcess` 执行。 [zcl_sampleapps_ui.c#L323](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/Source/zcl_sampleapps_ui.c#L323) ```c //zcl_sampleapps_ui.c,line 323 #define DEFAULT_COMISSIONING_MODE (BDB_COMMISSIONING_MODE_NWK_STEERING | BDB_COMMISSIONING_MODE_NWK_FORMATION | BDB_COMMISSIONING_MODE_FINDING_BINDING) ``` 而如上抓包的协调器建立网络行为则是通过`bdb_startResumeCommissioningProcess` 中处理`BDB_COMMISSIONING_MODE_NWK_FORMATION` 执行的。 [bdb.c#L2086](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L2086) ```c //bdb.c,function bdb_startResumeCommissioningProcess,line 2086 if(bdbAttributes.bdbCommissioningMode & BDB_COMMISSIONING_MODE_NWK_FORMATION) { bdbCommissioningProcedureState.bdbCommissioningState = BDB_COMMISSIONING_STATE_FORMATION; if(bdbAttributes.bdbNodeCommissioningCapability & BDB_NETWORK_FORMATION_CAPABILITY) { if(!bdbAttributes.bdbNodeIsOnANetwork) { #if (BDB_TOUCHLINK_CAPABILITY_ENABLED == TRUE) bdb_ClearNetworkParams(); #endif vDoPrimaryScan = TRUE; osal_memset(&bdbCommissioningProcedureState,0,sizeof(bdbCommissioningProcedureState)); bdbCommissioningProcedureState.bdbCommissioningState = BDB_COMMISSIONING_STATE_FORMATION; bdb_nwkJoiningFormation(FALSE); bdb_NotifyCommissioningModeStart(BDB_COMMISSIONING_FORMATION); return; } } bdb_reportCommissioningState(BDB_COMMISSIONING_STATE_FORMATION, FALSE); return; ``` `bdb_nwkJoiningFormation`中调用了**zdo**层的接口,同时触发了zdo网络的初始化 [bdb.c#L664](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L664) ```c //ZDApp.c,function ZDOInitDeviceEx,line 664 uint8 ZDOInitDeviceEx( uint16 startDelay, uint8 mode) { //... if( ZDO_INIT_HOLD_NWK_START != startDelay ) { devState = DEV_INIT; // Remove the Hold state // Initialize leave control logic ZDApp_LeaveCtrlInit(); // Trigger the network start ZDApp_NetworkInit( extendedDelay ); } } ``` `ZDApp_NetworkInit` 则给`ZDApp_event_loop` 发送初始化事件`ZDO_NETWORK_INIT`。 [ZDApp.c#L406](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L406) ```c //ZDApp.c,function ZDApp_event_loop,line 406 UINT16 ZDApp_event_loop( uint8 task_id, UINT16 events ) { if ( events & ZDO_NETWORK_INIT ) { // Initialize apps and start the network ZDApp_ChangeState( DEV_INIT ); ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode, DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER ); // Return unprocessed events return (events ^ ZDO_NETWORK_INIT); } ``` [ZDObject.c#L280](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDObject.c#L280) ```c //ZDObject.c,function ZDO_StartDevice,line 280 void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder ) { //.... if ( ZG_BUILD_COORDINATOR_TYPE && logicalType == NODETYPE_COORDINATOR ) { if ( startMode == MODE_HARD ) { ZDApp_ChangeState( DEV_COORD_STARTING ); ret = NLME_NetworkFormationRequest( zgConfigPANID, zgApsUseExtendedPANID, runtimeChannel, zgDefaultStartingScanDuration, beaconOrder, ``` 之后通过`ZDO_StartDevice`调用**nwk**层接口发起建立网络原语`NLME_NetworkFormationRequest`。 至此,建立网络的请求原语就从bdb->zdo->nwk 依次传递执行,如果建立网络成功那么对应的确认又依次会从nwk->zdo->bdb。而nwk建立网络成功的消息则通过zdo注册的回调函数通知到zdo。zdo 则通过`bdb_nwkFormationAttempt` 通知到bdb。 [ZDApp.c#L2317](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L2317) ```c //ZDApp.c,function ZDO_NetworkFormationConfirmCB,line 2317 void ZDO_NetworkFormationConfirmCB( ZStatus_t Status ) { //... osal_set_event( ZDAppTaskID, ZDO_NETWORK_START ); } ``` [ZDApp.c#L418](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L418) ```c //ZDApp.c,function ZDApp_event_loop,line 418 UINT16 ZDApp_event_loop( uint8 task_id, UINT16 events ) { //... if ( ZSTACK_ROUTER_BUILD ) { if ( events & ZDO_NETWORK_START ) { ZDApp_NetworkStartEvt(); // Return unprocessed events return (events ^ ZDO_NETWORK_START); } ``` [ZDApp.c#L882](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L418) ```c //ZDApp.c,function ZDApp_NetworkStartEvt,line 882 void ZDApp_NetworkStartEvt( void ) { if ( nwkStatus == ZSuccess ) { // Successfully started a ZigBee network if ( devState == DEV_COORD_STARTING ) { //save NIB to NV before child joins if NV_RESTORE is defined ZDApp_NwkWriteNVRequest(); ZDApp_ChangeState( DEV_ZB_COORD ); if(bdbCommissioningProcedureState.bdbCommissioningState == BDB_COMMISSIONING_STATE_FORMATION) { bdb_nwkFormationAttempt(TRUE); ZDApp_StoreNwkSecMaterial(); } else if(bdbCommissioningProcedureState.bdbCommissioningState == BDB_INITIALIZATION) { bdb_reportCommissioningState(BDB_INITIALIZATION,TRUE); } ``` 之后`bdb_nwkFormationAttempt`会再次上报状态`bdb_reportCommissioningState`,此时`BDB_COMMISSIONING_STATE_FORMATION` 会通知应用层并执行开放网络工程。 [bdb.c#L1100](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L1100) ```c //bdb.c,function bdb_reportCommissioningState,line 1100 void bdb_reportCommissioningState(uint8 bdbCommissioningState,bool didSuccess) { //... if(pfnCommissioningStatusCB) { //Notify the user about the status, the main state which has failed bdbCommissioningModeMsg.bdbCommissioningStatus = bdbAttributes.bdbCommissioningStatus; bdb_NotifyApp((uint8*)&bdbCommissioningModeMsg); } } ``` [zcl_samplelight.c#L507](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleLight/Source/zcl_samplelight.c#L507) ```c //zcl_samplelight.c,function zclSampleLight_ProcessCommissioningStatus,line 507 static void zclSampleLight_ProcessCommissioningStatus(bdbCommissioningModeMsg_t *bdbCommissioningModeMsg) { switch(bdbCommissioningModeMsg->bdbCommissioningMode) { case BDB_COMMISSIONING_FORMATION: if(bdbCommissioningModeMsg->bdbCommissioningStatus == BDB_COMMISSIONING_SUCCESS) { //After formation, perform nwk steering again plus the remaining commissioning modes that has not been process yet bdb_StartCommissioning(BDB_COMMISSIONING_MODE_NWK_STEERING | bdbCommissioningModeMsg->bdbRemainingCommissioningModes); ``` [bdb.c#L791](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L791) ```c //bdb.c,function bdb_StartCommissioning,line 791 void bdb_StartCommissioning(uint8 mode) { //... //If we are on the network and got requested to do nwk steering, we do not need to wait other process, // just send permit joining and report the application if((bdbAttributes.bdbNodeIsOnANetwork) && (mode & BDB_COMMISSIONING_MODE_NWK_STEERING)) { bdb_nwkSteeringDeviceOnNwk(); ``` [bdb.c#L1996](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L1996) ```c //bdb.c,function bdb_nwkSteeringDeviceOnNwk,line 1996 void bdb_nwkSteeringDeviceOnNwk(void) { //... // Trust Center significance is always true ZDP_MgmtPermitJoinReq( &dstAddr, BDBC_MIN_COMMISSIONING_TIME, TRUE, FALSE ); } ``` 至此,如上抓包行为中的active scan和开放网络行为和代码一一对应。有了上面的代码走读,我们再来结合zigbeee specification 中的对应章节理解如上的数据交互。 * 建立网络 ![建立网络](images/establishing_a_new_network.png) > **提示**:zigbee specification .pdf->Chapter 3 Network Specification->3.6 Functional Description->3.6.1 Network and Device Maintenance->3.6.1.1 Establishing a New Network 不难看出,这里APL调用的api和如上代码流程中的一一对应。 | primitive | api | | ------------------------------ | ----------------------------- | | NLME-NETWORK-FROMATION.request | NLME_NetworkFormationRequest | | NLME-NETWORK-FROMATION.confirm | ZDO_NetworkFormationConfirmCB | * 开放网络 ![运行设备加入网络](images/permitting_devices_to_join_network.png) > **提示**:zigbee specification .pdf->Chapter 3 Network Specification->3.6 Functional Description->3.6.1 Network and Device Maintenance->3.6.1.2 Permitting Devices to Join a Network | primitive | api | | --------------------------- | --------------------- | | NLME-PERMIT-JOINING.request | ZDP_MgmtPermitJoinReq | | NLME-PERMIT-JOINING.confirm | | ### sample switch 加入网络并且交换密钥 先看抓包 ![关联加入网络并交换密钥](images/sample_switch_join_network.png) * 4-5 网络发现; * 7-14 加入网络; * 1-41 交换密钥; 这次先参考zigbee specification 梳理加入网络的数据交互,找到APL层需要交互消息原语,再来review源码。 ![加入网络](images/joinning_netwok.png) > **提示**:zigbee specification .pdf->Chapter 3 Network Specification->3.6 Functional Description->3.6.1 Network and Device Maintenance->3.6.1.4 Joining a Network | primitive | api | | ------------------------------ | ----------------------------- | | NLME-NETWORK-DISCOVERY.request | NLME_NetworkDiscoveryRequest | | NLME-NETWORK-DISCOVERY.confirm | ZDO_NetworkDiscoveryConfirmCB | | NLME-NETWORK-JOIN.request | NLME_JoinRequest | | NLME-NETWORK-JOIN.confirm | | | NLME-NETWORK-ROUTER.confirm | | | NLME-NETWORK-ROUTER.request | | * NLME-NETWORK-DISCOVERY.request 和sample light一样,**bdb** 开始commissioning [zcl_sampleapps_ui.c#L713](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/Source/zcl_sampleapps_ui.c#L713) ```c //zcl_sampleapps_ui.c,function uiActionStartComissioning,line 713 static void uiActionStartComissioning(uint16 keys) { //.... bdb_StartCommissioning(uiSelectedBdbComissioningModes); } ``` 之后的数据链路一大概一致,不同的是。`ZDO_StartDevice` 里面判断了设备类型,如果是路由或者终端设备这里直接发起网络发现原语`NLME_NetworkFormationRequest`。 [ZDObject.c#L320](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDObject.c#L320) ```c //ZDObject.c,function ZDO_StartDevice,line 320 void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder ) { //... #if defined( MANAGED_SCAN ) ZDOManagedScan_Next(); ret = NLME_NetworkDiscoveryRequest( managedScanChannelMask, BEACON_ORDER_15_MSEC ); #else ret = NLME_NetworkDiscoveryRequest( runtimeChannel, zgDefaultStartingScanDuration ); } ``` * NLME-NETWORK-DISCOVERY.confirm 同样地,之后的确认消息变成了异步,只有在zdo层注册的回调消息中等待应答。 [ZDApp.c#L2199](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L2199) ```c //ZDApp.c,function ZDO_NetworkDiscoveryConfirmCB,line 2199 ZStatus_t ZDO_NetworkDiscoveryConfirmCB(uint8 status) { //... ZDApp_SendMsg( ZDAppTaskID, ZDO_NWK_DISC_CNF, sizeof(osal_event_hdr_t), (uint8 *)&msg ); ``` 通过`bdb_nwkDiscoveryAttempt`通知bdb。 [ZDApp.c#L1177](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zdo/ZDApp.c#L1177) ```c //ZDApp.c,function ZDApp_ProcessOSALMsg,line 1177 void ZDApp_ProcessOSALMsg( osal_event_hdr_t *msgPtr ) { //... if(nwk_getNwkDescList()) { bdb_nwkDiscoveryAttempt(TRUE); } else { bdb_nwkDiscoveryAttempt(FALSE); } ``` bdb 直接发消息到bdb,消息头事件标志位为`BDB_COMMISSIONING_STATE_JOINING`。 [bdb.c#L2668](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L2668) ```c //bdb.c,function bdb_ProcessOSALMsg,line 2668 void bdb_ProcessOSALMsg( bdbInMsg_t *msgPtr ) { switch(msgPtr->hdr.event) { #if (ZG_BUILD_JOINING_TYPE) case BDB_COMMISSIONING_STATE_JOINING: if(ZG_DEVICE_JOINING_TYPE) { switch(msgPtr->buf[0]) { case BDB_JOIN_EVENT_NWK_DISCOVERY: if(msgPtr->hdr.status == BDB_MSG_EVENT_SUCCESS) { bdb_filterNwkDisc(); bdb_tryNwkAssoc(); } else { bdb_nwkDiscoveryAttempt(FALSE); } ``` * NLME-NETWORK-JOIN.request 成功处理网络发现确认结果后,bdb直接通过`bdb_tryNwkAssoc`控制关联加入网络。 [bdb.c#L1667](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L1667) ```c //bdb.c,function bdb_tryNwkAssoc,line 1667 static void bdb_tryNwkAssoc(void) { if(pBDBListNwk) { bdbCommissioningProcedureState.bdbJoinState = BDB_JOIN_STATE_ASSOC; //Try the first in the list after the filtering if(ZSuccess != bdb_joinProcess(pBDBListNwk)) { ``` [bdb.c#L1762](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L1762) ```c //bdb.c,function bdb_joinProcess,line 1762 ZStatus_t bdb_joinProcess(networkDesc_t *pChosenNwk) { ZStatus_t status; ZDApp_ChangeState( DEV_NWK_JOINING ); ZDApp_NodeProfileSync( pChosenNwk->stackProfile); status = NLME_JoinRequest( pChosenNwk->extendedPANID, pChosenNwk->panId, pChosenNwk->logicalChannel, ZDO_Config_Node_Descriptor.CapabilityFlags, pChosenNwk->chosenRouter, pChosenNwk->chosenRouterDepth ); ``` * NLME-NETWORK-JOIN.confirm 同样地,管理加入网络的确认消息通同样在zdo回调注册后,在通过bdb消息到bdb消息处理,这里不再具体review。 ### sample switch 执行开关命令 该数据完整链路是sample switch 本地检测到按键发送toggle 命令,sample light 收到toggle命令后翻转本地led。 sample switch 上面发送zcl toggle 命令的数据链路相对简单,直接找到对应的按键消息就行。对应注册在 [zcl_sampleapps_ui.c#L414](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/Source/zcl_sampleapps_ui.c#L414) ```c //zcl_sampleapps_ui.c,line 414 static const uiState_t gui_states_main[] = { //... UI_STATE_DEFAULT_MOVE, UI_KEY_SW_5_PRESSED, &uiActionAppSecificMenu}, ``` [zcl_samplesw.c#L186](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleSwitch/Source/zcl_samplesw.c#L186) ```c //zcl_samplesw.c,line 186 const uiState_t zclSampleSw_UiStatesMain[] = { /* UI_STATE_BACK_FROM_APP_MENU */ {UI_STATE_DEFAULT_MOVE, UI_STATE_TOGGLE_LIGHT, UI_KEY_SW_5_PRESSED, &UI_ActionBackFromAppMenu}, //do not change this line, except for the second item, which should point to the last entry in this menu /* UI_STATE_TOGGLE_LIGHT */ {UI_STATE_BACK_FROM_APP_MENU, UI_STATE_DEFAULT_MOVE, UI_KEY_SW_5_PRESSED | UI_KEY_SW_5_RELEASED, &zclSampleSw_UiActionToggleLight}, }; ``` 最终按键事件处理程序,直接通过`zclGeneral_SendOnOff_CmdToggle` 发送zcl toggle命令。 [zcl_samplesw.c#L742](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleSwitch/Source/zcl_samplesw.c#L742) ```c //zcl_samplesw.c,function zclSampleSw_UiActionToggleLight,line 186 void zclSampleSw_UiActionToggleLight(uint16 keys) { if (zclSampleSw_OnOffSwitchActions == ON_OFF_SWITCH_ACTIONS_TOGGLE) { if (keys & UI_KEY_SW_5_PRESSED) { zclGeneral_SendOnOff_CmdToggle( SAMPLESW_ENDPOINT, &zclSampleSw_DstAddr, FALSE, bdb_getZCLFrameCounter() ); } } } ``` ### sample light 执行远程开关命令 对于sample light 收到zcl toggle 命令执行led翻转我们需要快速理清的是和上面bdb网络交互不一样,zcl的数据链路不再通过endpoint 0到 zdo,而是在aps之上直接通过af到zcl注册的应用短点。 有了前面[zigbee 协议概述](http://notes.leconiot.com/zigbee_protocol_overview.html)系统框架的认识和之前的数据链路走读,这里不难猜测完整的数据链路应该是 af->zcl->toggle handler。 接下来,按照猜测来依次验证。 `afIncomingData` 表示af层收到的数据。 [AF.c#L386](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components\stack\af\AF.c#L386) ```c //AF.c, funciton afIncomingData, line 386 void afIncomingData( aps_FrameFormat_t *aff, zAddrType_t *SrcAddress, uint16 SrcPanId, NLDE_Signal_t *sig, uint8 nwkSeqNum, uint8 SecurityUse, uint32 timestamp, uint8 radius ) { // overwrite with descriptor's endpoint aff->DstEndPoint = epDesc->endPoint; afBuildMSGIncoming( aff, epDesc, SrcAddress, SrcPanId, sig, nwkSeqNum, SecurityUse, timestamp, radius ); ``` > **提示**:值得一提的是,这里的afIncomingData 不是以一个回到函数注册到之下的aps或者nwk层的,而且是直接被次下层的直接调用。暂时不做评论,但是确实是个非常规操作。 [AF.c#519](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components\stack\af\AF.c#L519) ```c //AF.c, funciton afBuildMSGIncoming, line 519 static void afBuildMSGIncoming( aps_FrameFormat_t *aff, endPointDesc_t *epDesc, zAddrType_t *SrcAddress, uint16 SrcPanId, NLDE_Signal_t *sig, uint8 nwkSeqNum, uint8 SecurityUse, uint32 timestamp, uint8 radius ) { //... { // Send message through task message. osal_msg_send( *(epDesc->task_id), (uint8 *)MSGpkt ); } } ``` af收到数据过后开始通过先前注册好的端点和task_id 向外分发。 而zcl的应用端点在任务初始化过程中已经注册好。 [zcl_samplelight.c#L304](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleLight/Source/zcl_samplelight.c#L304) ```c //zcl_samplelight.c, funciton zclSampleLight_Init, line 304 void zclSampleLight_Init( byte task_id ) { // Register the Simple Descriptor for this application bdb_RegisterSimpleDescriptor( &zclSampleLight_SimpleDesc ); ``` [bdb.c#L291](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/bdb/bdb.c#L291) ```c //zcl.c, funciton bdb_RegisterSimpleDescriptor, line 291 void bdb_RegisterSimpleDescriptor( SimpleDescriptionFormat_t *simpleDesc ) { endPointDesc_t *epDesc; // Register the application's endpoint descriptor // - This memory is allocated and never freed. epDesc = osal_mem_alloc( sizeof ( endPointDesc_t ) ); if ( epDesc ) { // Fill out the endpoint description. epDesc->endPoint = simpleDesc->EndPoint; epDesc->task_id = &zcl_TaskID; // all messages get sent to ZCL first epDesc->simpleDesc = simpleDesc; epDesc->latencyReq = noLatencyReqs; // Register the endpoint description with the AF afRegister( epDesc ); ``` 而这里的`zcl_TaskID` 则为zcl的任务id,所以af消息会直接传递到zcl的任务处理函数,即`zcl_event_loop` ->`SYS_EVENT_MSG`。 [zcl.c#L385](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L385) ```c //zcl.c, funciton zcl_event_loop, line 385 uint16 zcl_event_loop( uint8 task_id, uint16 events ) { uint8 *msgPtr; (void)task_id; // Intentionally unreferenced parameter if ( events & SYS_EVENT_MSG ) { msgPtr = osal_msg_receive( zcl_TaskID ); while ( msgPtr != NULL ) { uint8 dealloc = TRUE; if ( *msgPtr == AF_INCOMING_MSG_CMD ) { zcl_ProcessMessageMSG( (afIncomingMSGPacket_t *)msgPtr ); ``` `zcl_ProcessMessageMSG` 会通过cluster id解析zcl[zcl.c#L2036](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L2036)消息,并且判断这是一条基础命令还是cluster 相关的命令[zcl.c#L2039](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L2039),然后执行不同的命令处理函数[zcl.c#L2081](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L2081) 、[zcl.c#L2110](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L2110)。 [zcl.c#L970](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L1970) ```c //zcl.c, funciton zcl_ProcessMessageMSG, line 1970 zclProcMsgStatus_t zcl_ProcessMessageMSG( afIncomingMSGPacket_t *pkt ) { // Find the appropriate plugin pInPlugin = zclFindPlugin( pkt->clusterId, epDesc->simpleDesc->AppProfId ); // Is this a foundation type message if ( !interPanMsg && zcl_ProfileCmd( inMsg.hdr.fc.type ) ) { if ( (inMsg.attrCmd != NULL) && (zclCmdTable[inMsg.hdr.commandID].pfnProcessInProfile != NULL) ) { // Process the command if ( zclProcessCmd( inMsg.hdr.commandID, &inMsg ) == FALSE ) { // Couldn't find attribute in the table. } } } else { if ( pInPlugin && pInPlugin->pfnIncomingHdlr ) { // The return value of the plugin function will be // ZSuccess - Supported and need default response // ZFailure - Unsupported // ZCL_STATUS_CMD_HAS_RSP - Supported and do not need default rsp // ZCL_STATUS_INVALID_FIELD - Supported, but the incoming msg is wrong formatted // ZCL_STATUS_INVALID_VALUE - Supported, but the request not achievable by the h/w // ZCL_STATUS_SOFTWARE_FAILURE - Supported but ZStack memory allocation fails status = pInPlugin->pfnIncomingHdlr( &inMsg ); if ( status == ZCL_STATUS_CMD_HAS_RSP || ( interPanMsg && status == ZSuccess ) ) { rawAFMsg = NULL; return ( ZCL_PROC_SUCCESS ); // We're done } } } } ``` 如果是一个基础命令,通过提前注册好的注册好的`zclCmdTable` 处理[zcl.c#L2081](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L2081)。如果cluster相关私有命令,需要通过查找`zclFindPlugin`找到对应命令的处理函数[zcl.c#L2110](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L2110)。 对于`zclCmdTable` 很好理解,直接是命令和处理函数的键值表。 对于`zclFindPlugin` 则对应`zcl_registerPlugin` ,plugin 可以理解为zcl specification 中的cluster list。也就是cluster的列表。 [zcl.c#L729](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl.c#L729) ```c //zcl.c, funciton zcl_registerPlugin, line 729 ZStatus_t zcl_registerPlugin( uint16 startClusterID, uint16 endClusterID, zclInHdlr_t pfnIncomingHdlr ) { ``` 比如 toggle 命令属于on/off cluster,又属于general cluster list,所以zcl_general.c 会将该cluster list中的所有命令注册到plugin。 [zcl_general.c#L197](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.c#L197) ```c //zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 197 ZStatus_t zclGeneral_RegisterCmdCallbacks( uint8 endpoint, zclGeneral_AppCallbacks_t *callbacks ) { ... zcl_registerPlugin( ZCL_CLUSTER_ID_GEN_BASIC, ZCL_CLUSTER_ID_GEN_MULTISTATE_VALUE_BASIC, zclGeneral_HdlIncoming ); ``` 成功查找到plugin后调用其plugin->pfnIncomingHdlr处理函数也就是如上注册的`zclGeneral_HdlIncoming` 会通过`zclGeneral_FindCallbacks` 查找general cluster list 的命令处理函数。 [zcl_general.c#L1592](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.c#L1592) ```c //zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 1592 static ZStatus_t zclGeneral_HdlIncoming( zclIncoming_t *pInMsg ) { stat = zclGeneral_HdlInSpecificCommands( pInMsg ); } ``` [zcl_general.c#L1618](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.c#L1618) ```c //zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 1618 static ZStatus_t zclGeneral_HdlInSpecificCommands( zclIncoming_t *pInMsg ) { ZStatus_t stat; zclGeneral_AppCallbacks_t *pCBs; // make sure endpoint exists pCBs = zclGeneral_FindCallbacks( pInMsg->msg->endPoint ); ``` 成功查找到的general cluster list 的命令处理函数在通过cluser id 执行对应的cluster 命令处理函数。 [zcl_general.c#L1660](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.c#L1660) ```c //zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 1660 static ZStatus_t zclGeneral_HdlInSpecificCommands( zclIncoming_t *pInMsg ) { #ifdef ZCL_ON_OFF case ZCL_CLUSTER_ID_GEN_ON_OFF: stat = zclGeneral_ProcessInOnOff( pInMsg, pCBs ); break; #endif // ZCL_ON_OFF } ``` [zcl_general.c#L3046](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.c#L3046) ```c //zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 3046 static ZStatus_t zclGeneral_ProcessInOnOff( zclIncoming_t *pInMsg, zclGeneral_AppCallbacks_t *pCBs ) { //.... if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) ) { switch ( pInMsg->hdr.commandID ) { case COMMAND_OFF: case COMMAND_ON: case COMMAND_TOGGLE: if ( pCBs->pfnOnOff ) { pCBs->pfnOnOff( pInMsg->hdr.commandID ); } break; } ``` 而这里的`pCBs->pfnOnOff`则是通过之前的`zclGeneral_RegisterCmdCallbacks` 中的`callbacks` 进行注册。 [zcl_general.c#L224](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.c#L224) ```c //zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 224 ZStatus_t zclGeneral_RegisterCmdCallbacks( uint8 endpoint, zclGeneral_AppCallbacks_t *callbacks ) {pNewItem->CBs = callbacks; // Find spot in list if ( zclGenCBs == NULL ) { zclGenCBs = pNewItem; } else { // Look for end of list pLoop = zclGenCBs; while ( pLoop->next != NULL ) pLoop = pLoop->next; // Put new item at end of list pLoop->next = pNewItem; } ``` 而这里的 `callbacks`形参就对应gerneal 中的 所有cmd。 [zcl_general.h#L1414](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Components/stack/zcl/zcl_general.h#L1414) ```c //zcl_general.h, line 1414 // This means that this app sent the request for this response. typedef void (*zclGCB_LocationRsp_t)( zclLocationRsp_t *pRsp ); // Register Callbacks table entry - enter function pointers for callbacks that // the application would like to receive typedef struct { zclGCB_BasicReset_t pfnBasicReset; // Basic Cluster Reset command zclGCB_IdentifyTriggerEffect_t pfnIdentifyTriggerEffect; // Identify Trigger Effect command zclGCB_OnOff_t pfnOnOff; // On/Off cluster //... } zclGeneral_AppCallbacks_t; ``` 我们需要关系的toggle 对应这里的`pfnOnOff`,而zcl任务初始化完成了这里的gerneal cluster 命令的注册。 [zcl_samplelight.c#L307](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleLight/Source/zcl_samplelight.c#L307) ```c //zcl_samplelight.c, funciton zclSampleLight_Init, line 307 void zclSampleLight_Init( byte task_id ) { zclGeneral_RegisterCmdCallbacks( SAMPLELIGHT_ENDPOINT, &zclSampleLight_CmdCallbacks ); ``` 从而我们找到实际接收到toggle命令处理函数。 [zcl_samplelight.c#L254](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleLight/Source/zcl_samplelight.c#L254) ```c //zcl_samplelight.c, line 254 static zclGeneral_AppCallbacks_t zclSampleLight_CmdCallbacks = { zclSampleLight_BasicResetCB, // Basic Cluster Reset command NULL, // Identify Trigger Effect command zclSampleLight_OnOffCB, // On/Off cluster commands ``` [zcl_samplelight.c#L586](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleLight/Source/zcl_samplelight.c#L586) ```c //zcl_samplelight.c, funciton zclSampleLight_OnOffCB, line 586 static void zclSampleLight_OnOffCB( uint8 cmd ) { afIncomingMSGPacket_t *pPtr = zcl_getRawAFMsg(); uint8 OnOff; zclSampleLight_DstAddr.addr.shortAddr = pPtr->srcAddr.addr.shortAddr; ``` [zcl_samplelight.c#L1187](https://gitee.com/leconiot/z-stack/blob/2ef2f0ca00f3b973e8299afcfc8c3f8441edb76d/Projects/zstack/HomeAutomation/SampleLight/Source/zcl_samplelight.c#L1187) ```c //zcl_samplelight.c, funciton zclSampleLight_UpdateLedState, line 307 void zclSampleLight_UpdateLedState(void) { // set the LED1 based on light (on or off) if ( zclSampleLight_OnOff == LIGHT_ON ) { HalLedSet ( UI_LED_APP, HAL_LED_MODE_ON ); } else { HalLedSet ( UI_LED_APP, HAL_LED_MODE_OFF ); } } ``` 一些列的各种回调可能让你晕头转向,这里直接画图试图再次说明。 ![zcl 序列图](images/zcl_uml.png)