对于抓包详细走读 sample_light/switch 工程。
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 协议概述》中的zigbee协议规范框架中的功能分层,熟悉的mac、nwk、zdo、af、zcl、bdb都会根据功能独立实现一个任务。不同层任务之间的需要通过层接口(api、回调函数)和数据接口(消息、事件)完成分层之间通信。
在《基于zstack 的zigbee3.0 第一个例程》已经分别介绍了sample_light和sample_switch 程序功能,如下通过抓包走读代码梳理数据链路。
bdb 开始commissioning
//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,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,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,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,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网络的初始化
//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,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,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,function ZDO_NetworkFormationConfirmCB,line 2317
void ZDO_NetworkFormationConfirmCB( ZStatus_t Status ) {
//...
osal_set_event( ZDAppTaskID, ZDO_NETWORK_START );
}
//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,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,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,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,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,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和如上代码流程中的一一对应。
primitive | api |
---|---|
NLME-NETWORK-FROMATION.request | NLME_NetworkFormationRequest |
NLME-NETWORK-FROMATION.confirm | ZDO_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
primitive | api |
---|---|
NLME-PERMIT-JOINING.request | ZDP_MgmtPermitJoinReq |
NLME-PERMIT-JOINING.confirm |
先看抓包
这次先参考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
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,function uiActionStartComissioning,line 713
static void uiActionStartComissioning(uint16 keys) {
//....
bdb_StartCommissioning(uiSelectedBdbComissioningModes);
}
之后的数据链路一大概一致,不同的是。ZDO_StartDevice
里面判断了设备类型,如果是路由或者终端设备这里直接发起网络发现原语NLME_NetworkFormationRequest
。
//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,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,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,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,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,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 本地检测到按键发送toggle 命令,sample light 收到toggle命令后翻转本地led。
sample switch 上面发送zcl toggle 命令的数据链路相对简单,直接找到对应的按键消息就行。对应注册在
//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,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,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 收到zcl toggle 命令执行led翻转我们需要快速理清的是和上面bdb网络交互不一样,zcl的数据链路不再通过endpoint 0到 zdo,而是在aps之上直接通过af到zcl注册的应用短点。
有了前面zigbee 协议概述系统框架的认识和之前的数据链路走读,这里不难猜测完整的数据链路应该是
af->zcl->toggle handler。
接下来,按照猜测来依次验证。
afIncomingData
表示af层收到的数据。
//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, 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, funciton zclSampleLight_Init, line 304
void zclSampleLight_Init( byte task_id )
{
// Register the Simple Descriptor for this application
bdb_RegisterSimpleDescriptor( &zclSampleLight_SimpleDesc );
//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, 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#L2081 、zcl.c#L2110。
//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, 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, 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, function zclGeneral_RegisterCmdCallbacks,line 1592
static ZStatus_t zclGeneral_HdlIncoming( zclIncoming_t *pInMsg ) {
stat = zclGeneral_HdlInSpecificCommands( pInMsg );
}
//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, 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, 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, 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, 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, funciton zclSampleLight_Init, line 307
void zclSampleLight_Init( byte task_id )
{
zclGeneral_RegisterCmdCallbacks( SAMPLELIGHT_ENDPOINT, &zclSampleLight_CmdCallbacks );
从而我们找到实际接收到toggle命令处理函数。
//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, 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, 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 );
}
}
一些列的各种回调可能让你晕头转向,这里直接画图试图再次说明。