这里主要再讨论一下绑定的机制,绑定是ZigBee中应该是比较重要的一个部分。前面的几篇文章也对绑定有了具体的分析,主要分析了两种绑定方式,介绍了绑定的流程,源代码方面。这里主要是理清整个绑定在组网中的概念。绑定是和EndPoint紧密联系在一起的,其中很多是自己通过看资料,自己的一些理解,当中肯定有不正确的地方,欢迎有兴趣的一起讨论。 ZigBee中还有一个重要的概念端点,理解的不是很清楚,现在再一次总结分析一下,端点是应用对象存在的地方,ZigBee允许多个应用同时位于一个节点上,例如一个节点具有控制灯光的功能,又具有感应温度的功能,又具有收发文本消息的功能,这种设计有利于复杂ZigBee设备的出现。我们可以从ZigBee的体系结构图中可以看到可以有240个应用,也就是说一个物理的ZigBee设备的话,可以有240个具体的应用,例如上面提到的其中的三种具体的应用。 一共有二个特殊的端点,即端点0和端点255。端点0用于整个ZigBee设备的配置和管理。应用程序可以透过端点0与ZigBee堆栈的其它层通讯,因而实现对这些层的初始化和配置。附属在端点0的对象被称为ZigBee设备对象(ZDO)。端点255用于向所有端点的广播。端点241到254是保留端点。 typedef struct 下面是在SerialApp程序中简单描述符的定义。 const SimpleDescriptionFormat_t SerialApp_SimpleDesc = { SERIALAPP_ENDPOINT, // int Endpoint; SERIALAPP_PROFID, // uint16 AppProfId[2]; SERIALAPP_DEVICEID, // uint16 AppDeviceId[2]; SERIALAPP_DEVICE_VERSION, // int AppDevVer:4; SERIALAPP_FLAGS, // int AppFlags:4; SERIALAPP_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)SerialApp_ClusterList, // byte *pAppInClusterList; SERIALAPP_MAX_CLUSTERS, // byte AppNumOutClusters; (cId_t *)SerialApp_ClusterList // byte *pAppOutClusterList; }; const endPointDesc_t SerialApp_epDesc = { SERIALAPP_ENDPOINT, &SerialApp_TaskID, (SimpleDescriptionFormat_t *)&SerialApp_SimpleDesc, noLatencyReqs }; 在TI给的例子中都只是定义了一个端点,猜想是不是每一个应用中都必须有一个相应的端点,也就会有一个相应的端点描述符。例如这里的三个应用,一个节点具有控制灯光的功能,又具有感应温度的功能,又具有收发文本消息的功能,那么需要占用三个端点号,也就会需要三个端点的描述符,因为在发送数据的函数中会用到这个端点的描述符。 AF_DataRequest( &SerialApp_RspDstAddr, (endPointDesc_t *)&SerialApp_epDesc, SERIALAPP_CLUSTERID2, SERIAL_APP_RSP_CNT, rspBuf, &SerialApp_MsgID, 0, AF_DEFAULT_RADIUS ); 在绑定的应用中必须要用到端点的描述符,可不可以这样理解就是说,在绑定时其实并不是两个设备之间的绑定,其实质是在这两个设备上两个端点之间的绑定,再进一步说就是两个应用之间的绑定,因为在一个终端中可能有很多的应用,也就是端点,可不以在一个端点中只和其中的一个应用进行绑定,也就是只和其中的一个端点进行绑定,其他的端点可以使用别的地址方式,如直接地址模式,或者是广播地址的方式,进行数据的处理。虽然和同一个设备进行了绑定,但是其中的应用并不相同。描述符匹配是不是就是这个意思。如果两个设备没有相同的描述符是不会绑定成功的。 case Match_Desc_rsp: { ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg ); if ( pRsp ) { if ( pRsp->status == ZSuccess && pRsp->cnt ) { SerialApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit; SerialApp_DstAddr.addr.shortAddr = pRsp->nwkAddr; // Take the first endpoint, Can be changed to search through endpoints SerialApp_DstAddr.endPoint = pRsp->epList[0];
// Light LED HalLedSet( HAL_LED_4, HAL_LED_MODE_ON ); } osal_mem_free( pRsp ); } } Break; 在下面的函数中是绑定必须要执行一个处理函数,这个函数就是处理和回应对Match_Desc_req 消息。这个函数在绑定的第三篇文章中也有分析到。 void ZDO_ProcessMatchDescReq( zdoIncomingMsg_t *inMsg ) { uint8 epCnt = 0; uint8 numInClusters; uint16 *inClusters = NULL; uint8 numOutClusters; uint16 *outClusters = NULL; epList_t *epDesc; SimpleDescriptionFormat_t *sDesc = NULL; uint8 allocated; uint8 *msg; uint16 aoi; uint16 profileID; // Parse the incoming message msg = inMsg->asdu; aoi = BUILD_UINT16( msg[0], msg[1] ); profileID = BUILD_UINT16( msg[2], msg[3] ); msg += 4;
if ( ADDR_BCAST_NOT_ME == NLME_IsAddressBroadcast(aoi) ) { ZDP_MatchDescRsp( inMsg->TransSeq, &(inMsg->srcAddr), ZDP_INVALID_REQTYPE, ZDAppNwkAddr.addr.shortAddr, 0, NULL, inMsg->SecurityUse ); return; } else if ( (ADDR_NOT_BCAST == NLME_IsAddressBroadcast(aoi)) && (aoi != ZDAppNwkAddr.addr.shortAddr) ) { ZDP_MatchDescRsp( inMsg->TransSeq, &(inMsg->srcAddr), ZDP_INVALID_REQTYPE, ZDAppNwkAddr.addr.shortAddr, 0, NULL, inMsg->SecurityUse ); return; } numInClusters = *msg++; if ( numInClusters ) { inClusters = (uint16*)osal_mem_alloc( numInClusters * sizeof( uint16 ) ); msg = ZDO_ConvertOTAClusters( numInClusters, msg, inClusters ); } numOutClusters = *msg++; if ( numOutClusters ) { outClusters = (uint16 *)osal_mem_alloc( numOutClusters * sizeof( uint16 ) ); msg = ZDO_ConvertOTAClusters( numOutClusters, msg, outClusters ); } /* First count the number of endpoints that match. typedef struct { endPointDesc_t *epDesc; eEP_Flags flags; pDescCB pfnDescCB; // Don't use if this function pointer is NULL. void *nextDesc; } epList_t; */ epDesc = epList; while ( epDesc )//扫描本节点的全部EP,看是否有匹配的?这个是一个链表的形式存储的。 { // Don't search endpoint 0 and check if response is allowed //不扫描端点0 if ( epDesc->epDesc->endPoint != ZDO_EP && (epDesc->flags&eEP_AllowMatch) ) { if ( epDesc->pfnDescCB ) { sDesc = (SimpleDescriptionFormat_t *)epDesc->pfnDescCB( AF_DESCRIPTOR_SIMPLE, epDesc->epDesc->endPoint ); allocated = TRUE; } else { sDesc = epDesc->epDesc->simpleDesc; allocated = FALSE; } if ( sDesc && sDesc->AppProfId == profileID )//如果profileID相同,这里也就说明了为什么会在绑定的时候要求profileID相同了。sDesc不为空的话, { { uint8 *uint8Buf = (uint8 *)ZDOBuildBuf; // If there are no search input/ouput clusters – respond /在这里对EP中的输入/输出簇列表进行了比较,也就是ZDP_MatchDescReq发送来的,SerialApp_ClusterList if ( ((numInClusters == 0) && (numOutClusters == 0)) // Are there matching input clusters? || (ZDO_AnyClusterMatches( numInClusters, inClusters, sDesc->AppNumInClusters, sDesc->pAppInClusterList )) // Are there matching output clusters? || (ZDO_AnyClusterMatches( numOutClusters, outClusters, sDesc->AppNumOutClusters, sDesc->pAppOutClusterList )) ) { // Notify the endpoint of the match. 通知终端匹配 uint8 bufLen = sizeof( ZDO_MatchDescRspSent_t ) + (numOutClusters + numInClusters) * sizeof(uint16); ZDO_MatchDescRspSent_t *pRspSent = (ZDO_MatchDescRspSent_t *) osal_msg_allocate( bufLen ); if (pRspSent) { pRspSent->hdr.event = ZDO_MATCH_DESC_RSP_SENT; pRspSent->nwkAddr = inMsg->srcAddr.addr.shortAddr; pRspSent->numInClusters = numInClusters; pRspSent->numOutClusters = numOutClusters; if (numInClusters) { pRspSent->pInClusters = (uint16*) (pRspSent + 1); osal_memcpy(pRspSent->pInClusters, inClusters, numInClusters * sizeof(uint16)); } else { pRspSent->pInClusters = NULL; } if (numOutClusters) { pRspSent->pOutClusters = (uint16*)(pRspSent + 1) + numInClusters; osal_memcpy(pRspSent->pOutClusters, outClusters, numOutClusters * sizeof(uint16)); } else { pRspSent->pOutClusters = NULL; } osal_msg_send( *epDesc->epDesc->task_id, (uint8 *)pRspSent ); } uint8Buf[epCnt++] = sDesc->EndPoint;// 匹配EndPoint列表,这个就是Match_Desc_rsp回传的内容之一 } } if ( allocated ) osal_mem_free( sDesc ); } epDesc = epDesc->nextDesc; } // Send the message only if at least one match found. 如果发现至少有一个匹配的以后,发送匹配消息 if ( epCnt ) { if ( ZSuccess == ZDP_MatchDescRsp( inMsg->TransSeq, &(inMsg->srcAddr), ZDP_SUCCESS, ZDAppNwkAddr.addr.shortAddr, epCnt, (uint8 *)ZDOBuildBuf, inMsg->SecurityUse ) ) { #if defined( LCD_SUPPORTED ) HalLcdWriteScreen( "Match Desc Req", "Rsp Sent" ); #endif } } else { #if defined( LCD_SUPPORTED ) HalLcdWriteScreen( "Match Desc Req", "Non Matched" ); #endif }
if ( inClusters ) osal_mem_free( inClusters ); if ( outClusters ) osal_mem_free( outClusters ); } 从上面的代码内容中也可以看到,当两个节点绑定时,进行的匹配实质是描述符之间的匹配。当然绑定时可能有多个的描述符的匹配,不知道我这样的理解是否正确,如果网友有这方面的经验欢迎讨论一下!! 这里还有一点就是绑定服务只能在“互补”设备之间建立。那就是,只有分别在两个节点的简单描述结构体(simple descriptor structure)中,同时注册了相同的命令标识符(command_id)并且方向相反(一个属于输出指令“output”,另一个属于输入指令“input”),才能成功建立绑定。 通过查看TI给我例子程序中,只有开头的例程才算是真正意义上的一个是输出,一个是输入。 对于灯结点是输入, const cId_t zb_InCmdList[NUM_IN_CMD_CONTROLLER] = { TOGGLE_LIGHT_CMD_ID }; // Define SimpleDescriptor for Switch device const SimpleDescriptionFormat_t zb_SimpleDesc = { MY_ENDPOINT_ID, // Endpoint MY_PROFILE_ID, // Profile ID DEV_ID_CONTROLLER, // Device ID DEVICE_VERSION_CONTROLLER, // Device Version 0, // Reserved NUM_IN_CMD_CONTROLLER, // Number of Input Commands (cId_t *) zb_InCmdList, // Input Command List NUM_OUT_CMD_CONTROLLER, // Number of Output Commands (cId_t *) NULL // Output Command List }; 对于开关结点是输出。 const cId_t zb_OutCmdList[NUM_OUT_CMD_SWITCH] = { TOGGLE_LIGHT_CMD_ID }; // Define SimpleDescriptor for Switch device const SimpleDescriptionFormat_t zb_SimpleDesc = { MY_ENDPOINT_ID, // Endpoint MY_PROFILE_ID, // Profile ID DEV_ID_SWITCH, // Device ID DEVICE_VERSION_SWITCH, // Device Version 0, // Reserved NUM_IN_CMD_SWITCH, // Number of Input Commands (cId_t *) NULL, // Input Command List NUM_OUT_CMD_SWITCH, // Number of Output Commands (cId_t *) zb_OutCmdList // Output Command List }; 可以在GenericApp和 SerialApp的例程中看到,并没有严格的按照这样的命令来。例如下面的SampleApp例程中,当然这样也是两个设备中的命令也是相反的,只是没有开关那个例程中更加直观, const cId_t GenericApp_ClusterList[GENERICAPP_MAX_CLUSTERS] = { GENERICAPP_CLUSTERID }; const SimpleDescriptionFormat_t GenericApp_SimpleDesc = { GENERICAPP_ENDPOINT, // int Endpoint; GENERICAPP_PROFID, // uint16 AppProfId[2]; GENERICAPP_DEVICEID, // uint16 AppDeviceId[2]; GENERICAPP_DEVICE_VERSION, // int AppDevVer:4; GENERICAPP_FLAGS, // int AppFlags:4; GENERICAPP_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)GenericApp_ClusterList, // byte *pAppInClusterList; GENERICAPP_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)GenericApp_ClusterList // byte *pAppInClusterList; }; APS绑定表是在静态RAM中定义的一张表,定义在nwk_globals.c中。表的大小可以通过f8wConfig.cfg中的 /* Maximum number of entries in the Binding table. */ -DNWK_MAX_BINDING_ENTRIES=10 /* Maximum number of cluster IDs for each binding table entry. */ -DMAX_BINDING_CLUSTER_IDS=5 只有定义了REFLECTOR或者COORDINATOR_BINDING才能包含此表,用REFLECTOR编译选项来支持APS层的源绑定。 邦定表结构 – BindingEntry_t typedef struct { uint16 srcIdx; // Address Manager index uint8 srcEP; uint8 dstGroupMode; // Destination address type; 0 - // Group address uint16 dstIdx; // This field is used in both modes (group and non-group) to // save NV and RAM space // dstGroupMode = 0 - Address Manager index // dstGroupMode = 1 - Group Address uint8 dstEP; uint8 numClusterIds; uint16 clusterIdList[MAX_BINDING_CLUSTER_IDS]; // Don't use MAX_BINDING_CLUSTERS_ID when // using the clusterIdList field. Use // gMAX_BINDING_CLUSTER_IDS } BindingEntry_t; srcIdx –源地址(绑定记录的源地址)的地址管理器索引,地址管理器保存着源地址的IEEE地址和短地址。 srcEP -源终端 dstGroupMode -目的地址类型。 0 普通地址 1 组地址 dstIdx -若dstGroupMode为0,则包含目的地址的地址管理器索引,若dstGroupMode为1,则包含目的组地址 dstEP -目的终端 numClusterIds -clusterIdList中的入口数目 clusterIdList -簇ID列表。列表的最大数目定义由MAX_BINDING_CLUSTER_IDS [f8wConfig.cfg]指定 绑定的分析就到这里吧。如果有什么不对的地方,欢迎一起讨论。 |
|