之前分析了GenericApp这个例程的应用任务初始化流程,这里继续接着分析应用任务对应的事件处理程序,也就是在zigbee设备平时运行的时候,应用任务进行数据处理和收发的部分。首先来看下例程里是怎么描述该任务的:
/*********************************************************************
* @fn GenericApp_ProcessEvent
*
* @brief Generic Application Task event processor. This function
* is called to process all events for the task. Events
* include timers, messages and any other user defined events.
*
* @param task_id - The OSAL assigned task ID.
* @param events - events to process. This is a bit map and can
* contain more than one event.
*
* @return none
*/
UINT16 GenericApp_ProcessEvent( byte task_id, UINT16 events )
{
.....
}
从注释里可以看出该函数是例程应用的循环处理任务,用来处理task_id对应的各种事件的。Zstack提供的开发方式也主要就是基于此两部分,初始化和循环事件处理。
由于这个例程在TI的文档里反复强调具有较强的参考价值,所以其中的一些事件处理方式是可以就地采用的。
首先需要注意到的是该函数具有两个传入参数,task_id都很明白了,events则是一个16位的事件变量,其中的每一个都可以用来表示一个事件。所以task_processevent的作用很明显就是主要对传入参数中的各种事件进行处理了。而且OSAL只有在有对应事件的时候才会调用该processevent.另外OSAL应用任务支持最多15个自定义事件,因为还有一个事件已经被OSAL系统占用了,名为:SYS_EVENT_MSG。TI文档上是这样描述的:One task event, SYS_EVENT_MSG (0x8000), is reserved and required by the OSAL Task design. 而这个事件下面又有一系列子集合事件,包括:
1、AF_DATA_CONFIRM_CMD //本设备发送消息,目的设备收到并返回确认信息的消息事件
2、AF_INCOMING_MSG_CMD //有消息发送到本设备的消息事件
3、KEY_CHANGE //检测到有按键变化的消息事件
4、ZDO_STATE_CHANGE //zigbee联网状态发生改变的消息事件,如切换功能,网络中有节点增减都会有该消息产生并发送给已经注册AF的应用任务
5、ZDO_CB_MSG //应用任务注册接收指定的传输信息后的消息事件
这里先贴出来介绍起,后面的具体处理代码马上就会用到了~~~
接下来的代码分段分段来:
afIncomingMSGPacket_t *MSGpkt;
afDataConfirm_t *afDataConfirm;
// Data Confirmation message fields
byte sentEP;
ZStatus_t sentStatus;
byte sentTransID; // This should match the value sent
(void)task_id; // Intentionally unreferenced parameter
这些都是定义一些局部的变量,以便在接下来的事件处理中用来存储和处理相应的数据。
这里需要特别关注两个指针数据变量: afIncomingMSGPacket_t *MSGpkt和 afDataConfirm_t *afDataConfirm;
这里埋个伏笔,后面到了具体的代码再来分析。
GenericApp_ProcessEvent这个函数的代码总体结构如图4所示:
可以看出定义局部变量之后,程序就进入了两个if判断,第一个if判断是用来处理SYS_EVENT_MSG的,这个事件在分析笔记1中已经做了介绍,SYS_EVENT_MSG包含一系列的子事件,都是OSAL系统级的。说明了第一个if主要用于处理系统事件,第二个事件,GENERICAPP_SEND_MSG_EVT是用户可以自定义的15个事件中的一个,本例程中,只使用了一个事件,并被如此定义:
#define GENERICAPP_SEND_MSG_EVT 0x0001
则第二个if语句是用来处理用于自定义事件的。这就是这个函数的总体结构。接下来更细致的对着两个if结构内进行分析。需要特别注意的是,关于OSAL系统级事件的处理方式,TI官方的手册推荐用户制作自己产品的时候也最好照着例程的这种处理方式来,毕竟这个OSAL的健壮性还是值得怀疑的,所以尽量按照官方推荐的方式处理能够最大程度上避免BUG。
处理SYS_EVENT_MSG事件的代码结构如下图5所示:
从总体结构上看,这是一个循环提取task_id对应的事件,并通过switch-case结构进行事件处理的逻辑结构。这样看来,代码结构是很清晰的。下面来具体地对每一句有价值的进行分析。
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
这一句里面包含的东西可多了,首先先来看下osal_msg_receive这个函数是干什么滴,在TI提供的官方手册OSAL API.pdf(SWRA194)中关于该函数有这样的介绍,如图6所示;
通过说明可以知道该函数是任务用来获取对应的消息的,其返回值是一个指针变量,用于指向消息所在的地址。这里需要注意一点,在调用了该函数之后,需要再调用osal_msg_deallocate来释放内存。因为OSAL是动态分配内存的,在MCU中,也就是所谓的RAM。
然后关于该句,第二点是(afIncomingMSGPacket_t *),很显然,这是一个强制类型转换,把消息转换成某种特定的消息格式以便于后续的处理中,提取出相应的数据进行处理。值得注意的是(afIncomingMSGPacket_t *)这个结构体是专门用来对SYS_EVENT_MSG的事件具体信息进行封装的数据结构,其成员变量MSGpkt->hdr.event可以用来产寻具体的OSAL消息。也就是SYS_EVENT_MSG事件所对应的子集合事件。
接着继续后面的分析:
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
.....
break;
case KEY_CHANGE:
.......
break;
case AF_DATA_CONFIRM_CMD:
......
break;
case AF_INCOMING_MSG_CMD:
....
break;
case ZDO_STATE_CHANGE:
.....
break;
default:
break;
}
osal_msg_deallocate( (uint8 *)MSGpkt );
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
}
在这里,switch通过对MSGpkt->hdr.event 里面的事件进行查询,就能够进行对应的处理了。这里首先要注意第一句 :while ( MSGpkt ),这样使用不会出问题吗?前面刚分析了osal_msg_receive这个函数,关于其返回值有这样一句介绍:Return value is a pointer to a buffer containing the message or NULL if there is no received message. 所以如果MSGpkt不为0,那么就代表有消息需要处理。而且TI官方文档关于此结构有这样一句话:It is recommended that a task implement a minimum subset of all of the possible types of SYS_EVENT_MSG messages,意思很明白了,就是推荐我们在编程的时候在同一个任务里对SYS_EVENT_MSG的所有子事件进行处理。
switch-case处理完一条消息之后,然后通过调用osal_msg_deallocate释放内存,然后再提取下一条消息进行处理,直到没有新的消息为止。
至于这其中的每个case所对应的具体处理函数就不多说了,会C的应该都能看明白了。
对了,这个if结构最后结尾一句: return (events ^ SYS_EVENT_MSG);,这一句话也很重要,本句代码本身很好理解,主要关注其作用,是用于返回还未进行处理的消息的。刚开始的时候就说了,process_event这个函数的unit16 events这个行参变量里可能有多个位是被置位了的,现在只对SYS_EVENT_MSG 进行了处理,当然就只复位SYS_EVENT_MSG,其他未处理的消息肯定要返回以便后续进行处理。
下一个if结构,用于处理用户自定义的事件,代码如下:
if ( events & GENERICAPP_SEND_MSG_EVT )
{
// Send "the" message
GenericApp_SendTheMessage();
// Setup to send message again
osal_start_timerEx( GenericApp_TaskID,
GENERICAPP_SEND_MSG_EVT,
GENERICAPP_SEND_MSG_TIMEOUT );
// return unprocessed events
return (events ^ GENERICAPP_SEND_MSG_EVT);
}
里面第一句: GenericApp_SendTheMessage();,这个函数是用户自己写来用来进行操作的,通过查找定义发现,其具体代码是代用AF_dataRequest函数进行消息传输,就是向其它zigbee设备发送消息的AF层调用函数。这儿不做具体介绍,只需要知道这句大概在干嘛即可。
第二句同样也是一个调用函数: osal_start_timerEx,通过手册查找其官方描述如下图7所示:
由上可知,该函数是一个定时器函数,用于向指定的任务设置一个超时触发指定事件的。所以该段代码的意思是会一直间隔一定时间后发送消息。
这就基本是GenericApp例程的分析了。我们主要要关注其App编程结构,初始化和任务处理两部分。消息的提取方式和处理方式,和一些常用的API函数的使用。对于其中关于SYS_EVENT_MSG事件的处理是值得借鉴和模仿的。