分享

ZStack OSAL的事件(event)与消息(message)——part1 & par...

 一起听海520 2016-10-06

ZStack OSAL的事件(event)与消息(message)——part1

本文转载自:http://blog.csdn.net/ceci_zhou/article/details/9787349


在zstack中,有两种方式在OSAL的任务(task)中添加自定义的功能:事件(event)和消息(message)。


这篇主要讲讲和event有关的事,和message有关的事请移步

ZStack OSAL的事件(event)与消息(message)——part2

这里http://blog.csdn.net/ceci_prayer/article/details/9835399


一、事件

事件是驱动任务去执行某些操作的条件,当系统产生了一个事件,将这个触发传递给相应的任务后,任务才能执行一个相应的操作。

OSAL通过一个16位宽度的数组来管理事件,意味着OSAL最多可以支持16个事件,其中最高位(0x08000,SYS_EVENT_MSG)系统保留,用户可以使用的事件有15个。

事件的使用很简单:
1)需要找个地方定义事件的ID,实际上是指定该事件占用事件数组的哪个位。如#define MY_EVENT 0x02,占用bit1。

2)在需要触发事件的地方调用osal_set_event(task_id, event_flag) ,这个函数有两个参数,一个是接收事件任务的ID,另一个参数指定事件ID.

3)在相应任务的处理函数,检查该事件执行相应代码即可。

4)清除事件标识。


一个event被调用的过程:

1. main() -> osal_start_system()在ZMain.c中;

2.osal_start_system() -> XX_event在OSAL.C中。

第一步简单易懂,重要的是第二步的实现。

