我使用的协议栈版本及例子信息:
ZigBee2006\ZStack-1.4.3-1.2.1个人注释版_5.24\Projects\zstack\Samples\SimpleApp\CC2430DB
建立一个绑定表格有3种方式:
(1)ZDO绑定请求:一个试运转的工具能告诉这个设备制作一个绑定报告
(2)ZDO终端设备绑定请求:设备能告诉协调器他们想建立绑定表格报告。该协调器将使协调并在这两个设备上创建绑定表格条目。
(3)设备应用:在设备上的应用能建立或管理一个绑定表格。
有两种可用的机制配置设备绑定:
(1)如果目的设备的扩展地址是已知的,则zb_BindDeviceRequest()函数能创建一个绑定条目。
(2)如果扩展地址是未知的,则一个"按钮"可以利用。这样的话,这个目的设备首先处于一种状态,它将被zb_AllowBindResponse()发出一个匹配响应;然后在源设备处,zb_AllowBindRequest()函数带着空地址出发.
以上两种绑定机制,最终都是用函数APSME_BindRequest()创建绑定。不同的是,前者采用的目的地址是64位扩展地址,而后者采用的目的地址是16位网络地址。前者已知扩展地址,调用了ZDP_NwkAddrReq()函数获得目的设备短地址;后者利用描述匹配得到了短地址,然后调用了ZDP_IEEEAddrReq()函数,获取目的设备的扩展地址.
**************************************************************************************************************************
**************************************************************************************************************************
**************************************************************************************************************************
1、已知扩展地址的绑定
这里可以直接调用函数zb_BindDevice()发起绑定请求:
zb_BindDevice ( uint8 create, //是否创建绑定,TRUE则创建,FALSE则解除
uint16 commandId, //命令ID,基于某命令的绑定,相当于簇
uint8 *pDestination ) //指向扩展地址的指针
函数程序如下(已知扩展地址的绑定部分)
******************************************
void zb_BindDevice ( uint8 create, uint16 commandId, uint8 *pDestination )
{
zAddrType_t destination;
uint8 ret = ZB_ALREADY_IN_PROGRESS;
if ( create ) //create = true 建立绑定
{
if (sapi_bindInProgress == 0xffff) //不允许绑定过程??
{
//---------------------------------
if ( pDestination ) //已知扩展地址的绑定,即*pDestination 为非NULL
{
destination.addrMode = Addr64Bit;
osal_cpyExtAddr( destination.addr.extAddr, pDestination );
//直接调用APS绑定请求函数
ret = APSME_BindRequest( sapi_epDesc.endPoint, //源EP
commandId, //簇ID
&destination, //目的地址模式
sapi_epDesc.endPoint ); //目的EP
if ( ret == ZSuccess ) //绑定成功
{
// Find nwk addr 发现网络地址,得到被绑定设备的短地址
ZDP_NwkAddrReq(pDestination, ZDP_ADDR_REQTYPE_SINGLE, 0, 0 );
osal_start_timerEx( ZDAppTaskID, ZDO_NWK_UPDATE_NV, 250 );
}
}
//---------------------------------
else //未知目的扩展地址,即*pDestination=NULL
{
ret = ZB_INVALID_PARAMETER;
destination.addrMode = Addr16Bit;
destination.addr.shortAddr = NWK_BROADCAST_SHORTADDR; //0xffff 描述符匹配请求:广播
/*如果commandId是输出簇,则检测是否与本终端输出簇列表中的某一项相匹配(相同)*/
if ( ZDO_AnyClusterMatches( 1, &commandId, sapi_epDesc.simpleDesc->AppNumOutClusters,
sapi_epDesc.simpleDesc->pAppOutClusterList ) )
{
// Try to match with a device in the allow bind mode 寻找一个允许匹配状态下的设备进行描述符匹配
ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR,
sapi_epDesc.simpleDesc->AppProfId, 1, &commandId, 0, (cId_t *)NULL, 0 );
}
/*如果commandId是输入簇,则检测是否与本终端输入簇列表中的某一项相匹配(相同)*/
else if ( ZDO_AnyClusterMatches( 1, &commandId, sapi_epDesc.simpleDesc->AppNumInClusters,
sapi_epDesc.simpleDesc->pAppInClusterList ) )
{ //寻找一个允许匹配状态下的设备进行描述符匹配
ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR,
sapi_epDesc.simpleDesc->AppProfId, 0, (cId_t *)NULL, 1, &commandId, 0 );
}
if ( ret == ZB_SUCCESS )
{
// Set a timer to make sure bind completes
osal_start_timerEx(sapi_TaskID, ZB_BIND_TIMER, AIB_MaxBindingTime);
sapi_bindInProgress = commandId;
return; // dont send cback event
}
}
//---------------------------------
}
SAPI_SendCback( SAPICB_BIND_CNF, ret, commandId );
}
else //create = false 删除绑定
{
// Remove local bindings for the commandId
BindingEntry_t *pBind;
// Loop through bindings an remove any that match the cluster
while ( pBind = bindFind( sapi_epDesc.simpleDesc->EndPoint, commandId, 0 ) )
{
bindRemoveEntry(pBind);
}
osal_start_timerEx( ZDAppTaskID, ZDO_NWK_UPDATE_NV, 250 );
}
return;
}
******************************************
在上面已知扩展地址的绑定程序中调用了APS绑定函数APSME_BindRequest(),这个函数在两个设备间建立绑定,通过APSME_BIND.confirm原语返回,而且这两者是不可分割的。如果绑定成功,则调用函数ZDP_NwkAddrReq()得到目的设备的短地址。ZDP_NwkAddrReq()这个函数可以产生一个根据已知遥远设备的IEEE地址,请求得到16位短地址的信息.该信息以广播的方式发送给网络中的所有设备.
2、未知扩展地址的绑定(simpApp例子中默认的绑定机制)
该绑定方式下,在发送绑定请求前,先要让被绑定的目的设备处于允许绑定模式。可以调用函数zb_AllowBind()进入该模式,函数如下:
******************************************
//函数设置设备处于允许绑定模式
//timerout=0x00:不允许绑定
//timerout=0xff:一直处于绑定模式
//0<timeout<65:允许绑定的时间(单位/秒)
void zb_AllowBind ( uint8 timeout )
{
osal_stop_timerEx(sapi_TaskID, ZB_ALLOW_BIND_TIMER);
if ( timeout == 0 )
{
afSetMatch(sapi_epDesc.simpleDesc->EndPoint, FALSE);
}
else
{
afSetMatch(sapi_epDesc.simpleDesc->EndPoint, TRUE); //设置允许响应匹配描述符请求
if ( timeout != 0xFF )
{
if ( timeout > 64 )
{
timeout = 64;
}
//设置了允许匹配后,开启一个定时器,时间为timeout*1000,
//时间一到触发sapi任务ZB_ALLOW_BIND_TIMER事件,SAPI_ProcessEvent()对其的处理是
//afSetMatch(sapi_epDesc.simpleDesc->EndPoint, FALSE)而取消允许绑定
osal_start_timerEx(sapi_TaskID, ZB_ALLOW_BIND_TIMER, timeout*1000);
}
}
return;
}
******************************************
参数timeout是进入绑定模式持续的时间(s)。如果设置为OxFF,则设备在任何时候都在允许绑定模式;如果设置为OxOO,则设备将通过该命令取消允许绑定模式.
调用该函数使设备在给定时间内进入允许绑定模式.一个在允许绑定模式下同等的设备调用函数zb_BindDevice()能与之建立绑定,此时目的扩展地址为空(参见上面zb_BindDevice()未知目的扩展地址部分).zb_AllowBind()调用afSetMatch(),使之允许响应ZDO的匹配描述符请求.
以上设置目的设备允许绑定模式(比如simpleApp的灯设备),那在目的设备处于允许绑定模式的时间内,源设备(比如simpleApp的开关设备)可以调用zb_BindDevice()来发起绑定请求.此时执行的程序如下:
******************************************
void zb_BindDevice ( uint8 create, uint16 commandId, uint8 *pDestination )
{
…………
//---------------------------------
else //未知目的扩展地址,即*pDestination=NULL
{
ret = ZB_INVALID_PARAMETER;
destination.addrMode = Addr16Bit;
destination.addr.shortAddr = NWK_BROADCAST_SHORTADDR; //0xffff
/*如果commandId是输出簇,则检测是否与本终端输出簇列表中的某一项相匹配(相同)*/
if ( ZDO_AnyClusterMatches( 1, &commandId, sapi_epDesc.simpleDesc->AppNumOutClusters,
sapi_epDesc.simpleDesc->pAppOutClusterList ) )
{
// Try to match with a device in the allow bind mode
//寻找一个允许匹配状态下的设备进行描述符匹配
ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR,
sapi_epDesc.simpleDesc->AppProfId, 1, &commandId, 0, (cId_t *)NULL, 0 );
}
/*如果commandId是输入簇,则检测是否与本终端输入簇列表中的某一项相匹配(相同)*/
else if ( ZDO_AnyClusterMatches( 1, &commandId, sapi_epDesc.simpleDesc->AppNumInClusters,
sapi_epDesc.simpleDesc->pAppInClusterList ) )
{ //寻找一个允许匹配状态下的设备进行描述符匹配
ret = ZDP_MatchDescReq( &destination, NWK_BROADCAST_SHORTADDR,
sapi_epDesc.simpleDesc->AppProfId, 0, (cId_t *)NULL, 1, &commandId, 0 );
}
if ( ret == ZB_SUCCESS )
{
// Set a timer to make sure bind completes 设置一个时间,确保绑定完成
osal_start_timerEx(sapi_TaskID, ZB_BIND_TIMER, AIB_MaxBindingTime);
sapi_bindInProgress = commandId; //允许基于commandId命令的绑定过程
return; // dont send cback event
}
}
//---------------------------------
…………
}
******************************************
在之中调用了函数ZDP_MatchDescReq(),将建立和发送一个匹配描述符请求。用这个函数在一个应用中的输入/输出簇列表中搜索匹配某条件的设备/应用。该绑定响应处理在SAPI_ProcessEvent事件处理函数中
case ZDO_CB_MSG: /*ZDO信息数据*/
SAPI_ProcessZDOMsgs( (zdoIncomingMsg_t *)pMsg );
看下SAPI_ProcessZDOMsgs()函数
******************************************
// SAPI_Init()函数中注册了以下两个ZDO信息
// ZDO_RegisterForZDOMsg( sapi_TaskID, NWK_addr_rsp );
// ZDO_RegisterForZDOMsg( sapi_TaskID, Match_Desc_rsp );
void SAPI_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg )
{
switch ( inMsg->clusterID )
{
//--------------------
case NWK_addr_rsp:
…………
//--------------------
case Match_Desc_rsp:
{
zAddrType_t dstAddr;
ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg );
if ( sapi_bindInProgress != 0xffff ) //commandId
{
// Create a binding table entry 创建一个绑定条目
dstAddr.addrMode = Addr16Bit;
dstAddr.addr.shortAddr = pRsp->nwkAddr;
/*调用这个函数来实现两个设备的绑定*/
if ( APSME_BindRequest( sapi_epDesc.simpleDesc->EndPoint, //源EP
sapi_bindInProgress, //簇ID
&dstAddr, //目的地址模式
pRsp->epList[0] ) == ZSuccess ) //目的EP
//成功实现绑定后
{
//zb_BindDevice()中开启了一个定时器,用于接收Match_Desc_rsp计时
//如果接收到,则停止这个定时器,如下;如果溢出,则触发相应任务事件
osal_stop_timerEx(sapi_TaskID, ZB_BIND_TIMER);
osal_start_timerEx( ZDAppTaskID, ZDO_NWK_UPDATE_NV, 250 );
sapi_bindInProgress = 0xffff;
// Find IEEE addr
ZDP_IEEEAddrReq( pRsp->nwkAddr, ZDP_ADDR_REQTYPE_SINGLE, 0, 0 );
// Send bind confirm callback to application
zb_BindConfirm( sapi_bindInProgress, ZB_SUCCESS );
}
}
}
break;
}
}
******************************************
以上内容摘自《zigbee2006无线网络与无线定位实战》这本书,根据协议版本的不同作了一些修改。
实例中的两种绑定机制,第一种(已知扩展地址的绑定)流程,就是zb_BindDevice()根据扩展地址直接调用APSME_BindRequest()创建绑定条目实现绑定,再得到其16位网络地址;第二种(未知扩展地址的绑定),因地址未知,须先经过一个描述符匹配过程得到相匹配设备的16位短地址,然后通过APSME_BindRequest()创建绑定条目实现绑定,再得到其扩展地址.
下面引用《zigbee2006无线网络与无线定位实战》对未知扩展地址的绑定流程的描述(以simpApp灯开关实验为例),根据协议版本不同作了相应修改:
(1)首先调用zb_AllowBind(myAllowBindTimeout)函数,使管理设备(灯)处于允许绑定(匹配)响应模式
(2)在myAllowBindTimeout规定的时间内,终端设备需要调用zb_BindDevice(TRUE, TOGGLE_LIGHT_CMD_ID, NULL)函数发送绑定(描述符匹配ZDP_MatchDescReq)请求.
(3)当管理器接收到匹配请求后,对该匹配作出响应,发送一个匹配响应(P:在ZDO_ProcessMatchDescReq()函 数中),之后可以看到发送匹配响应后的确认事件(P:在ZDO_ProcessMatchDescReq()函数中):
pRspSent->hdr.event = ZDO_MATCH_DESC_RSP_SENT;
(4)当终端接收到匹配响应后,产生该事件:
case Match_Desc_rsp:
该事件详细处理过程参见SAPI_ProcessZDOMsgs,这里面调用了APSME_BindRequest()建立绑定,而且调用 了ZDP_IEEEAddrReq()得到被绑定的IEEE地址.
(5)最后完成绑定,在终端设备建立绑定表格.
一开始看书上这匹配过程还是蛮纠结的,下面详细记录下个人对未知扩展地址的绑定流程的理解:
**************************************************************************************************************************
**************************************************************************************************************************
**************************************************************************************************************************
首先理清两个节点,以灯开关实验为例:
管理器(灯) 终端(开关)
1、-------------允许绑定模式----------------------- ----------
2、------------- --<----<---<--------广播发送描述符匹配请求(绑定请求)
3、-----接收后发送描述符匹配响应-->--->--->-- -----------
4、------------- ---------------接收后获得管理器地址,建立终端与管理器间的绑定---
第一步:管理器处于允许绑定模式,参见上面的记录
第二步:终端调用zb_BindDevice()发送描述符匹配请求(即绑定请求)给管理器.
zb_BindDevice()调用ZDP_MatchDescReq():
******************************************
//函数建立和发送一个匹配描述请求.灯开关实验zb_BindDevice()中初始化为广播:
//destination.addr.shortAddr = NWK_BROADCAST_SHORTADDR;
//使用这个函数查询与应用程序输入或输出簇列表相匹配的应用程序或设备.
//即发现服务,或者叫自动寻求匹配设备。
afStatus_t ZDP_MatchDescReq(
zAddrType_t *dstAddr, //目的地址类型
uint16 nwkAddr, //16位目的网络地址
uint16 ProfileID,
byte NumInClusters, cId_t *InClusterList, //簇输入列表簇ID数及簇ID输入数组
byte NumOutClusters, cId_t *OutClusterList, //簇输出列表簇ID数及簇ID输出数组
byte SecurityEnable )
{
/*指针pBuf指向ZDP_TmpBuf,下面为配置数组ZDP_TmpBuf[ ]*/
byte *pBuf = ZDP_TmpBuf;//ZDP_TmpBuf=ZDP_Buf+1; ZDP_Buf[80]
// nwkAddr+ProfileID+NumInClusters+NumOutClusters.
byte i, len = 2 + 2 + 1 + 1; // nwkAddr+ProfileID+NumInClusters+NumOutClusters.
len += (NumInClusters + NumOutClusters) * sizeof(uint16);
if ( len >= ZDP_BUF_SZ-1 )
{
return afStatus_MEM_FAIL;
}
*pBuf++ = LO_UINT16( nwkAddr ); // NWKAddrOfInterest
*pBuf++ = HI_UINT16( nwkAddr );
*pBuf++ = LO_UINT16( ProfileID ); // Profile ID
*pBuf++ = HI_UINT16( ProfileID );
//-------------------------
*pBuf++ = NumInClusters; // Input cluster list
if ( NumInClusters )
{
for (i=0; i<NumInClusters; ++i) {
*pBuf++ = LO_UINT16( InClusterList[i] );
*pBuf++ = HI_UINT16( InClusterList[i] );
}
}
//-------------------------
*pBuf++ = NumOutClusters; // Output cluster list
if ( NumOutClusters )
{
for (i=0; i<NumOutClusters; ++i) {
*pBuf++ = LO_UINT16( OutClusterList[i] );
*pBuf++ = HI_UINT16( OutClusterList[i] );
}
}
//-------------------------
//注意这里clusterID设置为Match_Desc_req
return fillAndSend( &ZDP_TransID, dstAddr, Match_Desc_req, len );
}
******************************************
ZDP_MatchDescReq()中配置了clusterID=Match_Desc_req,dstAddr为广播,数据为ZDP_TmpBuf的信息,通过fillAndSend()来发送,看下fillAndSend():
******************************************
static afStatus_t fillAndSend( uint8 *transSeq, zAddrType_t *addr, cId_t clusterID, byte len )
{
afAddrType_t afAddr;
ZADDR_TO_AFADDR( addr, afAddr );
*(ZDP_TmpBuf-1) = *transSeq;
return AF_DataRequest( &afAddr, &ZDApp_epDesc, clusterID,
(uint16)(len+1), (uint8*)(ZDP_TmpBuf-1),
transSeq, ZDP_TxOptions, AF_DEFAULT_RADIUS );
}
******************************************
可以看到fillAndSend()最终调用AF_DataRequest()来广播发送描述符匹配请求.
第三步:管理器接收到终端发送的描述符匹配请求(也即绑定请求).
那管理器是如何作出处理的?
因为ZDO层是管理绑定的(绑定表建在APS中),管理器接收到的描述符匹配请求是发往其ZDO层的,首先来看下这个函数:ZDP_IncomingData()
******************************************
* @fn ZDP_IncomingData
*
* @brief This function indicates the transfer of a data PDU (ASDU)
* from the APS sub-layer to the ZDO.
*
* @param pData - Incoming Message
*
* @return none
*/
//APS-->ZDO
void ZDP_IncomingData( afIncomingMSGPacket_t *pData )
{
uint8 x = 0;
uint8 handled;
zdoIncomingMsg_t inMsg;
inMsg.srcAddr.addrMode = Addr16Bit;
inMsg.srcAddr.addr.shortAddr = pData->srcAddr.addr.shortAddr;
inMsg.wasBroadcast = pData->wasBroadcast;
inMsg.clusterID = pData->clusterId;
inMsg.SecurityUse = pData->SecurityUse;
inMsg.asduLen = pData->cmd.DataLength-1;
inMsg.asdu = pData->cmd.Data+1;
inMsg.TransSeq = pData->cmd.Data[0];
//通过 ZDO_SendMsgCBs()这个函数把ZDO信息发送到注册过ZDO信息的任务中去.
//比如sapi中SAPI_Init()注册过两种类型clusterId的ZDO信息:NWK_addr_rsp 和 Match_Desc_rsp
//因此其它命令,比如请求命令不会发送到sapi应用层中
//注意,End_Device_Bind_req这个ZDO信息是注册在ZDAppTaskID任务中的.
handled = ZDO_SendMsgCBs( &inMsg );
#if defined( MT_ZDO_FUNC )
MT_ZdoRsp( &inMsg );
#endif
//注意,这里只会对请求/设置/通知命令才调用相应处理函数,如果是响应命令则不处理
//但是要ZDO信息处理表中包含的命令才有相应的处理函数,有些没有的就不会调用
//比如End_Device_Bind_req,这个很重要,它在哪里被处理呢?在ZDApp层任务中,
//函数ZDApp_RegisterCBs()把这个请求命令登记到了ZDAppTaskID,具体参见ZDApp_RegisterCBs()
while ( zdpMsgProcs[x].clusterID != 0xFFFF ) //一个个查询过去
{
if ( zdpMsgProcs[x].clusterID == inMsg.clusterID )
{
zdpMsgProcs[x].pFn( &inMsg ); //调用相应ZDO信息处理函数
return; //如x=6,为ZDO_ProcessMatchDescReq();
}
x++;
}
// Handle unhandled message
if ( !handled )
ZDApp_InMsgCB( &inMsg );
}
******************************************
这里涉及到三个函数:ZDO_SendMsgCBs();zdpMsgProcs[x].pFn( &inMsg );ZDApp_InMsgCB
(1)ZDO_SendMsgCBs():把ZDO接收信息发送到注册过ZDO信息的任务中去;具体流程是比较接收信息的簇ID与注册过的簇ID,有相同则调用osal_msg_send()触发相应任务事件,事件这里默认为ZDO_CB_MSG,因此可以看到SAPI_ProcessEvent()中有这个事件.注意这个函数只会传送信息到注册过相应ZDO信息的任务中去.
(2)对于zdpMsgProcs[x].pFn( &inMsg ),ZDProfile.c如下定义:
//把pfnZDPMsgProcessor声明为一种函数指针的类型别明,指向类似Fun(*inMsg)这种类型
//的函数.如果用此别名来声明一个指针变量,如:pfnZDPMsgProcessor pFn,则当pFn获取
//函数的地址后,就可以像调用原函数一样来使用这个函数指针:pFn(*inMsg)
typedef void (*pfnZDPMsgProcessor)( zdoIncomingMsg_t *inMsg );
typedef struct
{
uint16 clusterID;
pfnZDPMsgProcessor pFn;
} zdpMsgProcItem_t;
//定义一个zdpMsgProcItem_t类型的数组zdpMsgProcs[],则数组zdpMsgProcs[]中
//的每个元素都是zdpMsgProcItem_t类型的结构体:{clusterID,pFn}
CONST zdpMsgProcItem_t zdpMsgProcs[ ] =
{
{ NWK_addr_req, zdpProcessAddrReq },
{ IEEE_addr_req, zdpProcessAddrReq },
{ Node_Desc_req, ZDO_ProcessNodeDescReq },
{ Power_Desc_req, ZDO_ProcessPowerDescReq },
{ Simple_Desc_req, ZDO_ProcessSimpleDescReq },
{ Active_EP_req, ZDO_ProcessActiveEPReq },
{ Match_Desc_req, ZDO_ProcessMatchDescReq },
…………(后面省略)
{0xFFFF, NULL} // Last
};
(3)ZDApp_InMsgCB():函数说明如下:
* @brief This function is called to pass up any message that is
* not yet supported. This allows for the developer to
* support features themselves..
这函数我就不去管了.
回到刚才问题:管理器接收到描述符匹配请求后是如何处理的?
因为这里是接收到描述符匹配请求,SAPI_Init中没有注册过这个ZDO信息,因此不会通过ZDO_SendMsgCBs()发送到sapi应用.然后查询ZDO信息处理表,当x=6时发现命令相符,调用ZDO_ProcessMatchDescReq()对描述符匹配请求进行处理.
下面来看下ZDO_ProcessMatchDescReq()函数:
******************************************
void ZDO_ProcessMatchDescReq( zdoIncomingMsg_t *inMsg )
{
…………
// Parse the incoming message
…………
//--------------------------发送描述符匹配请求的响应
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;
}
//--------------------------
…………
//发送匹配响应后的确认事件ZDO_MATCH_DESC_RSP_SENT
pRspSent->hdr.event = ZDO_MATCH_DESC_RSP_SENT;
…………
osal_msg_send( *epDesc->epDesc->task_id, (uint8 *)pRspSent );
…………
}
******************************************
可以看到这里管理器首先通过ZDP_MatchDescRsp()发送匹配响应给终端.而ZDP_MatchDescRsp()宏定义为ZDP_EPRsp(),ZDP_EPRsp()设置clusterID为Match_Desc_rsp并调用FillAndSendTxOptions(),FillAndSendTxOptions()中调用fillAndSend(),fillAndSend()中最终调用AF_DataRequest()来发送匹配响应给终端(灯).发送完匹配响应后管理器触发一个确认事件ZDO_MATCH_DESC_RSP_SENT,来指示有个设备试图与自己绑定.管理器是如何对这个确认事件进行处理的?如下:
SAPI_ProcessEvent()中对ZDO_MATCH_DESC_RSP_SENT的处理:
case ZDO_MATCH_DESC_RSP_SENT: /*发送匹配响应*/
SAPI_AllowBindConfirm( ((ZDO_MatchDescRspSent_t *)pMsg)->nwkAddr );
来看下SAPI_AllowBindConfirm()这个函数:
******************************************
* @fn SAPI_AllowBindConfirm
*
* @brief Indicates when another device attempted to bind to this device
*
* @param
*
* @return none
*/
void SAPI_AllowBindConfirm( uint16 source )
{
#if defined ( MT_SAPI_CB_FUNC )
/* First check if MT has subscribed for this callback. If so , pass it as
a event to MonitorTest and return control to calling function after that */
if ( SAPICB_CHECK( SPI_CB_SAPI_ALLOW_BIND_CNF ) )
{
zb_MTCallbackAllowBindConfirm( source ); //这里应该是把请求匹配的设备的网络地址通过串口发PC
}
else
#endif //MT_SAPI_CB_FUNC
{
zb_AllowBindConfirm( source ); //未定义
}
}
******************************************
管理器接收到终端发送的描述符匹配请求后,发送一个描述符匹配请求的响应给终端,并触发一个确认事件指示有个设备试图与本设备进行绑定.
第四步:终端接收到管理器发送的描述符匹配响应.
同样,这个响应信息也是发往ZDO层的,因此首先要来看ZDP_IncomingData()这个函数,具体见第三步.因为接收到的响应信息clusterID为Match_Desc_rsp,SAPI_Init注册过,因此会通过ZDO_SendMsgCBs()把这个描述符匹配响应信息发送到sapi应用层,而在ZDO信息处理表中查询不到相符合的clusterID,因此不会调用任何处理函数.这里刚好与第二步中对描述符匹配请求的处理相反.下面来看下ZDO_SendMsgCBs()把这个描述符匹配响应信息发送到sapi应用层后的处理,ZDO_SendMsgCBs()发送的流程参见第三步,其最终触发任务sapi的ZDO_CB_MSG事件,而sapi任务事件处理函数SAPI_ProcessEvent()通过调用SAPI_ProcessZDOMsgs()对其进行处理:
case ZDO_CB_MSG: /*ZDO信息*/
SAPI_ProcessZDOMsgs( (zdoIncomingMsg_t *)pMsg );
break;
来看下SAPI_ProcessZDOMsgs():
******************************************
// SAPI_Init()函数中注册了以下两个ZDO信息
// ZDO_RegisterForZDOMsg( sapi_TaskID, NWK_addr_rsp );
// ZDO_RegisterForZDOMsg( sapi_TaskID, Match_Desc_rsp );
void SAPI_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg )
{
switch ( inMsg->clusterID )
{
//--------------------
case NWK_addr_rsp:
{
…………
break;
//--------------------
case Match_Desc_rsp:
{
zAddrType_t dstAddr;
ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg );
if ( sapi_bindInProgress != 0xffff ) //允许基于commandID的绑定
{
// Create a binding table entry 创建一个绑定条目
dstAddr.addrMode = Addr16Bit;
dstAddr.addr.shortAddr = pRsp->nwkAddr; //通过描述符匹配响应信息得到管理器的网络地址
/*调用这个函数来实现两个设备的绑定*/
if ( APSME_BindRequest( sapi_epDesc.simpleDesc->EndPoint, //源EP
sapi_bindInProgress, //簇ID
&dstAddr, //目的地址模式
pRsp->epList[0] ) == ZSuccess ) //目的EP
//成功实现绑定后
{
//zb_BindDevice()中开启了一个定时器,用于接收Match_Desc_rsp计时
//如果接收到,则停止这个定时器,如下;如果溢出,则触发相应任务事件
osal_stop_timerEx(sapi_TaskID, ZB_BIND_TIMER);
osal_start_timerEx( ZDAppTaskID, ZDO_NWK_UPDATE_NV, 250 );
sapi_bindInProgress = 0xffff;//不允许绑定过程
// Find IEEE addr
ZDP_IEEEAddrReq( pRsp->nwkAddr, ZDP_ADDR_REQTYPE_SINGLE, 0, 0 );
// Send bind confirm callback to application
//告诉应用绑定成功
zb_BindConfirm( sapi_bindInProgress, ZB_SUCCESS );
}
}
}
break;
}
}
******************************************
可以看到通过接收到的描述符匹配响应信息,终端获得允许绑定模式下管理器的16位网络地址:
dstAddr.addr.shortAddr = pRsp->nwkAddr;最终调用APSME_BindRequest()来绑定终端设备与管理器.
引用《ZigBee四种绑定方式在TI Z-Stack中的应用》的一段话:
一、 “终端设备绑定请求”这一命名有误导的嫌疑。这一请求不仅仅适用于终端设备,而且适用于对希望在协调器上绑定的两个设备中匹配的簇实施绑定。一旦这个函数被调用,将假设REFLECTOR这一编译选项在所有希望使用这一服务的节点中都已经打开。具体操作如下:
(1) (Bind Req) Device 1 --> Coordinator <--- Device 2 (Bind Req)
协调器首先找出包含在绑定请求中的簇,然后对比每一设备的IEEE地址,如果簇可以匹配,而且这几个设备没有已经存在的绑定表,那他将发送一个绑定应答给每一个设备。
(2) Device 1 <--- NWK Addr Req ------ Coordinator ------- NWK addr Req ----> Device 2
(3) Device 1 ----> NWK Addr Rsp ---> Coordinator <---- NWK addr Rsp <--- Device 2
(4) Device 1 <----- Bind Rsp <----- Coordinator -----> Bind Rsp ----> Device 2
二、 “描述符匹配”为源设备的服务发现提供了一种灵巧的方法。下面是具体的操作,这一过程并没有通过协调器。
(1) Device 1 ----> Match Descriptor request (broadcast or unicast) Device 2
(2) Device 1 <---- Match Descriptor response (if clusters, application profile id match) that includes src endpoint, src address <---- Device 2
1号设备需要维护一个端点和地址的记录。
许多应用服务最终都会使用第二种方法。
说明:
眼睛花了……
1、本文作者所记录,错误之处还请高手指点,本人随时更新.
2、欢迎交流,转载请注明出处,谢谢!
3、具体请参考《SimpleApp和GenericApp实例绑定程序流程》
2010.6.01 ~XF