查看: 14575|回复: 15

新手请进,绝对能入门的,Sample Application解答。如果觉得有收获,请留下脚步

[复制链接]
等待2012 发表于 2012-12-6 14:11:03 | 显示全部楼层 |阅读模式
一、Sample Application工程概况描述
     Sample Application是ZStack协议栈提供的一个非常简单的演示实例,实例中的每个设备都可以发送和接收两种信息:周期信息和闪烁信息。
周期信息---------当设备加入该网络后,所有设备每隔 5S(加上一个随机数,毫秒mS为单位)发送一个周期信息,该信息的数据载荷为发送信息的次数。
闪烁信息---------通过按下按键 SW1发送一个控制LED灯闪烁的广播信息,该广播信息只针对组 1 内的所有设备。所有设备初始化都被加入组 1,所以网络一旦建立完成便可执行LED灯闪烁实验。可以通过按下设备的 SW2 退出组 1,如果设备退出组1则不再接收来自组1的消息,其按键SW1发送的消息也不再控制组1LED灯的闪烁。通过再次按下 SW2 便可让设备再次加入到组1,从而又可以接受来自组1的消息,其SW1也可以控制组1内设备的LED灯闪烁了。
当设备接收到闪烁信息会闪烁LED灯,而当接收到周期信息时协议栈没有提供具体的实验现象,留给了用户自行处理,可以根据实际需要自行更改实验代码。
在该工程中使用了两个按键SW1和SW2。即ZStack协议栈中的HAL_KEY_SW_1和HAL_KEY_SW_2。同时工程中也定义了一个事件用来处理周期信息事件,即SAMPLEAPP_SEND_PERIODIC_MSG_EVT[SampleApp.h]。

二、一般工程说明:
    在学习ZStack协议栈的时候我们要把握一个重点就是事件的产生和事件的处理。任务的初始化为事件的产生制造了“温床”,是事件产生的前提,任何工程都需要先初始化。当有事件产生OS就会调用相应的处理函数进行处理。在OS循环那一节我们可以看到在任务初始化的最后一项就是应用层的初始化,而在指向处理函数的指针数组中最后一项是对应的应用层的处理函数。应用层相关事件会由应用层处理函数进行处理。每一层都是相互对应,各司其职。

三、Sample Application工程初始化与事件的处理

3.1、Sample Application工程初始化如下:
void SampleApp_Init( uint8 task_id )
{
  SampleApp_TaskID = task_id;// OS通过数参数的传递为每一层分发任务ID,
  SampleApp_NwkState = DEV_INIT;//设定设备的网络状态为“初始化”
  SampleApp_TransID = 0;
  
#if defined ( SOFT_START )
   /*SOFT_START 是一个编译选项,如果一个网络中没有协调
器可以让设备以协调器的形式启动*/
   // 这里我们根据跳线决定设备是路由器或者是协调器,如果检测到
//跳线则为协调器否则为路由器,在设备启动提及如果定义了SOFT_START
//则设备初始化时设备的类型为可选类型。当程序执行到这里就明确了具
//体是什么类型的设备
  if ( readCoordinatorJumper() )//如果检测到跳线则设备为协调器
    zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;
  else//如果没有检测到跳线则设备为路由器
    zgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;
#endif // SOFT_START
  
#if defined ( HOLD_AUTO_START )
  //如果编译了 HOLD_AUTO_START则执行以下函数
  ZDOInitDevice(0);
#endif
  
  // 设定周期信息的地址,此地址为广播地址0xFFFF
  SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;
  SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
  SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;

  //设定闪烁信息的地址,此地址为组1的地址
  SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup;
  SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
  SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;

  // 对端点SAMPLEAPP_ENDPOINT进行描述
  SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;
  SampleApp_epDesc.task_id = &SampleApp_TaskID;
  SampleApp_epDesc.simpleDesc
            = (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc;
  SampleApp_epDesc.latencyReq = noLatencyReqs;

  // 注册端点描述符
  afRegister( &SampleApp_epDesc );

  // 注册按键,在按键机制详细解释
  RegisterForKeys( SampleApp_TaskID );

  //默认情况,所有的设备都加入组1
  SampleApp_Group.ID = 0x0001;//设定组ID
  osal_memcpy( SampleApp_Group.name, "Group 1", 7  );//设定组名
  aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//加入组

//如果编译了LCD_SUPPORTED,在液晶上显示"SampleApp"。注意:需要底层硬//件的支持
#if defined ( LCD_SUPPORTED )
  HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 );
#endif
}