void osal_start_system( void )
{
#if !defined ( ZBIT ) && !defined ( UBIT )
  for(;;)  // Forever Loop
#endif
  {
    uint8 idx = 0;

    osalTimeUpdate();
    Hal_ProcessPoll();  // This replaces MT_SerialPoll() and osal_check_timer().
    
    do {
      if (tasksEvents[idx])  // Task is highest priority that is ready.
      {                      /* idx 越小,任务的优先级越高   */
        break;               /* 较高优先级的任务总是优先处理 */
      }
    } while ( idx < tasksCnt);
    /* 得到了待处理的具有最高优先级的任务索引号 idx  */

    if (idx < tasksCnt)
    {
      uint16 events;
      halIntState_t intState;

      HAL_ENTER_CRITICAL_SECTION(intState); /* 进入临界区---保存EA状态然后置EA = 0 */
      events = tasksEvents[idx];  //处理该idx的task的event
      tasksEvents[idx] = 0;  // Clear the Events for this task.
      HAL_EXIT_CRITICAL_SECTION(intState);  /* 退出临界区---恢复EA状态 */

      events = (tasksArr[idx])( idx, events );  //调用该idx个任务的事件处理函数(函数指针指向的函数)

      HAL_ENTER_CRITICAL_SECTION(intState);
      tasksEvents[idx] |= events;  // Add back unprocessed events to the current task.
      HAL_EXIT_CRITICAL_SECTION(intState);
    }


加粗的代码就是上述的第二步,可以看出,这一步并不是直接调用的,而是根据不同的参数(idx,events)来动态调用的。这里涉及到一个很重要的结构体tasksArr[]:


const pTaskEventHandlerFn tasksArr[] = { 
  macEventLoop,
  nwk_event_loop,
  Hal_ProcessEvent,
#if defined( MT_TASK )
  MT_ProcessEvent,
#endif
  APS_event_loop,
  ZDApp_event_loop,
  ControlWifi_ProcessEvent, 
  GenericApp_ProcessEvent
};


可以看出这个结构体是一个数组,但是是一个什么类型的数组呢?pTaskEventHandlerFn类型的。这个类型有代表什么的?接下来我们找到这个类型的定义看一下:

typedef unsigned short (*pTaskEventHandlerFn)( unsigned char task_id, unsigned short event );

又见函数指针类型。所以,tasksArr[]中的每一个元素都是一个指向函数的指针。但是,这个结构体还是没有解决我们调用_ProcessEvent或者别的event的问题。

这时候,就要回到我们之前加粗的那个语句了:events = (tasksArr[idx])( idx, events )。可以看出,这个语句是调用tasksArr[]中第idx个函数,然后把返回值赋给events(events是若干个标志位,每个标志位代表一个事件的发生与否)。至于调用的是哪个函数,就跟参数(idx,events)有关了,而这个参数的选取,具体见这行代码之前的若干代码。


-----------------------------------------补充的分割线----------------------------------------

之前我们讲到了一个事件的处理函数是怎么被调用的,但是一个事件是怎么被触发的,我只是寥寥写了一些。这种事,不写下来总会忘记的,所以还是补充一下有关事件触发的那些事

还记得events这个参数吗?events里对应的标志位决定着相应的事件处理函数被调用,只要某一个事件的标志位变成了1,处理函数就被调用。那么什么时候这个标志位会从0变成1呢?


(以下代码显示了这些标志位都被初始化为0

void osalInitTasks( void )
{
  uint8 taskID = 0;


  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));


这是由最开始时提到的那个重要的API osal_set_event(task_id, event_flag)完成的。你想要在哪些动作完成后触发这个事件,就调用这个函数,将相应的标志位置1,那么在之后协议栈运行的过程中就会根据需要调用这个事件的处理函数了。


ps:如果是消息的话,使用osal_msg_send可以完成类似的功能。有关方面,请移步

这里http://blog.csdn.net/ceci_prayer/article/details/9835399


ZStack OSAL的事件(event)与消息(message)——part2

本文转载自:http://blog.csdn.net/ceci_zhou/article/details/9835399


ZStack OSAL的事件(event)与消息(message)——part 1 (有关event的那些事)

在这里 http://blog.csdn.net/ceci_prayer/article/details/9787349


二、消息

消息可以理解为带有附加信息的事件。最典型的一类便是按键消息,它同时产生了一个哪个按键被按下了附加信息。所以在OnBoard_SendKeys这个

数中,不仅向GenericApp发送了事件,还通过调用osal_msg_send函数向GenericApp发送了一个消息,这个消息记录了这个事件的附加信息

一般来说,一个消息总是和一个事件对应。当协议栈接收到消息后,在ProcessEvent函数中有以下语句:

if ( events & SYS_EVENT_MSG )
  {
    MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
    while ( MSGpkt )
    {
      switch ( MSGpkt->hdr.event )

     {

case ……

     }

可以看出,消息是被当作系统事件接收的,接收到之后会找到消息对应的event,之后进行相应的处理。

消息的使用与事件类似,但是使用了不同的函数触发:

byte osal_msg_send( byte destination_task, byte *msg_ptr ) 

这个函数有两个参数分别为接收事件任务的ID,另一个是指向消息的指针。它的功能是向一个任务发送命令或数据消息,此外,这个函数也会触发目标任务的SYS_EVENT_MSG(系统消息任务)。


----------------------------以上是之前学习内容的分割线-----------------------------------


之前对于消息的分析是正确的,但过于笼统了。今天我重新梳理了一下关于消息(message)的思路,在这里记录一下。

首先我们需要明确一个概念,,msg是用来干什么的?msg和event有什么不同?

我们已经知道了,event是一个事件,当这个事件发生以后,会触发相应的事件处理函数。即event是事先定义好的,但不知道会在哪个确定时间点被触发。

而消息不同。顾名思义,消息是用来传递信息的,即有两个主体(如下图中的task1和task2),在这两个主体想要通信的时候,就会用到消息。


上面这个图是我自已总结的有关zstack中消息的用法(如果有不正确的地方希望大家指正^_^)。

step1:osal_msg_allocate

可以看出,task1想要给task2发送消息(这两个任务可能属于一个设备,也可能属于不同的设备,这一点稍后再说),于是task1就得先产生一个msg,这时候就要用到osal_msg_allocation给这个消息分配一个缓存:

uint8 * osal_msg_allocate( uint16 len
{
  osal_msg_hdr_t *hdr;

  if ( len == 0 )
    return ( NULL );

 hdr = (osal_msg_hdr_t *) osal_mem_alloc( (short)(len sizeof( osal_msg_hdr_t )) );
  if ( hdr )
  {
    hdr->next = NULL;
    hdr->len = len;
    hdr->dest_id = TASK_NO_TASK;
    return ( (uint8 *) (hdr 1) );
  }
  else
    return ( NULL );
}

注意加粗的代码。首先看一下这个函数的参数len,是什么呢?查手册可以知道,len是msg的长度。那osal_msg_hdr_t *hdr又是什么呢?看一下osal_msg_hdr_t这个数据结构的定义:

typedef struct
{
  void   *next;
  uint16 len;
  uint8  dest_id;
} osal_msg_hdr_t;

这个实际上是消息的头部。再看下一句:

hdr = (osal_msg_hdr_t *) osal_mem_alloc( (short)(len sizeof( osal_msg_hdr_t )) );

这句代码是给消息分配缓存区的,而缓存区的大小是len sizeof( osal_msg_hdr_t )),也就是消息的大小 消息头的大小。

到这里我们隐约能感觉到,一个消息应该是由两部分组成:消息头和消息的实际内容,消息头是协议栈定义好的,而消息的内容则应该是我们自己添加的。大家再关注一下osal_msg_allocate的代码,可以看出,除了分配缓存以外,它还赋给了消息头一个初始值,但是却没有对消息本身做什么处理。因为消息是要留给大家自己定义的,所以osal_msg_allocate将指向消息头hdr下一位的指针做为函数的返回值,以便大家添加自己的消息代码。

至于消息的具体定义,可以分为两种:系统消息和用户消息。系统消息什么的拜托协议栈就好啦,而用户消息就需要大家根据实际的要求动手了^0^

(在接下来的内容中,完整的消息被认为是已经定义好的,直接拿来用就可以了。)

step2:osal_msg_send

send函数有两个参数,一个是目标task的地址,另一个是指向消息的指针。
uint8 osal_msg_send( uint8 destination_task, uint8 *msg_ptr ) 
{
  if ( msg_ptr == NULL )
    return ( INVALID_MSG_POINTER );

  if ( destination_task >= tasksCnt )
  {
    osal_msg_deallocate( msg_ptr );
    return ( INVALID_TASK );
  }

  // Check the message header
  if ( OSAL_MSG_NEXT( msg_ptr ) != NULL ||
       OSAL_MSG_ID( msg_ptr ) != TASK_NO_TASK )
  {
    osal_msg_deallocate( msg_ptr );
    return ( INVALID_MSG_POINTER );
  }

  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 );

  return ( SUCCESS );
}

其他内容就不用过过关注了,看看加粗的代码:osal_msg_enqueue  
这句看起来是入栈操作,嗯嗯,就是这样^0^~其实在协议栈内部维护着一个消息队列,也就是说,消息是按照队列的方式被操作的,所以就有了入栈出栈的内容,具体实现就不关注了,这里只要明白发送的消息被放到(应该是目标任务的)消息队列里就好啦,别的东西用到的时候再说吧。

再看一下  osal_set_event( destination_task, SYS_EVENT_MSG );这句很重要啊,因为它告诉我们,在发送了一个消息之后,会触发目标协议栈的系统事件SYS_EVENT_MSG,也就是说,虽然消息的处理方式与事件类似,但是有关消息的事件是系统事件哦~想要处理消息的话知道去哪找了吧?

step3:osal_msg_receive

又是一个重量级的API,这个API关系到我们怎么取出消息。先看看在哪里会调用这个API,预告一下,会是我们十分熟悉的地方哦~

uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;

  if ( events & SYS_EVENT_MSG )
  {
    MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
    while ( MSGpkt )
    {
      switch ( MSGpkt->hdr.event )
      {
        // Received when a key is pressed
        case KEY_CHANGE:
          SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
          break;

        // Received when a messages is received (OTA) for this endpoint
        case AF_INCOMING_MSG_CMD:
          SampleApp_MessageMSGCB( MSGpkt );
          break;

ProcessEvent!这个是大家最早熟悉的部分了吧?在这个API中,首先定义了一个数据结构:afIncomingMSGPacket_t *MSGpkt,具体看一下:

typedef struct
{
  osal_event_hdr_t hdr;     /* OSAL Message header */
  uint16 groupId;           /* Message's group ID - 0 if not set */
  uint16 clusterId;         /* Message's cluster ID */
  afAddrType_t srcAddr;     /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP,
                               it's an InterPAN message */
  uint16 macDestAddr;       /* MAC header destination short address */
  uint8 endPoint;           /* destination endpoint */
  uint8 wasBroadcast;       /* TRUE if network destination was a broadcast address */
  uint8 LinkQuality;        /* The link quality of the received data frame */
  uint8 correlation;        /* The raw correlation value of the received data frame */
  int8  rssi;               /* The received RF power in units dBm */
  uint8 SecurityUse;        /* deprecated */
  uint32 timestamp;         /* receipt timestamp from MAC */
  afMSGCommandFormat_t cmd; /* Application Data */
} afIncomingMSGPacket_t;


有点吓人啊!不过仔细看一眼,除了第一句osal_event_hdr_t hdr之外,别的好像都不是很重要嘛~对的,以af_开头,说明这个数据是经过无线传输后收到的,也就是由另一个设备上的task发送给本设备的,所以必然会在我们原来消息的基础上层层打包,加上许多其他内容了。

再来看一下关键的osal_event_hdr_t结构体:
typedef struct
{
  uint8  event;
  uint8  status;
} osal_event_hdr_t;
并不是很复杂,只包括了一个事件发生的标志(event)和状态(status)。那它又是用来做什么的呢?
答案是,它是用来找到事件对应的消息的。

前面说过,在发送一个消息之后osal_mem_send,会触发一个系统事件。我们现在遇到的情况是,有了这个事件之后,怎么找到它对应的消息,这时就需要上门介绍的机制了,定义一个指向osal_event_hdr_t类型的指针,然后用osal_msg_receive找到这个消息并让前面的指针MSGpkt指向它。(在代码给出的例子中指向消息的指针类型是afIncomingMSGPacket_t,但是从刚才的分析可以看出,如果是在同一个设备中的两个task通信的话,也就是不需要无线传输的时候,用osal_event_hdr_t类型的指针就可以满足要求了)。

至于这一步所说的重要的函数osal_msg_receive,它的功能就是从协议栈维护的消息队列中取出相应的消息,想要了解就看看源代码吧~

现在我们既有事件,又有消息,接下来就进入愉快的处理带有消息的事件部分吧~
以case AF_INCOMING_MSG_CMD为例,嗯嗯,只要以MSGpkt为参数,调用相应的处理函数就好啦:SampleApp_MessageMSGCB( MSGpkt );很愉快哦~


呼,终于结束了,写出来居然有这么长~最后附上前一篇有关event的地址,方便学习咯~

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多