这篇博文主要和大家分享一下我在学习这四种数据发送模式的时候踩过的坑,以及一些心得。在介绍发送数据模式之前有必要先介绍一下终端(endpoint)的管理,我可是在这上面踩过大坑的,为方便讨论已,现在约定以下终端(endpoint)和端点(endpoint)以及端口号为同一个意思。
每个终端必须被注册了才能被使用,否则是接收不到数据的。我以前以为终端注册了只是在别的节点查询周围节点描述符时才会用到,然后就一直没管,直到我给没有注册的终端发送数据失败后才注意到这个问题。在ZStask中节点收到数据时会经APS层传递到AF层,然后在AF层会根据收到数据的目标端点来判断应该将数据发送给那个应用任务(如果端点注册了的话这个端点会绑定在一个应用任务上,往这个端点发送数据就是往其对应的应用任务发送数据)。关于数据接受的大体框架可以参考z-stack协议栈的使用中的AF部分。
ZStask-OSAL中将所有的终端以一个终端列表条目表现出来,并将它们都串在一个链表上,用全局epList结构体指针指向最后一个注册的终端列表条目结构体。一个任务就是一个应用程序,也就是一个终端,在任务初始化函数中调用afRegister函数,只需要我们传一个终端描述符endPointDesc_t,所有在调用afRegister函数之前需要将终端描述符endPointDesc_t进行初始化,初始化终端号、初始化任务ID指针、初始化简单描述符等。重点初始化终端号、初始化任务ID指针。
下面是关于端点的一些结构体
/*
*简单描述符
*其中AppProfId需要重点注意,这个在绑定中会用到
*/
typedef struct
{
uint8 EndPoint; //端点/端口/终端号 1-240
uint16 AppProfId; //支持的Profile ID,配置文件ID
uint16 AppDeviceId; //支持的设备ID
uint8 AppDevVer:4; //执行的设备描述的版本
uint8 Reserved:4; // AF_V1_SUPPORT uses for AppFlags:4.
uint8 AppNumInClusters; //终端支持的输入群集数目
cId_t *pAppInClusterList; //指向输入Cluster ID(输入群集)列表的指针
uint8 AppNumOutClusters; //终端支持的输出簇数目
cId_t *pAppOutClusterList; //指向输出Cluster ID(输出)列表的指针
} SimpleDescriptionFormat_t;
端点/接口描述。可以在SampleApp_Init()中初始化,也可以直接初始化好并定义为“const”(在代码空间中)。一个endPointDesc_t类型的对象对应一个端口.每个端口在使用前必须先使用afRegister注册到AF层,否则数据是无法上传到应用层(APS)的。也就是说使用多个端口时需要定义多个endPointDesc_t型变量,然后注册到AF层。
typedef struct
{
uint8 endPoint; //终端号
uint8 *task_id; //任务ID指针
SimpleDescriptionFormat_t *simpleDesc; //简单描述符,就是上面定义的结构体
afNetworkLatencyReq_t latencyReq; //必须赋值为noLatencyReqs
} endPointDesc_t;//终端描述符
/*其中SimpleDescriptionFormat_t *simpleDesc;当其他设备设备询问当前节点是否具有某些端口等信息时会
查找这个表。这个表中的信息并不会影响协议栈的使用,所以可以忽略这个表,使用时即使其他端点都用这个表也
可以正常通信。*/
//终端列表条目描述符
typedef struct _epList_t {
struct _epList_t *nextDesc; //下一个节点或称下一个终端条目
endPointDesc_t *epDesc; //终端/端点/端口描述符,里面包括简单的描述符
pDescCB pfnDescCB; //.回调函数
afAPSF_Config_t apsfCfg;
eEP_Flags flags; //终端标志,eEP_Flags是一个枚举类型,里面含有两个变量eEP_AllowMatch=1,eEP_NotUsed=2
} epList_t;
/*在这个列表中存储着每一个终端的信息*/
介绍完终端后终于可以介绍单播、广播、组播、绑定这四种通信方式了。
其实不管是那种通信方式,最终都是调用了AF_DataRequest这个函数。
/*
*作用:应用层利用此函数,向目标网络地址的设备发送buf中的数据
*Parameter Details(参数详解:)
*dstAddr -目标地址指针。其中可以在里面设置四中发送模式
* afAddrNotPresent 由反射器(邦定源,也即路由器或者协调器)指定
* afAddrGroup 发送到组
* afAddrBroadcast 发送广播消息
* afAddr16Bit 直接发送到节点(单播),还有一个Mac地址单播
*srcEP -发送终端的终端描述符指针
*cID -簇ID,cluster ID如同消息ID,并且在剖面(profile)中各不相同
*len -要发送的字节数
*buf -指向要发送的数据缓存的指针
*transID -事务序列号指针。如果消息缓存发送,这个函数将增加这个数字
*options -发送选项,可以由下面一项,或几项相或得到
* AF_ACK_REQUEST 0x10 要求APS应答,这是应用层的应答,只在直接发送(单播)时使用。
* AF_DISCV_ROUTE 0x20 总要包含这个选项
* AF_SKIP_ROUTING 0x80 设置这个选项将导致设备跳过路由而直接发送消息。终点设备将不向其父亲发
* 送消息。在直接发送(单播)和广播消息时很好用。
*radius – 最大的跳数,用默认值AF_DEFAULT_RADIUS
*afStatus_t – 成功则为ZSuccess(defined in ZComDef.h). 否则 Errors(defined in ZComDef.h)
*/
afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,
uint16 cID, uint16 len, uint8 *buf, uint8 *transID,
uint8 options, uint8 radius );
经过上面的介绍,现在已经对发送和接受有了一定的认识,其实单播、组播、广播在发送的时候都是一摸一样的,只是设置的目标地址不同罢了。下面为一般的发送流程:
/*发送方式*/
enum
{
AddrNotPresent = 0,
AddrGroup = 1,
Addr16Bit = 2,
Addr64Bit = 3,
AddrBroadcast = 15
};
//发送数据举例,组播的方式
SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrGroup;//以分组的模式发送数据
SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;//目标端口号
SampleApp_Periodic_DstAddr.addr.shortAddr = DevGroup;//组播的组号
if ( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,
LED_CTRL_CLUSTERID,//clusterID
2, //要发送的数据的长度
buf, //指向要发送的数据的地址的指针
&SampleApp_TransID,
AF_DISCV_ROUTE,
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
{
}
在说一下单播、广播、组播的区别,单播是指设备A向设备B单向的发送数据,他的目标地址可以为16位的短地址,也可以为64位的MAC地址。广播是设备A向网络中的所有节点都发送数据,目标短地址为0xFFFF。组播是向网络中的符合要求的节点发送数据,其目标地址为要发送的组号。
绑定相对于其他的模式较为复杂一些,但是其完成绑定后就和单播没什么区别了。
绑定一共有四种模式,我下面只对其中一种进行举例说明。
Match方式
这种绑定方式无需协调器设备存在,可采用按键机制来实现。
1、在ZDNwkMgr_Init中加入要注册的事件如
ZDO_RegisterForZDOMsg( ZDNwkMgr_TaskID, Match_Desc_rsp );//注册组播回调
2、在ZDO_CB_MSG的回调函数中添加
case Match_Desc_rsp:
ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg );
if ( pRsp )
{
if ( pRsp->status == ZSuccess && pRsp->cnt )
{
ZG_Serial_Control_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
ZG_Serial_Control_DstAddr.addr.shortAddr = pRsp->nwkAddr;
// Take the first endpoint, Can be changed to search through endpoints
ZG_Serial_Control_DstAddr.endPoint = pRsp->epList[0];
}
osal_mem_free( pRsp );
}
break;
这样就可以在设备完成绑定后触发Match_Desc_rsp事件,然后处理绑定后得到的绑定信息,也就是目标设备的地址信息。
关于绑定可以详细参考以下这两篇博客