zigbee 笔记5
zigbee ZCL流程分析
现在一般淘宝上面买的zigbee开发板给的资料都是ZStack-CC2530-2.3.0-1.4.0这个版本的资料,但是这个是已经比较旧的版本了,和现在ZHA,ZLL还是有点不一样的,其中主要不一样的地方就是ZCL,自己理解来说,我觉得ZCL是TI在zigbee收发的基础函数上面封装的一套规范,通过ZCL可以直接使用ZHA和ZLL的规范,不需要管具体发送的是什么数据;
但是,好了,现在不管发送什么数据,但是我现在想要多做一个透传接口,发现就很困难了;我现在遇到的问题就是这样的,公司zigbee网关外包出去,里面有封装好透传接口,主要用于门锁的透传加密数据,但是现在终端和路由是我们自己开发,就需要把这个透传接口加上去;这时候就需要清除了解ZCL具体的收发的封装了;下面我们来分析一下:
-
主要操作的文件是zcl.c, zcl_general.c, app.c这3个文件
-
先来看应用程序吧,zcl_samplesw.c:
static zclGeneral_AppCallbacks_t zclSampleSw_CmdCallbacks = { zclSampleSw_BasicResetCB, // Basic Cluster Reset command zclSampleSw_IdentifyCB, // Identify command #ifdef ZCL_EZMODE NULL, // Identify EZ-Mode Invoke command NULL, // Identify Update Commission State command #endif NULL, // Identify Trigger Effect command zclSampleSw_IdentifyQueryRspCB, // Identify Query Response command zclgetonoff, // On/Off cluster commands NULL, // On/Off cluster enhanced command Off with Effect NULL, // On/Off cluster enhanced command On with Recall Global Scene NULL, // On/Off cluster enhanced command On with Timed Off #ifdef ZCL_LEVEL_CTRL NULL, // Level Control Move to Level command NULL, // Level Control Move command NULL, // Level Control Step command NULL, // Level Control Stop command #endif #ifdef ZCL_GROUPS NULL, // Group Response commands #endif #ifdef ZCL_SCENES NULL, // Scene Store Request command NULL, // Scene Recall Request command NULL, // Scene Response command #endif #ifdef ZCL_ALARMS NULL, // Alarm (Response) commands #endif #ifdef SE_UK_EXT NULL, // Get Event Log command NULL, // Publish Event Log command #endif NULL, // RSSI Location command NULL, // RSSI Location Response command zclDoorLockDataIn //doorlock function,在这里添加doorlock的回调函数 }; ... static void zclDoorLockDataIn(uint8 *data,uint16 len);//注册门锁数据回调函数 //具体操作函数 static void zclDoorLockDataIn(uint8 *data,uint16 len){ HalLedSet(HAL_LED_2, HAL_LED_MODE_OFF); }
-
好了,应用程序端就这么简单,在注册回调函数里面添加透传接口的回调,但是想想都知道,直接在回调列表里面添加肯定不行,那么我们看看这个结构体zclGeneral_AppCallbacks_t 的定义,来到了zcl_general.h文件,那么我们就继续照葫芦画瓢:
typedef struct { zclGCB_BasicReset_t pfnBasicReset; // Basic Cluster Reset command zclGCB_Identify_t pfnIdentify; // Identify command #ifdef ZCL_EZMODE zclGCB_IdentifyEZModeInvoke_t pfnIdentifyEZModeInvoke; // Identify EZ-Mode Invoke command zclGCB_IdentifyUpdateCommState_t pfnIdentifyUpdateCommState; // Identify Update Commission State command #endif zclGCB_IdentifyTriggerEffect_t pfnIdentifyTriggerEffect; // Identify Trigger Effect command zclGCB_IdentifyQueryRsp_t pfnIdentifyQueryRsp; // Identify Query Response command zclGCB_OnOff_t pfnOnOff; // On/Off cluster commands zclGCB_OnOff_OffWithEffect_t pfnOnOff_OffWithEffect; // On/Off cluster enhanced command Off with Effect zclGCB_OnOff_OnWithRecallGlobalScene_t pfnOnOff_OnWithRecallGlobalScene; // On/Off cluster enhanced command On with Recall Global Scene zclGCB_OnOff_OnWithTimedOff_t pfnOnOff_OnWithTimedOff; // On/Off cluster enhanced command On with Timed Off #ifdef ZCL_LEVEL_CTRL zclGCB_LevelControlMoveToLevel_t pfnLevelControlMoveToLevel; // Level Control Move to Level command zclGCB_LevelControlMove_t pfnLevelControlMove; // Level Control Move command zclGCB_LevelControlStep_t pfnLevelControlStep; // Level Control Step command zclGCB_LevelControlStop_t pfnLevelControlStop; // Level Control Stop command #endif #ifdef ZCL_GROUPS zclGCB_GroupRsp_t pfnGroupRsp; // Group Response commands #endif #ifdef ZCL_SCENES zclGCB_SceneStoreReq_t pfnSceneStoreReq; // Scene Store Request command zclGCB_SceneRecallReq_t pfnSceneRecallReq; // Scene Recall Request command zclGCB_SceneRsp_t pfnSceneRsp; // Scene Response command #endif #ifdef ZCL_ALARMS zclGCB_Alarm_t pfnAlarm; // Alarm (Response) commands #endif #ifdef SE_UK_EXT zclGCB_GetEventLog_t pfnGetEventLog; // Get Event Log command zclGCB_PublishEventLog_t pfnPublishEventLog; // Publish Event Log command #endif zclGCB_Location_t pfnLocation; // RSSI Location command zclGCB_LocationRsp_t pfnLocationRsp; // RSSI Location Response command zclGCB_DoorLock_t pfnDoorLock; //添加doorlock门锁回调函数 } zclGeneral_AppCallbacks_t;
修改这个结构体,然后定义一下zclGCB_DoorLock_t这个回调函数:
typedef void (*zclGCB_DoorLock_t)( uint8 *data,uint16 len);//我们需要透传数据和数据长度
好了,zcl_general.h文件我们也改好了;
-
下面我们要正式分析zcl流程了:
ZCL的所有数据都是在zcl.c里面的uint16 zcl_event_loop( uint8 task_id, uint16 events );这里跑的;
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 );//我们debug进去看看 } else { uint8 taskID; taskID = zcl_getExternalFoundationHandler( (afIncomingMSGPacket_t *)msgPtr ); if ( taskID != TASK_NO_TASK ) { // send it to another task to process. osal_msg_send( taskID, msgPtr ); dealloc = FALSE; } } .... }
我们看到具体处理数据的是zcl_ProcessMessageMSG( (afIncomingMSGPacket_t *)msgPtr ),进去看看,比较长:
zclProcMsgStatus_t zcl_ProcessMessageMSG( afIncomingMSGPacket_t *pkt ) { endPointDesc_t *epDesc; zclIncoming_t inMsg;//这个就是接收到的数据, zclLibPlugin_t *pInPlugin; zclDefaultRspCmd_t defautlRspCmd; uint8 options; uint8 securityEnable; uint8 interPanMsg; ZStatus_t status = ZFailure; uint8 defaultResponseSent = FALSE; // Initialize rawAFMsg = (afIncomingMSGPacket_t *)pkt; inMsg.msg = pkt; inMsg.attrCmd = NULL; inMsg.pData = NULL; inMsg.pDataLen = 0; inMsg.pData = zclParseHdr( &(inMsg.hdr), pkt->cmd.Data );//官方就是这么取数据的 inMsg.pDataLen = pkt->cmd.DataLength; inMsg.pDataLen -= (uint16)(inMsg.pData - pkt->cmd.Data); // memcpy(gStr1,inMsg.pData,30); 可以这样取数据到数组看看透传过来的数据对不对? // Temporary workaround to allow callback functions access to the // transaction sequence number. Callback functions will call // zcl_getParsedTransSeqNum() to retrieve this number. savedZCLTransSeqNum = inMsg.hdr.transSeqNum; // Find the wanted endpoint,获取端点信息 epDesc = afFindEndPointDesc( pkt->endPoint ); if ( epDesc == NULL ) { rawAFMsg = NULL; return ( ZCL_PROC_EP_NOT_FOUND ); // Error, ignore the message } if ( ( epDesc->simpleDesc == NULL ) || ( zcl_DeviceOperational( pkt->endPoint, pkt->clusterId, inMsg.hdr.fc.type, inMsg.hdr.commandID, epDesc->simpleDesc->AppProfId ) == FALSE ) ) { rawAFMsg = NULL; return ( ZCL_PROC_NOT_OPERATIONAL ); // Error, ignore the message } #if defined ( INTER_PAN ) if ( StubAPS_InterPan( pkt->srcAddr.panId, pkt->srcAddr.endPoint ) ) { // No foundation command is supported thru Inter-PAN communication. // But the Light Link cluster uses a different Frame Control format // for it's Inter-PAN messages, where the messages could be confused // with the foundation commands. if ( zcl_ProfileCmd( inMsg.hdr.fc.type ) ) { rawAFMsg = NULL; return ( ZCL_PROC_INTERPAN_FOUNDATION_CMD ); } interPanMsg = TRUE; options = AF_TX_OPTIONS_NONE; } else #endif { interPanMsg = FALSE; options = zclGetClusterOption( pkt->endPoint, pkt->clusterId ); } if ( pkt->cmd.DataLength < ZCL_VALID_MIN_HEADER_LEN ) { return ( ZCL_PROC_INVALID ); // Error, ignore the message } // Find the appropriate plugin,获取回调 pInPlugin = zclFindPlugin( pkt->clusterId, epDesc->simpleDesc->AppProfId ); // Local and remote Security options must match except for Default Response command if ( ( pInPlugin != NULL ) && !zcl_DefaultRspCmd( inMsg.hdr ) )//这里和安防有关,有些工程师需要用到密钥的,就和这里有关,我们的架构没有密钥,开发的网络,所以可以不管这里 { securityEnable = ( options & AF_EN_SECURITY ) ? TRUE : FALSE; ....... } // Is this a foundation type message, if ( !interPanMsg && zcl_ProfileCmd( inMsg.hdr.fc.type ) )//我们debug正常的onoff指令是跑到下面的else那里的,所以这里如果你的透传接口是跑到下面的代码,就要看看你的inMsg.hdr.fc.type这里的type是否赋值错误,我自己添加的时候就是出现这样的情况,所以就自己添加了特殊处理的代码,下面说。。。 { ........ } else // Not a foundation type message, so it must be specific to the cluster ID. { if ( pInPlugin && pInPlugin->pfnIncomingHdlr )//和onoff指令一样进到这里了,但是往下不了,有可能和你的透传接口的custerID有关,是否pInPlugin的custerID和透传ID一样 { // 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 );//就是这里了,执行到这里基本上已经成功,这里执行了之后会调用zcl_general.c里面的回调处理方法找到相关的处理函数再回调上去到应用层,具体处理回调函数是:static ZStatus_t zclGeneral_HdlIncoming( zclIncoming_t *pInMsg ) if ( status == ZCL_STATUS_CMD_HAS_RSP || ( interPanMsg && status == ZSuccess ) ) { rawAFMsg = NULL; return ( ZCL_PROC_SUCCESS ); // We're done } } ....... }
下面我们继续,上面说到if ( !interPanMsg && zcl_ProfileCmd( inMsg.hdr.fc.type ) ) 这里跑的和onoff指令不一样,进不到else那里,发现是这个type问题,我们往上找,看哪里有给type赋值,发现是这个函数:
inMsg.pData = zclParseHdr( &(inMsg.hdr), pkt->cmd.Data );,就是这个一开始取数据的时候执行的函数,我们进去看看:
uint8 *zclParseHdr( zclFrameHdr_t *hdr, uint8 *pData ) { // Clear the header zcl_memset( (uint8 *)hdr, 0, sizeof ( zclFrameHdr_t ) ); // Parse the Frame Control //这里处理一下门锁的cmd-type if(*pData==0x1c){//我是直接debug,然后发现透传的时候传到这里的是0x1c,所以直接进行特殊处理,后来发现manuSpecific也要进行处理,所以就写成这样了,建议如果懂真正流程的话不要和我这么改,我是不懂的 hdr->fc.type =1; hdr->fc.manuSpecific =0; }else{ hdr->fc.type = zcl_FCType( *pData ); hdr->fc.manuSpecific = zcl_FCManuSpecific( *pData ) ? 1 : 0; } if ( zcl_FCDirection( *pData ) ) { hdr->fc.direction = ZCL_FRAME_SERVER_CLIENT_DIR; } else { hdr->fc.direction = ZCL_FRAME_CLIENT_SERVER_DIR; } ... }
修改完了就可以进去下一步了,好了到了 if ( pInPlugin && pInPlugin->pfnIncomingHdlr )这里又卡住进不去了,debug看看,发现是没有找到pInPlugin ,往上找什么时候赋值,发现是:pInPlugin = zclFindPlugin( pkt->clusterId, epDesc->simpleDesc->AppProfId );这个给它赋值了,进入看看:
static zclLibPlugin_t *plugins = (zclLibPlugin_t *)NULL; ZStatus_t zclGeneral_RegisterCmdCallbacks( uint8 endpoint, zclGeneral_AppCallbacks_t *callbacks ) { zclGenCBRec_t *pNewItem; zclGenCBRec_t *pLoop; // Register as a ZCL Plugin if ( zclGenPluginRegisted == FALSE ) { zcl_registerPlugin( ZCL_CLUSTER_ID_GEN_BASIC, ZCL_CLUSTER_ID_GEN_MULTISTATE_VALUE_BASIC, zclGeneral_HdlIncoming ); //....... } } ZStatus_t zcl_registerPlugin( uint16 startClusterID, uint16 endClusterID, zclInHdlr_t pfnIncomingHdlr ) { ... // Fill in the plugin record. pNewItem->next = (zclLibPlugin_t *)NULL; pNewItem->startClusterID = startClusterID; pNewItem->endClusterID = endClusterID; pNewItem->pfnIncomingHdlr = pfnIncomingHdlr; // Find spot in list if ( plugins == NULL ) { plugins = pNewItem;//在这里,然后再往上找发现在zclGeneral_RegisterCmdCallbacks里面调用了,然后再往上找发现在应用层初始化的时候调用了zclGeneral_RegisterCmdCallbacks,开始串起来了吧,就是要注册回调才能有这样的处理,发现startClusterID=0,endClusterID= 0x0014,难怪找不到回调 } ... } static zclLibPlugin_t *zclFindPlugin( uint16 clusterID, uint16 profileID ) { zclLibPlugin_t *pLoop = plugins;//找一下这个全局变量 (void)profileID; // Intentionally unreferenced parameter while ( pLoop != NULL ) { if ( ( clusterID >= pLoop->startClusterID ) && ( clusterID <= pLoop->endClusterID ) ) //就是这里了,我们的透传ID是3073->0x0c01,看上面的分析 { return ( pLoop ); }else if(clusterID==0x0c01)//特殊处理一下雍敏网关的透传接口,因为它的透传接口不在该范围,doorlock,然后就能找到该回调了 { return ( pLoop ); } pLoop = pLoop->next; } return ( (zclLibPlugin_t *)NULL ); }
终于能进到: status = pInPlugin->pfnIncomingHdlr( &inMsg );这里了,然后百度发现这里就是回调到回调函数处理那里的:static ZStatus_t zclGeneral_HdlIncoming( zclIncoming_t *pInMsg );我们去到:zcl_general.c文件里面查看这个函数:
static ZStatus_t zclGeneral_HdlIncoming( zclIncoming_t *pInMsg ) { ZStatus_t stat = ZSuccess; #if defined ( INTER_PAN ) if ( StubAPS_InterPan( pInMsg->msg->srcAddr.panId, pInMsg->msg->srcAddr.endPoint ) ) return ( stat ); // Cluster not supported thru Inter-PAN #endif if ( zcl_ClusterCmd( pInMsg->hdr.fc.type ) )//这里我们已经在上面修改过了所以会走下一步 { // Is this a manufacturer specific command? if ( pInMsg->hdr.fc.manuSpecific == 0 )//就是这里,我们也修改过了,所以可以走下一步 { stat = zclGeneral_HdlInSpecificCommands( pInMsg );//我们进去看看 } else { // We don't support any manufacturer specific command. stat = ZFailure; } } else { // Handle all the normal (Read, Write...) commands -- should never get here stat = ZFailure; } return ( stat ); }
继续进到 stat = zclGeneral_HdlInSpecificCommands( pInMsg );函数
static ZStatus_t zclGeneral_ProcessDookLockInfo(zclIncoming_t *pInMsg, zclGeneral_AppCallbacks_t *pCBs );//定义门锁处理函数,透传接口处理 //很明显了,就是这里回调函数到应用层的,我们看看 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 );//获取回调函数指针 if ( pCBs == NULL ) return ( ZFailure ); switch ( pInMsg->msg->clusterId )//根据clusterID来分辨回调函数 { #ifdef ZCL_BASIC case ZCL_CLUSTER_ID_GEN_BASIC: stat = zclGeneral_ProcessInBasic( pInMsg, pCBs ); break; #endif // ZCL_BASIC #ifdef ZCL_IDENTIFY case ZCL_CLUSTER_ID_GEN_IDENTIFY: stat = zclGeneral_ProcessInIdentity( pInMsg, pCBs ); break; #endif // ZCL_IDENTIFY #ifdef ZCL_GROUPS case ZCL_CLUSTER_ID_GEN_GROUPS: if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) ) stat = zclGeneral_ProcessInGroupsServer( pInMsg ); else stat = zclGeneral_ProcessInGroupsClient( pInMsg, pCBs ); break; #endif // ZCL_GROUPS #ifdef ZCL_SCENES case ZCL_CLUSTER_ID_GEN_SCENES: if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) ) stat = zclGeneral_ProcessInScenesServer( pInMsg, pCBs ); else stat = zclGeneral_ProcessInScenesClient( pInMsg, pCBs ); break; #endif // ZCL_SCENES #ifdef ZCL_ON_OFF case ZCL_CLUSTER_ID_GEN_ON_OFF: stat = zclGeneral_ProcessInOnOff( pInMsg, pCBs ); break; #endif // ZCL_ON_OFF #ifdef ZCL_LEVEL_CTRL case ZCL_CLUSTER_ID_GEN_LEVEL_CONTROL: stat = zclGeneral_ProcessInLevelControl( pInMsg, pCBs ); break; #endif // ZCL_LEVEL_CTRL #ifdef ZCL_ALARMS case ZCL_CLUSTER_ID_GEN_ALARMS: if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) ) stat = zclGeneral_ProcessInAlarmsServer( pInMsg, pCBs ); else stat = zclGeneral_ProcessInAlarmsClient( pInMsg, pCBs ); break; #endif // ZCL_ALARMS #ifdef ZCL_LOCATION case ZCL_CLUSTER_ID_GEN_LOCATION: if ( zcl_ServerCmd( pInMsg->hdr.fc.direction ) ) stat = zclGeneral_ProcessInLocationServer( pInMsg, pCBs ); else stat = zclGeneral_ProcessInLocationClient( pInMsg, pCBs ); break; #endif // ZCL_LOCATION case ZCL_CLUSTER_ID_DOORLOCK_INFO://参考onoff的回调函数,我们写好这个回调,这里要添加一些clusterID,在zcl.h文件里面: /* ..... // Light Link cluster #define ZCL_CLUSTER_ID_LIGHT_LINK 0x1000 //door lock self control ID门锁自定义透传ID #define ZCL_CLUSTER_ID_DOORLOCK_INFO 0x0C01 ..... */ stat = zclGeneral_ProcessDookLockInfo( pInMsg, pCBs ); break; case ZCL_CLUSTER_ID_GEN_POWER_CFG: case ZCL_CLUSTER_ID_GEN_DEVICE_TEMP_CONFIG: case ZCL_CLUSTER_ID_GEN_ON_OFF_SWITCH_CONFIG: case ZCL_CLUSTER_ID_GEN_TIME: default: stat = ZFailure; break; } return ( stat ); } //参考onoff的处理函数进行修改 //door lock process门锁处理函数 static ZStatus_t zclGeneral_ProcessDookLockInfo(zclIncoming_t *pInMsg, zclGeneral_AppCallbacks_t *pCBs ) { ZStatus_t stat = ZSuccess; if ( pCBs->pfnDoorLock ) { pCBs->pfnDoorLock( pInMsg->pData,pInMsg->pDataLen ); } return ( stat ); }
至此,我们已经实现了这个回调功能了更加多的功能以后再说。