3.2、Sample Application工程初始化事件处理函数如下:
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;
  if ( events & SYS_EVENT_MSG )
  {
    //从信息列表中获取SampleApp_TaskID相关的信息
MSGpkt = (afIncomingMSGPacket_t) osal_msg_receive(SampleApp_TaskID);
    while ( MSGpkt )//不为空,说明有信息
    {
      switch ( MSGpkt->hdr.event )//消息的事件
      {
        // 按键事件
        case KEY_CHANGE:
          SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
          break;
        //OTA消息事件
        case AF_INCOMING_MSG_CMD:
          SampleApp_MessageMSGCB( MSGpkt );
          break;
        // 设备状态改变事件
        case ZDO_STATE_CHANGE:
          SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
          if ( (SampleApp_NwkState == DEV_ZB_COORD)
              || (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;
        default:
          break;
      }
      // 释放内存以防内存泄露
      osal_msg_deallocate( (uint8 *)MSGpkt );
      //在列表中检索下一条信息
      MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
    }
    // 返回没有处理的事件
    return (events ^ SYS_EVENT_MSG);
  }
  // 周期信息事件
  if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
  {
    // 发送周期信息
    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 (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
  }
  // Discard unknown events
  return 0;
}

三、Sample Application工程流程:
1、周期信息:
在上一节我们看到设备上电就会自动启动(Sample Application没有编译HOLD_AUTO_START),当设备启动成功最终触发了事件ZDO_STATE_CHANGE,而此事件会向所有注册过的端点(除ZDO)发送。在Sample Application的初始化代码中SampleApp_epDesc 调用函数afRegister()进行了注册,所有OS会调用Sample Application的处理函数SampleApp_ProcessEvent()进行处理。处理代码如下:
程序代码:
case ZDO_STATE_CHANGE:
   SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
   if ( (SampleApp_NwkState == DEV_ZB_COORD)
      || (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;
处理ZDO_STATE_CHANGE:如果设备的网络状态为DEV_ZB_COORD、DEV_ROUTER或者DEV_END_DEVICE表明设备启动成功。网络状态在设备启动时被设定。如果设备启动成功,则调用了函数osal_start_timerEx()定时触发了事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT。该事件的任务ID为SampleApp_TaskID,即该事件还是由Sample Application的处理函数进行处理。定时长度为SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT(5000mS[SampleApp.h])。
处理代码如下:
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{
   //发送周期信息
   SampleApp_SendPeriodicMessage();
   //定时再次触发事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT
   osal_start_timerEx( SampleApp_TaskID,SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
                (SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );
   // 返回没有处理完成的事件
   return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
在处事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT时,协议栈调用了函数SampleApp_SendPeriodicMessage()。在SampleApp_SendPeriodicMessage()处理完成后再次定时触发了事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT,其任务ID依旧是SampleApp_TaskID,定时长度SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT(5000mS[SampleApp.h])。可以看出周期信息就是这样被周期性地触发SAMPLEAPP_SEND_PERIODIC_MSG_EVT产生的。间隔时间就是定时长度SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT(5000mS[SampleApp.h])。下面我们看SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件的处理函数SampleApp_ SendPeriodicMessage()。

程序代码:
void SampleApp_SendPeriodicMessage( void )
{
  if ( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,
                       SAMPLEAPP_PERIODIC_CLUSTERID,
                       1,
                       (uint8*)&SampleAppPeriodicCounter,
                       &SampleApp_TransID,
                       AF_DISCV_ROUTE,
                       AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
  {
  }
  else
  {
    // Error occurred in request to send.
  }
}

    在SampleApp_SendPeriodicMessage()函数中调用了数据发送函数AF_DataRequest()函数发送数据。参数中地址为SampleApp_Periodic_DstAddr在SampleApp_Init()被初始化。其中的参数簇ID为SAMPLEAPP_PERIODIC_CLUSTERID,数据载体为SampleAppPeriodicCounter。当接收端接收到该信息后会触发事件AF_INCOMING_MSG_CMD进行处理,根据簇ID接收端做出响应的处理。事件AF_INCOMING_MSG_CMD事件的处理如下:

程序代码:
case AF_INCOMING_MSG_CMD:
     SampleApp_MessageMSGCB( MSGpkt );
break;
在处理AF_INCOMING_MSG_CMD事件时调用了事件处理函数SampleApp_MessageMSGCB()进行处理。SampleApp_MessageMSGCB()函数如下:

程序代码:
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
   switch ( pkt->clusterId )
  {
    case SAMPLEAPP_PERIODIC_CLUSTERID:
      break;
    ……
  }
}
在SampleApp_MessageMSGCB()函数中根据簇ID的不同进行处理。但是Sample Application对事件SAMPLEAPP_SEND_PERIODIC_MSG_EVT的处理时什么都没有做,用户可以根据实际需要自行添加代码。

2、闪烁信息:

当按键SW1被按下时发送控制灯闪烁的广播信息,该广播信息只针对组 1 内的所有设备。按键会触发事件KEY_CHANGE。关于KEY_CHANGE事件会在按键机制详细讲解。协议栈会调用SampleApp_HandleKeys()对事件KEY_CHANGE。

程序代码:
case KEY_CHANGE:
  SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state,
((keyChange_t *)MSGpkt)->keys );
break;

SampleApp_HandleKeys()处理函数如下:
void SampleApp_HandleKeys( uint8 shift, uint8 keys )
{
  if ( keys & HAL_KEY_SW_1 )//如果是SW1被按下
  {
    SampleApp_SendFlashMessage( SAMPLEAPP_FLASH_DURATION );
  }
……
}

由上面的程序可以看出在处理按键SW1时调用了函数SampleApp_SendFlashMessage()。函数详细代码如下:
程序代码:
void SampleApp_SendFlashMessage( uint16 flashTime )
{
  uint8 buffer[3];
  buffer[0] = (uint8)(SampleAppFlashCounter++);
  buffer[1] = LO_UINT16( flashTime );
  buffer[2] = HI_UINT16( flashTime );
  
  if ( AF_DataRequest( &SampleApp_Flash_DstAddr, &SampleApp_epDesc,
                       SAMPLEAPP_FLASH_CLUSTERID,
                       3,
                       buffer,
                       &SampleApp_TransID,
                       AF_DISCV_ROUTE,
                       AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
  {
  }
  else
  {
    // Error occurred in request to send.
  }
}
在SampleApp_SendFlashMessage ()函数中调用了数据发送函数AF_DataRequest()函数发送数据。参数中地址为SampleApp_Flash_DstAddr在SampleApp_Init()被初始化,该地址为组地址,AF_DataRequest()会将相关信息发送到属于该组的所有设备。其中的参数簇ID为SAMPLEAPP_FLASH_CLUSTERID,数据载体为buffer,包括了发送信息次数和LED灯闪烁时间。当接收端接收到该信息后会触发事件AF_INCOMING_MSG_CMD进行处理,根据簇ID接收端做出响应的处理。事件AF_INCOMING_MSG_CMD事件的处理如下:

程序代码:
case AF_INCOMING_MSG_CMD:
     SampleApp_MessageMSGCB( MSGpkt );
break;
在处理AF_INCOMING_MSG_CMD事件时调用了事件处理函数SampleApp_MessageMSGCB()进行处理。SampleApp_MessageMSGCB()函数如下:

程序代码:
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
  uint16 flashTime;

  switch ( pkt->clusterId )
  {
    ……   
    case SAMPLEAPP_FLASH_CLUSTERID:
      flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
      HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
      break;  
  }
}
在SampleApp_MessageMSGCB()函数中根据簇ID的不同进行处理。Sample Application对事件SAMPLEAPP_FLASH_CLUSTERID的处理时调用了灯闪烁函数
HalLedBlink()控制LED灯的闪烁,闪烁时间由发送端设定值决定。

3、组的加入与退出
组可以将设备按一定的逻辑加以区分。向一个组发送一条信息则组内的所有设备都会收到这条信息。设备可以利用函数aps_AddGroup()加入组,利用函数aps_RemoveGroup()退出组。
协议栈里组结构体定义:
typedef struct
{
  uint16 ID;                       // 组ID
  uint8  name[APS_GROUP_NAME_LEN]; // 组名
} aps_Group_t;
由以上结构体可以看出一个组由组ID和组名唯一确定。
    在Sample Application工程中通过SW2按键来加入或者退出组1。代码如下:
按键首先触发事件KEY_CHANGE
程序代码:
case KEY_CHANGE:
  SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state,
((keyChange_t *)MSGpkt)->keys );
break;

SampleApp_HandleKeys()处理函数如下:
程序代码:
void SampleApp_HandleKeys( uint8 shift, uint8 keys )
{
……
if ( keys & HAL_KEY_SW_2 )
  {
    aps_Group_t *grp;
    grp = aps_FindGroup( SAMPLEAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );
    if ( grp )
    {
      aps_RemoveGroup( SAMPLEAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );
    }
    else
    {
      aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );
    }
}
通过函数aps_FindGroup()查找SAMPLEAPP_ENDPOINT是否加入了组1,如果加入组1则突出组1,否则加入组1。

其他常用组操作函数:
1、ZStatus_t aps_AddGroup( uint8 endpoint, aps_Group_t *group )
将endpoint加入组group
2、aps_Group_t *aps_FindGroup( uint8 endpoint, uint16 groupID )
查找endpoint端点是否加入了以groupID为组ID的组
3、uint8 aps_RemoveGroup( uint8 endpoint, uint16 groupID )
删除endpoint端点上以groupID为组ID的组
4、void aps_RemoveAllGroup( uint8 endpoint )
将endpoint端点上所有组全部删除
5、uint8 aps_CountGroups( uint8 endpoint )
统计endpoint端点组的个数

意义 发表于 2013-1-10 13:31:43 | 显示全部楼层
觉得有用,顶一下
mayulin2008 发表于 2013-1-17 11:11:47 | 显示全部楼层
{:soso_e179:}{:soso__6373743157846958732_1:}
胡杨 发表于 2013-1-17 16:32:14 | 显示全部楼层
楼主,对这个实验,理解很深刻了啊,其实,真是这样,对于z-stack里的教程,能真正的深入理解一个,就可以举一反三了
tlk214 发表于 2013-1-18 14:00:26 | 显示全部楼层
不错不错,绝对是人们级的东西~
 楼主| 等待2012 发表于 2013-1-18 14:09:52 | 显示全部楼层
胡杨 发表于 2013-1-17 16:32
楼主,对这个实验,理解很深刻了啊,其实,真是这样,对于z-stack里的教程,能真正的深入理解一个,就可以举 ...

以前一直在看绑定的例子 结果走了很大的弯路 还是从简单的做起比较好
木木在济南 发表于 2013-2-16 11:31:00 | 显示全部楼层
强烈支持,鼓掌加油
caN 发表于 2013-2-18 15:37:42 | 显示全部楼层
写的非常详细,注释什么的很好,给你个好评!
木木在济南 发表于 2013-2-18 16:29:28 | 显示全部楼层
这个都是正常可以情况下,可不可以给讲一下如果启动不成功会怎样呢?谢谢了
zz323399 发表于 2013-3-1 13:55:35 | 显示全部楼层
到目前为止自己最能看懂的一片解析
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表