Simple实例中的两种绑定机制
(2011/12/14 09:12)
目录: 公司动态 |
浏览字体: 大 中 小
实例:E:\学习\Zigbee\协议栈\ZStack-CC2530-2.3.0-1.4.0\Projects\zstack\Samples\SimpleApp\CC2530DB 可参考《协议栈simpleApp例程中两种绑定机制》 12.1 绑定方式在介绍 可参考《重谈Zigbee的绑定问题》,这个讲得比较透彻 下面简要介绍下 绑定有四种方式: a) 两个节点分别通过按键机制调用ZDP_EndDeviceBindReq()函数 即在一定的时间内两个节点都通过按键(其他方式也可以)触发这个函数 注意:时间16秒 绑定表存放在outcluster那侧,需要协调器,绑定成功后不需要协调器。 b) Match方式(Simple 例子就有这种方法) 即:一个节点A可以通过调用afSetMatch()函数允许或禁止本节点被Match(协议栈默认允许,这个函数在Simple例子中由zb_AllowBind()函数调用,也就是允许节点A被绑定),然后节点B在一定的时间内向节点A发起Match_Desc_Req请求(匹配描述符请求),节点A收到此请求后会响应这个请求,返回响应Match_Desc_Rsp,节点A收到此描述符响应后回自动处理绑定(通过函数APSME_BindRequest())。 注意:不需要协调器参数,只要两个节点之间即可实现,但是前提他们一定要match,即一方的Outcluster和另一方的incluster匹配。 c) ZDP_BindReq和ZDP_UnbindReq方式 为了让A和B进行绑定,还需要节点C参与。例如,若要A控制B,这种方式是由C调用函数ZDP_BindReq()发出Bind或Unbind命令给A(发给谁,谁就处理绑定、并负责存储绑定表),A在接收到req后直接处理绑定,也就是添加绑定表项,并且这个过程B不知道,但是A知道绑定表中关于B的记录,并且这种方式可以实现一个节点绑定到一个Group上去。 注意,这种方式需要知道A和B的长地址。 d) 手工管理绑定表 通过应用程序调用诸如bindAddEntry来实现手工绑定表管理,需要只奥被绑定节点的信息,如短地址、endpoint、incluster和outcluster等信息。 12.2 Simple例子中的两种配置设备绑定的机制 (1) 如果目的设备的扩展地址已知,直接调用APSME_BindRequest()调用函数创建绑定。 (2) 若目的设备的扩展地址未知(肯定也不知道目的设备的短地址),首先A通过广播方式发起匹配描述符请求Match_Desc_req,寻找一个允许匹配的设备(通过函数afSetMatch()函数实现)进行描述符匹配,若找到描述符匹配的设备B,该设备会处理该请求,然后返回匹配描述符响应Match_Desc_rsp给A。A利用该响应获取目的匹配设备B的短地址,再调用APSME_BindRequest()创建绑定。 注意:以上两种绑定都是利用函数APSME_BindRequest()创建绑定,不同的是,前者采用目的地址是64位的扩展地址,而后者是采用匹配描述符响应获取的16位短地址创建绑定。 12.3已知目的设备扩展地址的绑定机制实现 直接调用函数zb_BindDevice()发起绑定 void zb_BindDevice ( uint8 create, //创建还是去除绑定 uint16 commandId, //为clusterID uint8 *pDestination )//指向扩展地址的指针 void zb_BindDevice ( uint8 create, uint16 commandId, uint8 *pDestination ) { zAddrType_t destination; uint8 ret = ZB_ALREADY_IN_PROGRESS; if ( create ) //绑定 { if (sapi_bindInProgress == 0xffff) //??? { if ( pDestination ) //已知扩展地址的绑定 { destination.addrMode = Addr64Bit; //目的地址模式为64位的长地址 osal_cpyExtAddr( destination.addr.extAddr, pDestination ); //将扩展地址拷贝到长地址extAddr ret = APSME_BindRequest( sapi_epDesc.endPoint, commandId, //创建绑定请求,commandId---clusterID &destination, sapi_epDesc.endPoint ); if ( ret == ZSuccess ) { // Find nwk addr ZDP_NwkAddrReq(pDestination, ZDP_ADDR_REQTYPE_SINGLE, 0, 0 ); //请求被绑定设备的网络地址,即短地址 osal_start_timerEx( ZDAppTaskID, ZDO_NWK_UPDATE_NV, 250 );//设置定时器,定时到触发ZDO_NWK_UPDATE_NV事件 } } else //非扩展地址 { 。。。。 } } 12.4未知目的设备扩展地址的绑定机制实现 因为不知道目的设备的地址(扩展和短地址都不知道),首先A通过广播方式发起匹配描述符请求Match_Desc_req,寻找一个允许匹配的设备(通过函数adSetMatch()函数实现)进行描述符匹配,若找到描述符匹配的设备B,该设备会处理该请求,然后返回匹配描述符响应Match_Desc_rsp给A。A利用该响应获取目的匹配设备B的短地址,再调用APSME_BindRequest()创建绑定。 1)首先B设置允许匹配 在该绑定方式下,首先让被绑定的目的设备处理允许绑定模式(也就是允许匹配),可通过函数zb_AllowBind()实现(主要是调用函数afSetMatch()实现)。 timeout = 0x00---不允许绑定 timeout = 0xff---一直允许绑定 0EndPoint, FALSE); //设置不允许匹配 } else { afSetMatch(sapi_epDesc.simpleDesc->EndPoint, TRUE); //设置允许匹配 if ( timeout != 0xFF ) { if ( timeout > 64 ) //若0 ZDApp_event_loop()---SYS_EVENT_MSG---> ZDApp_ProcessOSALMsg()---AF_INCOMING_MSG_CMD---> ZDP_IncomingData()---Match_Desc_req---> ZDO_ProcessMatchDescReq() Osal 任务系统主循环函数osal_start_system()中周期循环调用ZDO任务函数ZDApp_event_loop()(通过任务表tasksArr),在该函数中,若接收到SYS_EVENT_MSG事件,则调用函数ZDApp_ProcessOSALMsg()对接收到的OSAL消息进行处理。若msgPtr->event为AF_INCOMING_MSG_CMD,在该函数中调用ZDP_IncomingData()函数,将数据从APS 子层传送到ZDO 层,若接收到Match_Desc_req请求(因为该命令没有注册过,不通过其中函数ZDO_SendMsgCBs()发送sapi应用),在该函数调用ZDO_ProcessMatchDescReq(),该函数在ZDO消息处理表zdpMsgProcs中定义。函数ProcessMatchDescReq对匹配描述符请求进行处理,处理完后再将响应Match_Desc_rsp发送回请求的设备: if (pRspSent) { pRspSent->hdr.event = ZDO_MATCH_DESC_RSP_SENT; pRspSent->nwkAddr = inMsg->srcAddr.addr.shortAddr; pRspSent->numInClusters = numInClusters; pRspSent->numOutClusters = numOutClusters; c)、开关终端A:(接收响应消息) osal_start_system()-——》执行tasksArr任务表(其中包含函数ZDApp_event_loop) ZDApp_event_loop()——》if 收到SYS_EVENT_MSG ZDApp_ProcessOSALMsg()——》if msgPtr->even为AF_INCOMING_MSG_CMD ZDP_IncomingData(msgPtr ); ——》 ZDO_SendMsgCBs( &inMsg );该函数将过滤ZDO消息,将在指定任务taskID中注册过的ZDO消息发送到应用层任务中,并发送ZDO_CB_MSG事件,如sapi_Init()注册过两种类型的clusterID的ZDO消息:NWK_addr_rsp和Match_Desc_rsp 3)A处理匹配描述符响应,创建绑定 在应用层的tasksArr任务表中添加应用层处理函数SAPI_ProcessEvent, osal_start_system()会周期循环执行tasksArr任务表中的处理函数: const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, #if defined( MT_TASK ) MT_ProcessEvent, #endif APS_event_loop, ZDApp_event_loop, SAPI_ProcessEvent }; SAPI_ProcessEvent(byte task_id, UINT16 events)——》if pMsg->event 为ZDO_CB_MSG SAPI_ProcessZDOMsgs(*inMsg)——》处理ZDO消息函数 If inMsg->clusterID 为Match_Desc_rsp 则到此为止,开关终端终于得到了匹配描述符请求的响应Match_Desc_rsp,然后对设备进行绑定 void SAPI_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg ) { switch ( inMsg->clusterID ) { 。。。 case Match_Desc_rsp: { zAddrType_t dstAddr; ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg ); if ( sapi_bindInProgress != 0xffff ) //commandID or clusterID { // Create a binding table entry dstAddr.addrMode = Addr16Bit; dstAddr.addr.shortAddr = pRsp->nwkAddr; //通过响应获取目的设备的短地址 if ( APSME_BindRequest( sapi_epDesc.simpleDesc->EndPoint, //绑定请求 sapi_bindInProgress, &dstAddr, pRsp->epList[0] ) == ZSuccess ) { osal_stop_timerEx(sapi_TaskID, ZB_BIND_TIMER); //取消定时触发事件ZB_BIND_TIMER,在zb_BindDevice()中开启了一个定时器,用于接收Match_Desc_rsp计时 osal_start_timerEx( ZDAppTaskID, ZDO_NWK_UPDATE_NV, 250 ); // Find IEEE addr ZDP_IEEEAddrReq( pRsp->nwkAddr, ZDP_ADDR_REQTYPE_SINGLE, 0, 0 ); #if defined ( MT_SAPI_CB_FUNC ) zb_MTCallbackBindConfirm( sapi_bindInProgress, ZB_SUCCESS ); #endif // Send bind confirm callback to application #if ( SAPI_CB_FUNC ) zb_BindConfirm( sapi_bindInProgress, ZB_SUCCESS ); #endif sapi_bindInProgress = 0xffff; } } } break; 。。。 12.5 Simple例子中的绑定操作实验 SimpleApp分两个实验:灯开关实验,温度传感器实验 ------------------------------------- 灯开关实验: Switch.c switch开关设备: 作为终端节点 按K1则作为终端启动;设备类型确定后再按K1则发送绑定请求命令 按K2则作为终端启动;设备类型确定后再按K2则发送灯toggle命令 按K3,删除所有绑定 Controller.c controller灯设备: 作为协调器或路由器 按K1则作为协调器启动;设备类型确定后再按K1则初始化设备允许绑定模式 按K2则作为路由器启动; ------------------------------------- 温度传感器实验 Sensor.c sensor传感器设备: 作为终端节点 按K1则作为终端启动; 按K2则作为终端启动; Collector.c 作为协调器或路由器 Collector中心收集设备:按K1作为协调器启动;设备类型确定后再按K1则初始化设备允许绑定模式 按K2作为路由器启动;设备类型确定后再按K2则禁止允许绑定模式 ------------------------------------- 请参考《SimpleApp和GenericApp实例绑定程序流程 》 12.6 在simple的开关和灯的实验中 开关设备按键2按两次后发送出clusterID TOGGLE_LIGHT_CMD_ID,灯设备收到此clusterID后会打开灯。 具体流程如下: (1) 开关设备 在开关的按键处理函数void zb_HandleKeys( uint8 shift, uint8 keys )中,若按键2按两次,调用下面的函数: // Send the command to toggle light zb_SendDataRequest( 0xFFFE, TOGGLE_LIGHT_CMD_ID, 0, (uint8 *)NULL, myAppSeqNumber, 0, 0 ); 发送clusterID TOGGLE_LIGHT_CMD_ID,开灯命令 函数原型: void zb_SendDataRequest ( uint16 destination, uint16 commandId, uint8 len, uint8 *pData, uint8 handle, uint8 txOptions, uint8 radius ) 参数: ---destination 目的设备的地址,有以下类型 - 16-Bit short address of device [0-0xfffD] - ZB_BROADCAST_ADDR sends the data to all devices in the network. -ZB_BINDING_ADDR sends the data to a previously bound device. 注: 地址有三类:短地址、广播地址、无效地址 (a) Short Address (value from 0x0000 through 0xFFF8) – The 16-bit short address of the actual destination device. (b)A Broadcast address with following valid values o 0xFFFF - Broadcast to all devices o 0xFFFD - Broadcast only to devices with receiver turned ON o 0xFFFC - Broadcast only to coordinator and all routers (c)An invalid address ( 0xFFFE ) – In this case, the destination device is not specified by the application. Instead, the stack will read the destination address from a previously established binding for the commandId, --- commandId 也就是clusterID --- len buf的长度 该函数调用了函数AF_DataRequest()发送数据,由于该实验是基于绑定的命令传输,所有目的地址设置为0xfffe,这样设备将自动地去绑定表中查找真正的目的地址。若绑定表中有多个目的地址,则信息将会复制多次分别发送出去。 (2) 灯设备 灯设备收到此clusterID后,将会有以下处理 void zb_ReceiveDataIndication( uint16 source, uint16 command, uint16 len, uint8 *pData ) { if (command == TOGGLE_LIGHT_CMD_ID) { // Received application command to toggle the LED HalLedSet(HAL_LED_1, HAL_LED_MODE_TOGGLE);//设置灯1开关切换 } } 函数HalLedSet是对硬件抽象层对灯的控制函数, /*************************************************************************************************** * @fn HalLedSet * * @brief Tun ON/OFF/TOGGLE given LEDs * * @param led - bit mask value of leds to be turned ON/OFF/TOGGLE * mode - BLINK, FLASH, TOGGLE, ON, OFF * @return None ***************************************************************************************************/ uint8 HalLedSet (uint8 leds, uint8 mode) { #if (defined (BLINK_LEDS)) && (HAL_LED == TRUE) uint8 led; HalLedControl_t *sts; switch (mode) { case HAL_LED_MODE_BLINK: 。。。break; case HAL_LED_MODE_FLASH: 。。。break; case HAL_LED_MODE_ON: case HAL_LED_MODE_OFF: case HAL_LED_MODE_TOGGLE: //开关切换 led = HAL_LED_1; leds &= HAL_LED_ALL; sts = HalLedStatusControl.HalLedControlTable; while (leds) { if (leds & led) { if (mode != HAL_LED_MODE_TOGGLE) { sts->mode = mode; /* ON or OFF */状态不变 } else { sts->mode ^= HAL_LED_MODE_ON; /* Toggle */开关状态切换 } HalLedOnOff (led, sts->mode); leds ^= led; } led <<= 1; sts++; } break; default: break; } #elif (HAL_LED == TRUE) LedOnOff(leds, mode); #else // HAL LED is disabled, suppress unused argument warnings (void) leds; (void) mode; #endif /* BLINK_LEDS && HAL_LED */ return ( HalLedState ); } 灯设备接收数据的流程: 1)afIncomingData()---> 2)afBuildMSGIncoming()-?设置消息的event= AF_INCOMING_MSG_CMD,并调用函数osal_msg_send( *(epDesc->task_id), (uint8 *)MSGpkt );---》3)将taskID添加到消息中;将消息添加到队列中;触发事件SYS_EVENT_MSG: OSAL_MSG_ID( msg_ptr ) = destination_task; // queue message osal_msg_enqueue( &osal_qHead, msg_ptr ); // Signal the task that a message is waiting osal_set_event( destination_task, SYS_EVENT_MSG ); 5) osal_set_event函数中将事件SYS_EVENT_MSG添加到tasksEvents[task_id] 在OS中: osal_start_system()--->若sapi_TaskID任务对应的事件发生(如上边的SYS_EVENT_MSG)---> SAPI_ProcessEvent()--AF_INCOMING_MSG_CMD---> SAPI_ReceiveDataIndication() ---> zb_ReceiveDataIndication() 注意:数据发送函数AF_DataRequest(&sapi_epDesc)中参数sapi_epDesc设置了taskID sapi_epDesc.task_id = &sapi_TaskID; 所以目的设备在接收此信息后会在任务sapi_ TaskID对应的处理函数中执行。