书接上回:
在用户事件处理函数uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )中,
通过 switch ( MSGpkt->hdr.event )语句, 首先区分了以下三类事件:
1. 按键KEY_CHANGE ,处理入口
SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t*)MSGpkt)->keys );
2. AF_INCOMING_MSG_CMD 数据接收
入口函数SampleApp_MessageMSGCB( MSGpkt );
3. ZDO_STATE_CHANGE 网络状态改变
处置操作SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
所以,
从上面看到,接收数据的入口函数是SampleApp_MessageMSGCB( MSGpkt );
下面来用例程1来观察接收数据的处理:
上述3中有个判断不同网络状态的处理,也就是完成网络初始化,确定该设备类型后的操作应该在这里进行。
case ZDO_STATE_CHANGE:
//只要网络状态发生改变,就通过ZDO_STATE_CHANGE事件通知所有的任务。
//同时完成对协调器,路由器,终端的设置
SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
//if ( (SampleApp_NwkState == DEV_ZB_COORD)//实验中协调器只接收数据所以取消发送事件
if ( (SampleApp_NwkState == DEV_ROUTER) || (SampleApp_NwkState == DEV_END_DEVICE) )
{
// Start sending the periodic message in a regular interval.
//这个定时器只是为发送周期信息开启的,设备启动初始化后从这里开始
//触发第一个周期信息的发送,然后周而复始下去
osal_start_timerEx( SampleApp_TaskID,
SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
}
else
{
// Device is no longer in the network
}
break;
观察osal_start_timerEX启动了周期性执行任务,看看这个函数的定义OSAL_Timers.c:
/*********************************************************************
* @fn osal_start_timerEx
*
* @brief
*
* This function is called to start a timer to expire in n mSecs.
* When the timer expires, the calling task will get the specified event.
*
* @param uint8 taskID - task id to set timer for
* @param uint16 event_id - event to be notified with
* @param UNINT16 timeout_value - in milliseconds.
*
* @return SUCCESS, or NO_TIMER_AVAIL.
*/
uint8 osal_start_timerEx( uint8 taskID, uint16 event_id, uint16 timeout_value )
{
halIntState_t intState;
osalTimerRec_t *newTimer;
HAL_ENTER_CRITICAL_SECTION( intState ); // Hold off interrupts.
// Add timer
newTimer = osalAddTimer( taskID, event_id, timeout_value );
HAL_EXIT_CRITICAL_SECTION( intState ); // Re-enable interrupts.
return ( (newTimer != NULL) ? SUCCESS : NO_TIMER_AVAIL );
}
启动了一个间隔n ms触发的事件
看看调用的变量定义SampleApp.h:
// Send Message Timeout
#define SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT 5000 // Every 5 seconds
// Application Events (OSAL) - These are bit weighted definitions.
#define SAMPLEAPP_SEND_PERIODIC_MSG_EVT 0x0001
也就是5s会触发一次动作(END_DEVICE或者Router)
那么这个周期性动作会在哪里触发呢?
恰恰是在用户处理事件的两个入口之一(另一个是系统事件消息SYS_EVENT_MSG),这一个就是周期性事件,这样就串起来了。
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{
// Send the periodic message 处理周期性事件,
//利用SampleApp_SendPeriodicMessage()处理完当前的周期性事件,然后启动定时器
//开启下一个周期性事情,这样一种循环下去,也即是上面说的周期性事件了,
//可以做为传感器定时采集、上传任务
SampleApp_SendPeriodicMessage();
// Setup to send message again in normal period (+ a little jitter)
osal_start_timerEx( SampleApp_TaskID, SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
(SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );
// return unprocessed events 返回未处理的事件
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
关键入口一:周期发送函数SampleApp_SendPeriodicMessage();
查看定义SampleApp.c
/*********************************************************************
* @fn SampleApp_SendPeriodicMessage
*
* @brief Send the periodic message.
*
* @param none
*
* @return none
*/
//分析发送周期信息
void SampleApp_SendPeriodicMessage( void )
{
byte SendData[3]="D1";
// 调用AF_DataRequest将数据无线广播出去
if( AF_DataRequest( &SampleApp_Periodic_DstAddr,//发送目的地址+端点地址和传送模式
&SampleApp_epDesc,//源(答复或确认)终端的描述(比如操作系统中任务ID等)源EP
SAMPLEAPP_PERIODIC_CLUSTERID, //被Profile指定的有效的集群号
2, // 发送数据长度
SendData,// 发送数据缓冲区
&SampleApp_TransID, // 任务ID号
AF_DISCV_ROUTE, // 有效位掩码的发送选项
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) //传送跳数,通常设置为AF_DEFAULT_RADIUS
{
}
else
{
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
// Error occurred in request to send.
}
}
主要是调用ZSTACK的数据发送API,顺便看看地址的数据结构:
afAddrType_t SampleApp_Periodic_DstAddr;
typedef struct
{
union
{
uint16 shortAddr;
ZLongAddr_t extAddr;
} addr;
afAddrMode_t addrMode;
byte endPoint;
uint16 panId; // used for the INTER_PAN feature
} afAddrType_t;
那么其中的数据在哪儿定义的呢?跟踪到了SampleApp_Init(),真相大白:)
// Setup for the periodic message's destination address 设置发送数据的方式和目的地址寻址模式
// Broadcast to everyone 发送模式:广播发送
SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;//广播
SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; //指定端点号
SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//指定目的网络地址为广播地址
端口号的定义:
// These constants are only for example and should be changed to the
// device's needs
#define SAMPLEAPP_ENDPOINT 20 //16进制不就是0x14吗
#define SAMPLEAPP_PROFID 0x0F08
#define SAMPLEAPP_DEVICEID 0x0001
#define SAMPLEAPP_DEVICE_VERSION 0
#define SAMPLEAPP_FLAGS 0
这些数据在抓包的时候都会最终看到。敬请留意
好,回顾完了,来看下接收到的数据处理过程:
/*********************************************************************
* @fn SampleApp_MessageMSGCB
*
* @brief Data message processor callback. This function processes
* any incoming data - probably from other devices. So, based
* on cluster ID, perform the intended action.
*
* @param none
*
* @return none
*/
//接收数据,参数为接收到的数据
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
uint16 flashTime;
byte buf[3];
switch ( pkt->clusterId ) //判断簇ID
{
case SAMPLEAPP_PERIODIC_CLUSTERID: //收到广播数据
osal_memset(buf, 0 , 3);
osal_memcpy(buf, pkt->cmd.Data, 2); //复制数据到缓冲区中
if(buf[0]=='D' && buf[1]=='1') //判断收到的数据是否为"D1"
{
HalLedBlink(HAL_LED_1, 0, 50, 500);//如果是则Led1间隔500ms闪烁
#if defined(ZDO_COORDINATOR) //协调器收到"D1"后,返回"D1"给终端,让终端Led1也闪烁
SampleApp_SendPeriodicMessage();
#endif
}
else
{
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
}
break;
case SAMPLEAPP_FLASH_CLUSTERID: //收到组播数据
flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
break;
}
}
1)对比簇ID
查看宏定义SampleApp.h中:
#define SAMPLEAPP_MAX_CLUSTERS 2
#define SAMPLEAPP_PERIODIC_CLUSTERID 1
#define SAMPLEAPP_FLASH_CLUSTERID 2
抓包来看看,结合前面协议栈的分析,有几个发现:
a) 按程序设计,是EP先发送数据“D1”,间隔应该是5秒,观察发送时隙:
间隔基本是5秒
b)观察MAC层数据
EP发出:
EC发回针对收到这一帧的ACK:
跟着在发出广播数据:
c)发送接口
AF_DataRequest( &SampleApp_Periodic_DstAddr,//发送目的地址+端点地址和传送模式
&SampleApp_epDesc,//源(答复或确认)终端的描述(比如操作系统中任务ID等)源EP
SAMPLEAPP_PERIODIC_CLUSTERID, //被Profile指定的有效的集群号
2, // 发送数据长度
SendData,// 发送数据缓冲区
&SampleApp_TransID, // 任务ID号
AF_DISCV_ROUTE, // 有效位掩码的发送选项
AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) //传送跳数,通常设置为AF_DEFAULT_RADIUS
都在NWK层和APS层得到充分体现:
先看NWK层:
DestAddress为广播方式0xFFFF,源地址为EP的16位短地址0x5F76
RADIUS就是跳数
对AF_DEFAULT_RADIUS顺藤摸瓜AF.h
// Default Radius Count value
#define AF_DEFAULT_RADIUS DEF_NWK_RADIUS
再摸nwk.h:
// the default network radius set twice the value of <nwkMaxDepth>
#define DEF_NWK_RADIUS ( 2 * BEACON_MAX_DEPTH )
再摸nwk.h:
#define BEACON_MAX_DEPTH 0x0F
从抓包数据看是0x1E,恰恰是0x0F*2=30 =0x1E!
再看APS层:
DestEndPoint就是刚才定义的端口号
#define SAMPLEAPP_ENDPOINT 20 //16进制不就是0x14吗
端和源一样都是0x14
因为初始化时定义了:
// Setup for the flash command's destination address - Group 1 组播发送
SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup; //组寻址
SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; //指定端点号
SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;//组号0x0001
// Fill out the endpoint description. 定义本设备用来通信的APS层端点描述符
SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT; //指定端点号
SampleApp_epDesc.task_id = &SampleApp_TaskID; //SampleApp 描述符的任务ID
SampleApp_epDesc.simpleDesc
= (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;//SampleApp简单描述符
SampleApp_epDesc.latencyReq = noLatencyReqs; //延时策略
ClusterID和刚才发送函数里指定的ID对应上0x0001,注意是四位(对接其他服务要靠它了)
#define SAMPLEAPP_PERIODIC_CLUSTERID 1
APS counter是字节计数?暂且这么理解
最后是有效载荷APS payload “44 31”,就是D1的ASCII码
看看完整包的RAW DATA:
Packet index: 1
Length: 27
Raw data (hex): 61 88 74 F1 FF 00 00 76 5F 08 00 FF FF 76 5F 1E 37 08 14 01 00 08 0F 14 05 44 31
RSSI [dBm]: -69
Correlation value: 108
CRC OK: 1
对应数据帧格式描述:
抓包工具看到的29长度包括了“净长”27+RSSI+校验值
前两个字节61 88为帧控制域,查定义:
其中,帧类型:
低位在前:
61对应:
bit0-3为“0x01”: 0-2为值为001, bit3=0,保护使能关闭
bit4-7 值为“0x60”:bit5=1,bit6=1,代表ACK使能,内部PAN(该子域1表示发送到同一PAN网络,如果为0,则发送值不同PAN网络)
88对应:
bit8-11值为0x08, bit10-11=10
bit12-15 值为0x80,bit14-15=10
均表示采用16位短地址模式
74 :帧序号
F1FF: PANID
0000:目标短地址
765F:(高位在前) 源短地址
接下来从08 00开始 是NWK 帧,查NWK帧格式定义:
所以08 00值帧控制域,定义:
bit0-1=00 ,所以此帧为数据帧
协议版本bit2-bit5=0100 ,协议版本号为0x02, zigbee2006以后的版本
bit6-7=00 ,发现路由
本应用里禁止路由(程序在哪儿定义? )
bit9=0 ,安全使能关闭
接下来是NWK payload, FFFF广播地址,76 5F 源地址(低位在前),1E是跳转深度,37是广播帧序列号
从08 14开始进入APS层(协议第二章详细描述),APS层 的帧格式:
所以0x14是目标端点的定义,上面提过了。
08代表帧控制域,来看帧控制域格式:
b1b0=00,帧类型,代表数据帧, 01命令,10确认ACK,11保留
b3b2=10 , 传送模式为广播,其他00为单播,01为间接寻址,11为组寻址
ACK请求关闭
进入APS数据域:
0x14: 目标端点
01 00: 0x0001 APS簇 ID,低位在前
08 0F: 0x0F08 ProfileID
在SampleApp.h中定义:
// These constants are only for example and should be changed to the
// device's needs
#define SAMPLEAPP_ENDPOINT 20
#define SAMPLEAPP_PROFID 0x0F08
#define SAMPLEAPP_DEVICEID 0x0001
#define SAMPLEAPP_DEVICE_VERSION 0
#define SAMPLEAPP_FLAGS 0
#define SAMPLEAPP_MAX_CLUSTERS 2
#define SAMPLEAPP_PERIODIC_CLUSTERID 1
#define SAMPLEAPP_FLASH_CLUSTERID 2
0x14 : 源端口
0x05 : counter计数器
最后 0x44 0x31 :净数据 ASCII码对应“D1”
代码对应抓包数据解析完毕!
这应该是理解应用层的重要一课,主要理解了应用层的工作流程,下一节应该是网络层。