用户工具

站点工具


zigbee:sample_light_code_review

这是本文档旧的修订版!


<markdown>

# 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)

<markdown>

zigbee/sample_light_code_review.1571907347.txt.gz · 最后更改: 2021/06/22 23:14 (外部编辑)