Chinaunix首页 | 论坛 | 博客
  • 博客访问: 93386
  • 博文数量: 23
  • 博客积分: 30
  • 博客等级: 民兵
  • 技术积分: 62
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-19 04:21
文章分类
文章存档

2015年(1)

2014年(5)

2013年(8)

2012年(9)

分类: 嵌入式

2013-04-25 09:00:01

原文地址:再谈ZigBee中的绑定机制 作者:frankzfz

这里主要再讨论一下绑定的机制,绑定是ZigBee中应该是比较重要的一个部分。前面的几篇文章也对绑定有了具体的分析,主要分析了两种绑定方式,介绍了绑定的流程,源代码方面。这里主要是理清整个绑定在组网中的概念。绑定是和EndPoint紧密联系在一起的,其中很多是自己通过看资料,自己的一些理解,当中肯定有不正确的地方,欢迎有兴趣的一起讨论。

ZigBee中还有一个重要的概念端点,理解的不是很清楚,现在再一次总结分析一下,端点是应用对象存在的地方,允许多个应用同时位于一个节点上,例如一个节点具有控制灯光的功能,又具有感应温度的功能,又具有收发文本消息的功能,这种设计有利于复杂设备的出现。我们可以从ZigBee的体系结构图中可以看到可以有240个应用,也就是说一个物理的ZigBee设备的话,可以有240个具体的应用,例如上面提到的其中的三种具体的应用。

一共有二个特殊的端点,即端点0和端点255。端点0用于整个ZigBee设备的配置和管理。应用程序可以透过端点0ZigBee堆栈的其它层通讯,因而实现对这些层的初始化和配置。附属在端点0的对象被称为ZigBee设备对象(ZDO)。端点255用于向所有端点的广播。端点241254是保留端点。 
   
所有端点都使用应用支持子层(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 - Normal address index, 1 -  

                        // 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  -dstGroupMode0,则包含目的地址的地址管理器索引,若dstGroupMode1,则包含目的组地址

dstEP   -目的终端

numClusterIds   -clusterIdList中的入口数目

clusterIdList   -ID列表。列表的最大数目定义由MAX_BINDING_CLUSTER_IDS [f8wConfig.cfg]指定

           还有一点就是绑定设备之间的通信方式更加的灵活,这主要体现在那里呢我想可能有下面几个原因,一是在前面也有介绍绑定有四种方式,主要介绍了两种一种是通过协调器,另一种是不通过协调器的绑定,这样可选择性多,但使用点对点的通信时,必须要通过IEEE地址得到网络中的短地址,也必须借助协调器进行,协调器的短地址是知道。第二个原因时:绑定也可以实现一对多的绑定,也就是相当于了组广播了,第三,绑定有较多的API操作进行,当终端结点离开或者有一个新的设备加入网络时,可以主动完成绑定过程,

绑定的分析就到这里吧。如果有什么不对的地方,欢迎一起讨论。


阅读(1203) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~