使用的协议栈版本及例子信息:
ZigBee2006\ZStack-1.4.3-1.2.1个人注释版_5.24\Projects\zstack\Samples\SimpleApp\CC2430DB
ZigBee2006\ZStack-1.4.3-1.2.1个人注释版_5.24\Projects\zstack\Samples\GenericApp\CC2430DB
记录下个人对这两个例程绑定的理解:
两个例程:SimpleApp和GenericApp,其中SimpleApp分两个实验:灯开关实验,温度传感器实验
建立一个绑定表格有3种方式:
(1)ZDO绑定请求:一个试运转的工具能告诉这个设备制作一个绑定报告
(2)ZDO终端设备绑定请求:设备能告诉协调器他们想建立绑定表格报告。该协调器将使协调并在这两个设备上创建绑定表格条目。
(3)设备应用:在设备上的应用能建立或管理一个绑定表格。
SimpleApp例程默认使用方法1,GenericApp可以使用方法1或2.
-------------------------------------
灯开关实验:
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则禁止允许绑定模式
-------------------------------------
GenericApp实验:
设备按K2发送终端设备绑定请求(这个说法待定)
设备按K4发送描述符匹配请求
*********************************************************************************************************************
*********************************************************************************************************************
*********************************************************************************************************************
1、灯开关实验绑定流程
1.1、按键处理函数
Controller管理器(灯)设备,其zb_HandleKeys()按键处理函数:
/************************************
void zb_HandleKeys( uint8 shift, uint8 keys )
{
…………
{
if ( keys & HAL_KEY_SW_1 )
{
/*初始化myAppState = APP_INIT,等灯设备类型确定下来后,如果再按K1,
则此时myAppState != APP_INIT,因而执行绑定的初始化*/
if ( myAppState == APP_INIT )
{
…………
logicalType = ZG_DEVICETYPE_COORDINATOR;//协调器
…………
}
else
{
// Initiate a binding
zb_AllowBind( myAllowBindTimeout ); //默认为10S,myAllowBindTimeout=10
}
}
//--------------------------
if ( keys & HAL_KEY_SW_2 )
{
…………
logicalType = ZG_DEVICETYPE_ROUTER; //路由器
…………
}
//--------------------------
…………
}
}
/*****************************
可以看到,按K1则作为协调器启动;设备类型确定后再按K1则初始化设备允许绑定模式;
按K2则作为路由器启动;
再来看下Switch终端(开关)设备,其zb_HandleKeys()按键处理函数:
*****************************
void zb_HandleKeys( uint8 shift, uint8 keys )
{
…………
{
if ( keys & HAL_KEY_SW_1 )
{
if ( myAppState == APP_INIT )
{
…………
logicalType = ZG_DEVICETYPE_ENDDEVICE; //终端设备
…………
}
else
{
// Initiate a binding with null destination
zb_BindDevice(TRUE, TOGGLE_LIGHT_CMD_ID, NULL); //发送绑定请求(不带目的扩展地址)
}
}
//--------------------------
if ( keys & HAL_KEY_SW_2 )
{
if ( myAppState == APP_INIT )
{
…………
logicalType = ZG_DEVICETYPE_ENDDEVICE; //终端设备
…………
}
else
{
// Send the command to toggle light
zb_SendDataRequest( 0xFFFE, TOGGLE_LIGHT_CMD_ID, 0,
(uint8 *)NULL, myAppSeqNumber, 0, 0 ); //发送toggle命令
}
}
//--------------------------
if ( keys & HAL_KEY_SW_3 ) //按K3,删除所有绑定
{
// Remove all existing bindings
zb_BindDevice(FALSE, TOGGLE_LIGHT_CMD_ID, NULL);
}
…………
}
}
*****************************
可以看到,按K1则作为终端启动;设备类型确定后再按K1则发送绑定请求命令
按K2则作为终端启动;设备类型确定后再按K2则发送灯toggle命令
按K3,删除所有绑定
流程:
当灯设备和开关设备逻辑类型确定,网络建立完成后,灯设备按K1允许绑定;开关设备按K1发送绑定请求(默认是未知扩展地址的绑定),后面具体参见<协议栈simpleApp例程中两种绑定机制>这篇记录.
*********************************************************************************************************************
*********************************************************************************************************************
*********************************************************************************************************************
2、温度传感器实验
2.1、按键处理函数
Collector中心收集设备,其按键处理函数zb_HandleKeys()
************************************
void zb_HandleKeys( uint8 shift, uint8 keys )
{
…………
if ( keys & HAL_KEY_SW_1 )
{
if ( myAppState == APP_INIT )
{
…………
logicalType = ZG_DEVICETYPE_COORDINATOR; //协调器
…………
}
else
{
// Turn ON Allow Bind mode indefinitely
zb_AllowBind( 0xFF ); //一直处于允许绑定模式
HalLedSet( HAL_LED_1, HAL_LED_MODE_ON ); //LED1亮
}
}
//--------------------------
if ( keys & HAL_KEY_SW_2 )
{
…………
logicalType = ZG_DEVICETYPE_ROUTER; //路由器
…………
}
else
{
// Turn OFF Allow Bind mode indefinitely
zb_AllowBind( 0x00 ); //禁止允许绑定模式
HalLedSet( HAL_LED_1, HAL_LED_MODE_OFF ); //LED1灭
}
}
…………
}
}
************************************
可以看到,按K1作为协调器启动;设备类型确定后再按K1则初始化设备允许绑定模式;
按K2作为路由器启动;设备类型确定后再按K2则禁止允许绑定模式
Sensor传感器设备,其按键处理函数zb_HandleKeys()
************************************
void zb_HandleKeys( uint8 shift, uint8 keys )
{
…………
else
{
if ( keys & HAL_KEY_SW_1 )
{
if ( myAppState == APP_INIT )// APP_INIT = Initial state = 0
{
…………
logicalType = ZG_DEVICETYPE_ENDDEVICE; //0x02
…………
}
}
//--------------------------
if ( keys & HAL_KEY_SW_2 )
{
if ( myAppState == APP_INIT )
{
…………
logicalType = ZG_DEVICETYPE_ENDDEVICE;
…………
}
}
…………
}
}
************************************
可以看到,按K1则作为终端启动;
按K2则作为终端启动;
从上面的按键函数发现,只有collector中心收集设备可以通过按键使其处于允许绑定模式,而看不到Sensor传感器设备通过按键发送绑定请求,那Sensor传感器设备是通过什么来发送绑定请求的?
2.2、发送绑定请求
在zb_HandleOsalEvent()函数中有这样一个事件:
if ( event & MY_FIND_COLLECTOR_EVT )
{
// Find and bind to a collector device
zb_BindDevice( TRUE, SENSOR_REPORT_CMD_ID, (uint8 *)NULL );
}
因此肯定有个地方会触发事件MY_FIND_COLLECTOR_EVT ,通过寻找发现有以下三个函数设置了触发事件MY_FIND_COLLECTOR_EVT的软定时器:
************************************
void zb_StartConfirm( uint8 status )
{
if ( status == ZB_SUCCESS )
{
myAppState = APP_START; //Sensor has joined network
// Set event to bind to a collector
osal_start_timerEx( sapi_TaskID, MY_FIND_COLLECTOR_EVT, myBindRetryDelay );
}
else
{
// Try joining again later with a delay
osal_start_timerEx( sapi_TaskID, MY_START_EVT, myStartRetryDelay );
}
}
************************************
void zb_SendDataConfirm( uint8 handle, uint8 status )
{
if ( status != ZSuccess )
{
// Remove bindings to the existing collector
zb_BindDevice( FALSE, SENSOR_REPORT_CMD_ID, (uint8 *)NULL );
myAppState = APP_START; //Sensor has joined network
myApp_StopReporting();
// Start process of finding new collector with minimal delay
osal_start_timerEx( sapi_TaskID, MY_FIND_COLLECTOR_EVT, 1 );
}
else
{
// send data ??
}
}
************************************
void zb_BindConfirm( uint16 commandId, uint8 status )
{
if ( ( status == ZB_SUCCESS ) && ( myAppState == APP_START ) )
{
myAppState = APP_BOUND; //Sensor is bound to collector 绑定成功
//Start reporting sensor values
myApp_StartReporting(); //开始报告传感器数据
}
else //启动不成功或没有入网,绑定失败
{
// Continue to discover a collector
osal_start_timerEx( sapi_TaskID, MY_FIND_COLLECTOR_EVT, myBindRetryDelay );
}
}
************************************
三个都是确认函数:
第一个是确认设备成功运行加入网络后,软定时触发MY_FIND_COLLECTOR_EVT事件
第二个是确认发送数据失败后,移除现有绑定,重新软定时触发MY_FIND_COLLECTOR_EVT事件
第三个是确认绑定失败后,重新软定时触发MY_FIND_COLLECTOR_EVT事件
通过zb_StartConfirm()看到如果设备启动正常并成功加入网络,10s钟后触发MY_FIND_COLLECTOR_EVT事件(myBindRetryDelay = 10000 milliseconds);第二三个确认函数是在一些运行失败后重新发现collector并与其建立绑定(重新发现的collector可能是另外一个collector).
下面看下对MY_FIND_COLLECTOR_EVT事件的处理,在zb_HandleOsalEvent()函数中:
************************************
void zb_HandleOsalEvent( uint16 event )
{
uint8 pData[2];
if ( event & MY_START_EVT )
{
zb_StartRequest();
}
if ( event & MY_REPORT_TEMP_EVT ) //温度报告
{
// Read and report temperature value
pData[0] = TEMP_REPORT; //0x01(用来指示这是温度数据)
pData[1] = myApp_ReadTemperature(); //温度值
//0xFFFE = INVALID_NODE_ADDR = ZB_BINDING_ADDR
zb_SendDataRequest( 0xFFFE, SENSOR_REPORT_CMD_ID, 2, pData, 0, AF_ACK_REQUEST, 0 );
/*因为是周期性地读取温度电池值,则每次事件处理完后要为下一次读取而重新开启一个定时器*/
osal_start_timerEx( sapi_TaskID, MY_REPORT_TEMP_EVT, myTempReportPeriod );
}
if ( event & MY_REPORT_BATT_EVT ) //电池电量报告
{
// Read battery value
// If battery level low, report battery value
pData[0] = BATTERY_REPORT; //0x02(用来指示这是电池能量数据)
pData[1] = myApp_ReadBattery();
zb_SendDataRequest( 0xFFFE, SENSOR_REPORT_CMD_ID, 2, pData, 0, AF_ACK_REQUEST, 0 );
osal_start_timerEx( sapi_TaskID, MY_REPORT_BATT_EVT, myBatteryCheckPeriod );
}
if ( event & MY_FIND_COLLECTOR_EVT )
{
// Find and bind to a collector device
zb_BindDevice( TRUE, SENSOR_REPORT_CMD_ID, (uint8 *)NULL );
}
}
************************************
可以看到最终调用zb_BindDevice( TRUE, SENSOR_REPORT_CMD_ID, (uint8 *)NULL ),是未知扩展地址的绑定,故接下来的流程参见<协议栈simpleApp例程中两种绑定机制>这篇记录.
流程:
当传感器设备和中心收集设备逻辑类型确定,网络建立完成后,collector中心收集设备按K1进入允许绑定模式.sensor传感器设备自动发送绑定请求(默认未知扩展地址的绑定).
2.3、关于sensor传感器设备进入zb_StartConfirm()的流程.
事实上这部分个人还是很模糊,好多细节不理解,大概记录下.
看下SAPI_Init()函数:
************************************
void SAPI_Init( byte task_id )
{
uint8 startOptions;
sapi_TaskID = task_id;
sapi_bindInProgress = 0xffff; //不允许绑定过程
sapi_epDesc.endPoint = zb_SimpleDesc.EndPoint; //0x02
sapi_epDesc.task_id = &sapi_TaskID;
sapi_epDesc.simpleDesc = (SimpleDescriptionFormat_t *)&zb_SimpleDesc;
sapi_epDesc.latencyReq = noLatencyReqs;
// Register the endpoint/interface description with the AF 注册终端
afRegister( &sapi_epDesc );
// Turn off match descriptor response by default
afSetMatch(sapi_epDesc.simpleDesc->EndPoint, FALSE); //关闭匹配描述符响应
// Register callback evetns from the ZDApp //从ZDApp登记返回事件
ZDO_RegisterForZDOMsg( sapi_TaskID, NWK_addr_rsp );
ZDO_RegisterForZDOMsg( sapi_TaskID, Match_Desc_rsp );
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
// Register for HAL events
RegisterForKeys( sapi_TaskID ); //登记按键事件
if ( HalKeyRead () == HAL_KEY_SW_1)
{
// If SW5 is pressed and held while powerup, force auto-start and nv-restore off and reset
startOptions = ZCD_STARTOPT_CLEAR_STATE | ZCD_STARTOPT_CLEAR_CONFIG; //复位启动状态和配置
//ZCD_NV_STARTUP_OPTION作为默认启动项配置,存到&startOptions中
//ZCD_NV_STARTUP_OPTION在按键时便赋值为ZCD_STARTOPT_AUTO_START
//这里关系到对事件ZB_ENTRY_EVENT的处理,顺利的话就发送一个启动协议栈请求
zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
zb_SystemReset(); //复位系统(更新启动项配置后,重启设备)
}
#endif // HAL_KEY
#ifdef LCD_SUPPORTED
Print8(HAL_LCD_LINE_2 ,20,"Simple",1);
#endif
// Set an event to start the application
osal_set_event(task_id, ZB_ENTRY_EVENT); //设置事件,启动应用
}
************************************
SAPI_Init()中初始化startOptions为:
startOptions = ZCD_STARTOPT_CLEAR_STATE | ZCD_STARTOPT_CLEAR_CONFIG;//复位启动状态和配置
然后通过zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions )把ZCD_NV_STARTUP_OPTION赋给startOptions,而ZCD_NV_STARTUP_OPTION在按键处理函数中都被赋值为ZCD_STARTOPT_AUTO_START.因此这里startOptions最终为ZCD_STARTOPT_AUTO_START.
SAPI_Init()最后有句:
osal_set_event(task_id, ZB_ENTRY_EVENT);
来看SAPI_ProcessEvent()对ZB_ENTRY_EVENT事件的处理:
************************************
SAPI_ProcessEvent()
{
if ( events & ZB_ENTRY_EVENT ) /*sapi启动事件*/ // 0x1000
{
uint8 startOptions;
// Give indication to application of device startup
zb_HandleOsalEvent( ZB_ENTRY_EVENT );
// LED off cancels HOLD_AUTO_START blink set in the stack
HalLedSet (HAL_LED_4, HAL_LED_MODE_OFF);
//读配置
zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
if ( startOptions & ZCD_STARTOPT_AUTO_START ) //自动启动
{
zb_StartRequest(); //发送协议栈启动请求
}
else
{
// blink leds and wait for external input to config and restart
//等待外部输入来配置和重启
HalLedBlink(HAL_LED_2, 0, 50, 500);
#if defined SENSOR //如果是传感器设备
logicalType = ZG_DEVICETYPE_ENDDEVICE;
zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType);
// Do more configuration if necessary and then restart device with auto-start bit set
zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
startOptions = ZCD_STARTOPT_AUTO_START;
zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
zb_SystemReset();
#endif
}
//-------------------(本小段更新于2010.6.03)
// This must be the last event to be processed
//应用事件events:
/*
// Application osal event identifiers
// Bit mask of events ( from 0x0000 to 0x00FF )
#define MY_START_EVT 0x0001
#define MY_REPORT_TEMP_EVT 0x0002
#define MY_REPORT_BATT_EVT 0x0004
#define MY_FIND_COLLECTOR_EVT 0x0008
*/
//而ZB_USER_EVENTS = 0x00FF;以上应用事件和ZB_USER_EVENTS相与都不为0,因此这些
//应用事件事实上都是通过ZB_USER_EVENTS进入zb_HandleOsalEvent()进行处理的!
if ( events & ( ZB_USER_EVENTS ) ) /*用户事件*/
{
// User events are passed to the application
zb_HandleOsalEvent( events );
// Do not return here, return 0 later
}
//-------------------
}
************************************
一、MY_FIND_COLLECTOR_EVT事件是在哪里联系到ZB_ENTRY_EVENT事件,从而通过ZB_ENTRY_EVENT事件调用zb_HandleOsalEvent()进行处理??? (不是通过ZB_ENTRY_EVENT,因为ZB_ENTRY_EVENT=0x1000; MY_FIND_COLLECTOR_EVT=0x0008,两者相与为0不会执行这里if内的程序,而是通过下面的ZB_USER_EVENTS=0x00FF,和MY_FIND_COLLECTOR_EVT相与不为0再执行zb_HandleOsalEvent().事实上本例所有用户应用事件都是通过ZB_USER_EVENTS被处理的!)
二、其实我在zb_HandleOsalEvent()并没看到事件ZB_ENTRY_EVENT,位运算也不对,这里怎么回事??? (估计就是没什么处理,不同类型节点的zb_HandleOsalEvent定义是不同的,simpleApp例子有些类型节点对这个函数也没有定义,个人觉得主要还是根据用户实际情况进行添加.比如开启后就开始触发一些事件等…)
三、如果startOptions为ZCD_STARTOPT_AUTO_START,则调用zb_StartRequest(),则必然会有个zb_StartConfirm(). 在每种类型设备的按键函数中都可以找到:startOptions = ZCD_STARTOPT_AUTO_START;也就是通过外部按键来改变了startOptions.当然这里还有一种情况,如上面函数中#if defined SENSOR,下面也有startOptions = ZCD_STARTOPT_AUTO_START;也就是说如果传感器设备迟迟没有按键按下来确定设备类型重启,这里将自动完成配置,事实上传感器节点无论按K1/2都作为终端启动.
设备的每次重启zb_SystemReset(),都将进行全部初始化,包括SAPI_Init().
因此大概流程如下:(这里我自己都认为不怎么准确,仅供参考)
设备启动,外部按键选择设备类型,startOptions为ZCD_STARTOPT_AUTO_START——SAPI_Init()触发ZB_ENTRY_EVENT事件启动应用——SAPI_ProcessEvent()处理ZB_ENTRY_EVENT事件——startOptions为ZCD_STARTOPT_AUTO_START,发送协议栈启动请求zb_StartRequest()——协议栈启动响应zb_StartConfirm(),如果启动成功,即入网成功则启动软定时器 osal_start_timerEx( sapi_TaskID, MY_FIND_COLLECTOR_EVT, myBindRetryDelay )——溢出则触发MY_FIND_COLLECTOR_EVT事件发送绑定请求.
*********************************************************************************************************************
*********************************************************************************************************************
*********************************************************************************************************************
3、GenericApp实验
3.1、按键函数
先来看下按键函数:
************************************
void GenericApp_HandleKeys( byte shift, byte keys )
{
…………
{
…………
if ( keys & HAL_KEY_SW_2 )
{
HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF );
/*终端设备绑定请求*/
// Initiate an End Device Bind Request for the mandatory endpoint
dstAddr.addrMode = Addr16Bit;
dstAddr.addr.shortAddr = 0x0000; // Coordinator
ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(),
GenericApp_epDesc.endPoint,
GENERICAPP_PROFID,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
FALSE );
}
…………
if ( keys & HAL_KEY_SW_4 )
{
HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF );
/*描述符匹配请求*/
// Initiate a Match Description Request (Service Discovery)
dstAddr.addrMode = AddrBroadcast; //广播传输
dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR; //0xFFFF
ZDP_MatchDescReq( &dstAddr, NWK_BROADCAST_SHORTADDR,
GENERICAPP_PROFID,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
GENERICAPP_MAX_CLUSTERS, (cId_t *)GenericApp_ClusterList,
FALSE );
}
}
}
************************************
可以看到按K2为发送终端设备绑定请求,按K4为发送描述符匹配请求.
对于按K2,这里我知道终端设备有效,路由器按K2应该有效,但协调器按K2是个什么现象?目前不知道.而描述符匹配请求,个人觉得三种类型节点都可以发送.但在GenericApp这个例程中我找不到有初始化设备处于允许绑定模式的语句.
下面分别记录下个人对GenericApp实验这两种绑定方式的理解.
3.2、终端设备绑定请求
这应该属于建立绑定表三种方式的第二种:设备能告诉协调器他们想建立绑定表格报告,该协调器将使协调并在这两个设备上创建绑定表格条目.
这里我假设两个终端设备向协调器发送终端设备绑定请求,因为无实验平台,暂时只能靠假设来理解.
(1)终端设备向协调器发送终端设备绑定请求
调用ZDP_EndDeviceBindReq(),目的地址设为0x0000;ZDP_EndDeviceBindReq()调用fillAndSend(),默认clusterID为End_Device_Bind_req,最后通过AF_DataRequest()发送出去.
(2)协调器收到终端设备绑定请求End_Device_Bind_req
这个信息被传送到ZDO层,来看下ZDP_IncomingData()函数:
************************************
void ZDP_IncomingData( afIncomingMSGPacket_t *pData )
{
…………
//通过这个函数把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++;
}
…………
}
************************************
因为ZDO信息处理表zdpMsgProcs[ ]没有对应的End_Device_Bind_req簇,因此没有调用ZDO信息处理表中的处理函数,但是前面的ZDO_SendMsgCBs()会把这个终端设备绑定请求发送到登记过这个ZDO信息的任务中去,那哪里登记过End_Device_Bind_req这个ZDO信息呢? 来看个函数:ZDApp_RegisterCBs():
************************************
void ZDApp_RegisterCBs( void )
{
#if defined ( ZDO_IEEEADDR_REQUEST ) || defined ( REFLECTOR )
ZDO_RegisterForZDOMsg( ZDAppTaskID, IEEE_addr_rsp );
#endif
#if defined ( ZDO_NWKADDR_REQUEST ) || defined ( REFLECTOR )
ZDO_RegisterForZDOMsg( ZDAppTaskID, NWK_addr_rsp );
#endif
#if defined ( ZDO_COORDINATOR ) //协调器
ZDO_RegisterForZDOMsg( ZDAppTaskID, Bind_rsp );
ZDO_RegisterForZDOMsg( ZDAppTaskID, Unbind_rsp );
ZDO_RegisterForZDOMsg( ZDAppTaskID, End_Device_Bind_req );//End_Device_Bind_req
#endif
#if defined ( REFLECTOR )
ZDO_RegisterForZDOMsg( ZDAppTaskID, Bind_req );
ZDO_RegisterForZDOMsg( ZDAppTaskID, Unbind_req );
#endif
}
************************************
这个函数通过ZDO_RegisterForZDOMsg()登记特定ZDO信息到任务ZDAppTaskID中,包括End_Device_Bind_req,看下ZDO_RegisterForZDOMsg()函数的说明:
************************************
* @fn ZDO_RegisterForZDOMsg
*
* @brief Call this function to register of an incoming over
* the air ZDO message - probably a response message
* but requests can also be received.
* Messages are delivered to the task with ZDO_CB_MSG
* as the message ID.
*
* @param taskID - Where you would like the message delivered
* @param clusterID - What message?
************************************
默认的信息类型是ZDO_CB_MSG.回过来看上面的ZDP_IncomingData()中ZDO_SendMsgCBs()是如何传送End_Device_Bind_req到任务ZDAppTaskID中去的.
ZDO_SendMsgCBs():
************************************
uint8 ZDO_SendMsgCBs( zdoIncomingMsg_t *inMsg )
{
…………
while ( pList )
{
if ( pList->clusterID == inMsg->clusterID )
{
…………
msgPtr->hdr.event = ZDO_CB_MSG; //事件为ZDO_CB_MSG
osal_msg_send( pList->taskID, (uint8 *)msgPtr );
ret = TRUE;
}
}
pList = (ZDO_MsgCB_t *)pList->next;
}
return ( ret );
}
************************************
可以看到通过osal_msg_send()来触发任务ZDAppTaskID的ZDO_CB_MSG事件.osal_msg_send()中调用的是osal_set_event( destination_task, SYS_EVENT_MSG ),事件为SYS_EVENT_MSG.
任务ZDAppTaskID事件处理函数ZDApp_event_loop()通过判断SYS_EVENT_MSG来调用ZDApp_ProcessOSALMsg()处理SYS_EVENT_MSG事件;
ZDApp_ProcessOSALMsg()再通过判断ZDO_CB_MSG来调用ZDApp_ProcessMsgCBs()处理ZDO_CB_MSG事件;
ZDApp_ProcessMsgCBs()再通过判断End_Device_Bind_req调用以下程序来处理End_Device_Bind_req终端设备绑定请求:
************************************
case End_Device_Bind_req: //终端设备绑定请求!
{
ZDEndDeviceBind_t bindReq;
ZDO_ParseEndDeviceBindReq( inMsg, &bindReq ); //解析终端绑定请求信息
ZDO_MatchEndDeviceBind( &bindReq ); //匹配终端绑定
// Freeing the cluster lists - if allocated.
if ( bindReq.numInClusters )
osal_mem_free( bindReq.inClusters );
if ( bindReq.numOutClusters )
osal_mem_free( bindReq.outClusters );
}
break;
************************************
这里ZDO_ParseEndDeviceBindReq()是把接收到的终端绑定请求信息inMsg解析赋给bindReq.
重点看下ZDO_MatchEndDeviceBind( &bindReq ):
************************************
* @fn ZDO_MatchEndDeviceBind()
*
* @brief
*
* Called to match end device binding requests
*
* @param bindReq - binding request information
* @param SecurityUse - Security enable/disable
*
* @return none
*/
void ZDO_MatchEndDeviceBind( ZDEndDeviceBind_t *bindReq )
{
zAddrType_t dstAddr;
uint8 sendRsp = FALSE;
uint8 status;
//--------------------------
// Is this the first request?
if ( matchED == NULL ) //如果接收到的是第一个终端绑定请求
{
// Create match info structure
matchED = (ZDMatchEndDeviceBind_t *)osal_mem_alloc( sizeof ( ZDMatchEndDeviceBind_t ) );
if ( matchED )
{
// Clear the structure
osal_memset( (uint8 *)matchED, 0, sizeof ( ZDMatchEndDeviceBind_t ) );
// Copy the first request's information 拷贝请求信息
if ( !ZDO_CopyMatchInfo( &(matchED->ed1), bindReq ) ) //如果拷贝不成功
{
status = ZDP_NO_ENTRY;
sendRsp = TRUE;
}
}
else //如果匹配信息空间开辟不成功
{
status = ZDP_NO_ENTRY;
sendRsp = TRUE;
}
if ( !sendRsp ) //内存空间分配及拷贝都成功
{
// Set into the correct state 状态为等待另一个请求
matchED->state = ZDMATCH_WAIT_REQ;
// Setup the timeout 等待时间计时
APS_SetEndDeviceBindTimeout( AIB_MaxBindingTime, ZDO_EndDeviceBindMatchTimeoutCB );
}
}
//-----------
else //如果接收到的不是第一个终端绑定请求
{
matchED->state = ZDMATCH_SENDING_BINDS; //状态为绑定中
// Copy the 2nd request's information 拷贝第二个请求信息
if ( !ZDO_CopyMatchInfo( &(matchED->ed2), bindReq ) ) //如果拷贝不成功
{
status = ZDP_NO_ENTRY;
sendRsp = TRUE;
}
//-----------
// Make a source match for ed1 以ed1为源
//对ed1终端绑定请求与ed2进行簇列表的匹配比较,函数返回的是ed1与ed2匹配的簇的数目
matchED->ed1numMatched = ZDO_CompareClusterLists(
matchED->ed1.numOutClusters, matchED->ed1.outClusters,
matchED->ed2.numInClusters, matchED->ed2.inClusters, ZDOBuildBuf );
if ( matchED->ed1numMatched ) //有相匹配的簇
{
// Save the match list 保存匹配簇列表
matchED->ed1Matched = osal_mem_alloc( (short)(matchED->ed1numMatched * sizeof ( uint16 )) ); //分配内存空间
if ( matchED->ed1Matched ) //如果内存空间分配成功
{
osal_memcpy( matchED->ed1Matched, ZDOBuildBuf, (matchED->ed1numMatched * sizeof ( uint16 )) ); //保存匹配簇
}
else //如果内存空间分配不成功
{
// Allocation error, stop
status = ZDP_NO_ENTRY;
sendRsp = TRUE;
}
}
//-----------
// Make a source match for ed2 以ed2为源
//对ed2终端绑定请求与ed1进行簇列表的匹配比较,函数返回的是ed2与ed1匹配的簇的数目
matchED->ed2numMatched = ZDO_CompareClusterLists(
matchED->ed2.numOutClusters, matchED->ed2.outClusters,
matchED->ed1.numInClusters, matchED->ed1.inClusters, ZDOBuildBuf );
if ( matchED->ed2numMatched )
{
// Save the match list
matchED->ed2Matched = osal_mem_alloc( (short)(matchED->ed2numMatched * sizeof ( uint16 )) );
if ( matchED->ed2Matched )
{
osal_memcpy( matchED->ed2Matched, ZDOBuildBuf, (matchED->ed2numMatched * sizeof ( uint16 )) );
}
else
{
// Allocation error, stop
status = ZDP_NO_ENTRY;
sendRsp = TRUE;
}
}
//如果两个请求有相匹配的簇,并且已经保存成功
if ( (sendRsp == FALSE) && (matchED->ed1numMatched || matchED->ed2numMatched) )
{
// Do the first unbind/bind state
ZDMatchSendState( ZDMATCH_REASON_START, ZDP_SUCCESS, 0 );//发送响应信息给两个终端请求设设 //备,这里面也调用ZDP_EndDeviceBindRsp()
}
else //不成功
{
status = ZDP_NO_MATCH;
sendRsp = TRUE;
}
}
//--------------------------
if ( sendRsp ) //如果匹配簇保存不成功
{
// send response to this requester 发送响应信息给两个请求者
dstAddr.addrMode = Addr16Bit;
dstAddr.addr.shortAddr = bindReq->srcAddr;
//ZDP_EndDeviceBindRsp()默认命令ID为End_Device_Bind_rsp
ZDP_EndDeviceBindRsp( bindReq->TransSeq, &dstAddr, status, bindReq->SecurityUse ); //status = ZDP_NO_MATCH
if ( matchED->state == ZDMATCH_SENDING_BINDS )
{
// send response to first requester
dstAddr.addrMode = Addr16Bit; //第一个请求终端
dstAddr.addr.shortAddr = matchED->ed1.srcAddr;
ZDP_EndDeviceBindRsp( matchED->ed1.TransSeq, &dstAddr, status, matchED->ed1.SecurityUse );
}
// Process ended - release memory used 释放内存空间
ZDO_RemoveMatchMemory();
}
}
//-------------------------
************************************
ZDO_MatchEndDeviceBind()函数,如果检测到接收到的绑定请求是第一个,则分配空间保存并计时等待第二个绑定请求;如果是第二个绑定请求则分别以两个请求为源绑定来与另一个请求匹配簇和Profile_ID,如果Profile_ID相同且有相匹配的簇,则保存(协调器里进行),并通过ZDMatchSendState()函数来发送匹配成功的响应信息End_Device_Bind_rsp给两个请求终端(这个函数里面也调用了ZDP_EndDeviceBindRsp());如果没有匹配或无法保存则发送匹配失败信息End_Device_Bind_rsp给两个请求终端.(这里纯属个人大概的理解)
在《Z-Stack 开发指南》中对End_Device_Bind_req的处理流程是这么描述的:
------------------------------------
当协调器接收到第一个绑定请求时,他会在一定的时限内保留这一请求并等待第二个请求的出现。(默认的最长时间间隔是16秒)。
一旦协调器接收到两个需要匹配的终端设备绑定请求时,它就会启动绑定过程,为发出请求的设备建立源绑定条目。假设在ZDO终端设备绑定请求中找到匹配,协调器将采取以下步骤:
1. 协调器发送一个ZDO解除绑定请求给第一个设备。终端设备绑定 是一个切换过程,所以解除绑定请求需要发送给第一个设备,以便移除一个已有的绑定条目。
2. 等待ZDO解除绑定的应答,如果返回的状态是ZDP_NO_ENTRY(没有已存在的绑定),协调器可以发送一个ZDO绑定请求,在源设备(ZDP_EndDeviceBindReq()第一个参数指定的地址)中建立绑定条目。假如此时返回的状态是ZDP_SUCCESS,可继续处理第一个设备的簇标识符(解除绑定指令已经移除了绑定条目,即已经切换完成)。
3. 等待ZDO绑定应答。收到以后,继续处理第一个设备的下一个簇标识符。
4. 等第一个设备完成了以后,在第二个设备上实行同样的过程。
5. 等第二个设备也完成了,协调器向两个设备发送ZDO终端设备绑定应答消息。
注意打开编译选项:REFLECTOR和ZDO_COORDINATOR
字面意思个人不是很理解.就这样过掉先.
------------------------------------
(3)终端设备接收到绑定响应End_Device_Bind_rsp
同样发送到ZDO层,因为在GenericApp_Init()中登记了这个End_Device_Bind_rsp:
ZDO_RegisterForZDOMsg( GenericApp_TaskID, End_Device_Bind_rsp );
因此在ZDP_IncomingData()接收到End_Device_Bind_rsp信息后会通过ZDO_SendMsgCBs()把这个终端绑定响应信息传送到任务GenericApp中,同协调器接收到End_Device_Bind_req类似,这里会触发任务GenericApp的事件SYS_EVENT_MSG—>ZDO_CB_MSG,在GenericApp_ProcessEvent()中调用GenericApp_ProcessZDOMsgs()来处理,来看下最终对End_Device_Bind_rsp的处理:
case End_Device_Bind_rsp:
if ( ZDO_ParseBindRsp( inMsg ) == ZSuccess )
{
// Light LED
HalLedSet( HAL_LED_4, HAL_LED_MODE_ON );
}
以上是一个终端设备发送终端绑定请求到协调器的大概流程,另一个终端设备流程也一样,很多细节自己也比较模糊,也不准备去磨时间了.个人在想,如果终端节点A与终端节点B以这种方式绑定,那从A发信息到B是不是一定都要经过协调器,在协调器绑定表中查找目的地址呢?因为A与B的绑定信息存储在协调器绑定表中.
3.3描述符匹配请求
对于GenericApp实验中这种方式的绑定,就像前面说过,首先我找不到哪里有设置设备处于允许绑定模式的程序,找不着…我也不准备找了,还是等后面可以做实验了再来看,这样可以更直观地看到现象,比我在这凭程序乱猜测好.
继续假设,终端设备节点A处于允许绑定模式,另一个终端设备B按K4通过调用ZDP_MatchDescReq()广播发送终端描述符匹配请求Match_Desc_req.那接下来的流程具体参见<协议栈simpleApp例程中两种绑定机制>中的未知扩展地址绑定流程.大概上就是节点A接收到这个描述符匹配请求Match_Desc_req,通过匹配比较,成功则发送一个描述符匹配响应Match_Desc_rsp给节点B,节点B接收到这个响应信息,通过这个响应信息所携带的节点A的16位网络地址,建立A与B的绑定.但对于GenericApp实验我发现最后不是真正意义上的绑定,因为没有通过APSME_BindRequest()建立绑定表.它对Match_Desc_rsp的处理如下:
GenericApp_ProcessZDOMsgs()
{
case Match_Desc_rsp: //描述符匹配响应
{
ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg );
if ( pRsp )
{
if ( pRsp->status == ZSuccess && pRsp->cnt ) //匹配成功
{
GenericApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
GenericApp_DstAddr.addr.shortAddr = pRsp->nwkAddr;
// Take the first endpoint, Can be changed to search through endpoints
GenericApp_DstAddr.endPoint = pRsp->epList[0];
// Light LED
HalLedSet( HAL_LED_4, HAL_LED_MODE_ON );
}
osal_mem_free( pRsp );
}
}
break;
}
GenericApp实验是直接把Match_Desc_rsp所携带的与节点B描述符相匹配的节点A的网络地址赋给 GenericApp_DstAddr,这样以后节点B的GenericApp_SendTheMessage()函数就会把信息直接发送到节点A,我想这样也是实现A与B的绑定吧.
GenericApp实验的绑定程序上个人只是大概地了解了下,流程不一定准确,甚至偏离正轨,仅供参考…最后引用《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号设备需要维护一个端点和地址的记录。
许多应用服务最终都会使用第二种方法。