sample light/switch 代码走读

z-stack,zigbee,cc2530 2019-09-26 202 次浏览 次点赞


sample light/switch code review

对于抓包详细走读 sample_light/switch 工程。

I. osal

osal 是ti cc25x0 系列用以实现ble、zigbee 复杂协议栈的一个操作系统抽象层,算不上一个完整的操作系统,但是也完成操作系统内核的部分功能,可以总结为一个基于事件驱动的优先级任务管理,同时实现了任务间通信的基本事件、消息机制,并且实现动态内存管理。同时整个osal还维护一个时间节拍。

osal每一个完整任务由task_init和task_event_loop组成,前者完成任务初始化,后面用以处理任务通过事件被触发后的事件处理函数。每一个事件处理函数(task_event_loop)包含一个多个事件处理,其中一个事件包含消息处理,消息头会携带消息id。

如下框图所示,sample_light 应用任务包含zclSampleLight_InitzclSampleLight_event_loop ,后者为处理任务事件,其中任务事件中有个SYS_EVENT_MSG特殊事件,用以处理任务消息。同样地,zcl任务也包含相同任务结构。

值得一提是,每一个任务都是《zigbee 协议概述》中的zigbee协议规范框架中的功能分层,熟悉的mac、nwk、zdo、af、zcl、bdb都会根据功能独立实现一个任务。不同层任务之间的需要通过层接口(api、回调函数)和数据接口(消息、事件)完成分层之间通信。

osal 任务间通信

在《基于zstack 的zigbee3.0 第一个例程》已经分别介绍了sample_light和sample_switch 程序功能,如下通过抓包走读代码梳理数据链路。

sample light 建立网络

协调器建立网络

  1. active scan;
  2. 开放网络,允许设备加入;

bdb 开始commissioning

zcl_sampleapps_ui.c#L713

//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

//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

//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

//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

//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

//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

//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

//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

//ZDApp.c,function ZDO_NetworkFormationConfirmCB,line 2317
void ZDO_NetworkFormationConfirmCB( ZStatus_t Status ) {
    //...
    osal_set_event( ZDAppTaskID, ZDO_NETWORK_START );
}

ZDApp.c#L418

//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

//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

//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

//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

//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

//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 中的对应章节理解如上的数据交互。

  • 建立网络

    建立网络

    提示: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和如上代码流程中的一一对应。

    primitiveapi
    NLME-NETWORK-FROMATION.requestNLME_NetworkFormationRequest
    NLME-NETWORK-FROMATION.confirmZDO_NetworkFormationConfirmCB
  • 开放网络

    运行设备加入网络

    提示: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
    primitiveapi
    NLME-PERMIT-JOINING.requestZDP_MgmtPermitJoinReq
    NLME-PERMIT-JOINING.confirm

sample switch 加入网络并且交换密钥

先看抓包

关联加入网络并交换密钥

  • 4-5 网络发现;
  • 7-14 加入网络;
  • 1-41 交换密钥;

这次先参考zigbee specification 梳理加入网络的数据交互,找到APL层需要交互消息原语,再来review源码。

加入网络

提示: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
primitiveapi
NLME-NETWORK-DISCOVERY.requestNLME_NetworkDiscoveryRequest
NLME-NETWORK-DISCOVERY.confirmZDO_NetworkDiscoveryConfirmCB
NLME-NETWORK-JOIN.requestNLME_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

    //zcl_sampleapps_ui.c,function uiActionStartComissioning,line 713
    static void uiActionStartComissioning(uint16 keys) {
        //....
        bdb_StartCommissioning(uiSelectedBdbComissioningModes);
    }

    之后的数据链路一大概一致,不同的是。ZDO_StartDevice 里面判断了设备类型,如果是路由或者终端设备这里直接发起网络发现原语NLME_NetworkFormationRequest

    ZDObject.c#L320

    //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

    //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

    //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

    //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

    //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

    //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

//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

//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

//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 协议概述系统框架的认识和之前的数据链路走读,这里不难猜测完整的数据链路应该是

af->zcl->toggle handler。

接下来,按照猜测来依次验证。

afIncomingData 表示af层收到的数据。

AF.c#L386

//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

//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

//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

//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

//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解析zclzcl.c#L2036消息,并且判断这是一条基础命令还是cluster 相关的命令zcl.c#L2039,然后执行不同的命令处理函数zcl.c#L2081zcl.c#L2110

zcl.c#L970

//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。如果cluster相关私有命令,需要通过查找zclFindPlugin找到对应命令的处理函数zcl.c#L2110

对于zclCmdTable 很好理解,直接是命令和处理函数的键值表。

对于zclFindPlugin 则对应zcl_registerPlugin ,plugin 可以理解为zcl specification 中的cluster list。也就是cluster的列表。

zcl.c#L729

//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

//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

//zcl_general.c, function zclGeneral_RegisterCmdCallbacks,line 1592
static ZStatus_t zclGeneral_HdlIncoming( zclIncoming_t *pInMsg ) {
          stat = zclGeneral_HdlInSpecificCommands( pInMsg );
}

zcl_general.c#L1618

//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

//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

//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

//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

//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

//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

//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

//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

//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 序列图


本文由 lijie 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处,点赞4

2 条评论

  1. lijie
    lijie

    明天再贴代码具体对应工程文件行位置。

  2. huahua
    huahua

    写的很详细啊

添加新评论