2015年(59)
分类: 嵌入式
2015-05-31 10:50:47
原文地址:再谈ZigBee中的绑定机制 作者:frankzfz
这里主要再讨论一下绑定的机制,绑定是ZigBee中应该是比较重要的一个部分。前面的几篇文章也对绑定有了具体的分析,主要分析了两种绑定方式,介绍了绑定的流程,源代码方面。这里主要是理清整个绑定在组网中的概念。绑定是和EndPoint紧密联系在一起的,其中很多是自己通过看资料,自己的一些理解,当中肯定有不正确的地方,欢迎有兴趣的一起讨论。
ZigBee中还有一个重要的概念端点,理解的不是很清楚,现在再一次总结分析一下,端点是应用对象存在的地方,允许多个应用同时位于一个节点上,例如一个节点具有控制灯光的功能,又具有感应温度的功能,又具有收发文本消息的功能,这种设计有利于复杂设备的出现。我们可以从ZigBee的体系结构图中可以看到可以有240个应用,也就是说一个物理的ZigBee设备的话,可以有240个具体的应用,例如上面提到的其中的三种具体的应用。
一共有二个特殊的端点,即端点0和端点255。端点0用于整个ZigBee设备的配置和管理。应用程序可以透过端点0与ZigBee堆栈的其它层通讯,因而实现对这些层的初始化和配置。附属在端点0的对象被称为ZigBee设备对象(ZDO)。端点255用于向所有端点的广播。端点241到254是保留端点。
所有端点都使用应用支持子层(APS)提供的服务。APS透过网络层和安全服务提供层与端点相接,并为数据传送、安全和固定服务,因此能够适配不同但兼容的设备,如带灯的开关。 下面是终端的描述符的结构体定义。
typedef struct
{
byte endPoint; //EP号
byte *task_id; // 指向任务编号的指针.
SimpleDescriptionFormat_t *simpleDesc; //设备的简单描述
afNetworkLatencyReq_t latencyReq; //枚举类型
} endPointDesc_t;
//设备的简单描述结构
typedef struct
{
byte EndPoint; //EPID
uint16 AppProfId; // Profile ID
uint16 AppDeviceId; // Device ID
byte AppDevVer:4; // Device Version
byte Reserved:4; //AF_V1_SUPPORT uses for AppFlags:4. Reserved
byte AppNumInClusters; //输入命令个数
cId_t *pAppInClusterList; //输入命令列表
byte AppNumOutClusters; //输出命令个数
cId_t *pAppOutClusterList; //输出命令列表
} SimpleDescriptionFormat_t;
下面是在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]指定
绑定的分析就到这里吧。如果有什么不对的地方,欢迎一起讨论。