Z-Stack + OSAL操作系统

Z-Stack + OSAL操作系统

Zigbee协议栈与Zigbee协议

协议是一系列的通信标准,通信双方需要共同按照这一标准进行正常的数据发射和接收。协议栈是协议的具体实现形式,简单地说就是协议栈是协议和用户之间的一个接口。开发人员通过协议栈来使用这个协议。进而实现数据的收发。

Zigbee的体系结构由称为层的各模块组成。每一层为其上层提供特定的服务:即由数据服务实体提供数据传输服务;管理实体提供所有的其他管理服务。毎个服务实体懣过相应的服务接入点(SAP为其上层提供一个接口,每个服务接入点通过服务原语来完成所对应的功能, ZigBee协议的体系结构如下图所示:

在这里插入图片描述

Zigbee基本概念

设备类型(一个网络必须最少有一个协调器,多个路由器和多个终端设备)

  • Coordinator(协调器)

    负责启动整个网络,它也是网络的第一个设备。协调器选择一个信道和一个网络ID(也称之为PAN ID),随后启动整个网络,协调器也可以用来协助建立网络中安全层和应用层绑定(bingings)。

  • Router(路由器)

    允许其他设备加入网路,多眺路由和协助它自己的由电池供电的终端设备的通讯。

    通常,路由器希望一直处于活动状态,因此他必须使用主电源供电。但是当使用树状网络拓扑结构时,允许路由间隔一定的周期操作一次,这样就可以使用电磁给其供电。

  • End-Device(终端设备)

    终端设备没有特定的维持网络结构的责任,它可以睡眠或唤醒,终端设备对存储空间(特别是RAM的需要)比较小。

网络结构

在这里插入图片描述

上图是一个简单的Zigbee网络示意图,其中红色节点为Coordinator,黄色节点为Router,绿色节点为End-Device。

协议栈规范

协议栈规范由Zigbee联盟定义指定。在同一网络中的设备必须符同一个协议栈规范。

Zigbee联盟为Zigbee协议栈2007定义了两个规范:Zigbee个Zigbee PRO。所有的设备只要遵守了该规范,即使在不同的厂商买的不同的设备同样可以形成网络。

如果开发正改变了规范,它的设备只能在自己的产品中使用。不能与其他的产品通信,更改网络之后的规范称之为“特定网络”规范。

协议栈ID号可以通过查询设备发送的beacon帧获得。在设备加入网络之前,首先需要确认协议栈规范的ID,“特定网络”规范ID号为0;Zigbee协议栈规范的ID号为1;Zigbee PRO协议的ID号为2.协议栈规范的ID(STACK_PROFLLE_ID)在nwk_globals.h中定义:

// nwk_globals.h

// Controls various stack parameter settings
#define NETWORK_SPECIFIC      0
#define HOME_CONTROLS         1
#define ZIGBEEPRO_PROFILE     2
#define GENERIC_STAR          3
#define GENERIC_TREE          4

#if defined ( ZIGBEEPRO )
  #define STACK_PROFILE_ID      ZIGBEEPRO_PROFILE
#else
  #define STACK_PROFILE_ID      HOME_CONTROLS
#endif

// 在fwConfig.cfg文件定义
/* Enable Zigbee-Pro */
-ZIGBEEPRO
拓扑结构

支持星状、树(簇)状和网状三种网络拓扑结构。

  • 星型拓扑

    最简单的一种拓扑形式,包含一个Coordinator(协调者)节点和一系列的End Device(终端)节点,每一个End Device节点只能和Coordinator节点进行通信。两个End Device节点之间通讯必须通过Coordintor节点进行信息的转发。

  • 树型拓扑

  • Mesh拓扑(网状拓扑)

在Z-Stack中网络拓扑结构定义如下:

// Controls the operational mode of network
#define NWK_MODE_STAR         0
#define NWK_MODE_TREE         1
#define NWK_MODE_MESH         2



#if ( STACK_PROFILE_ID == ZIGBEEPRO_PROFILE )
    #define MAX_NODE_DEPTH      20
    #define NWK_MODE            NWK_MODE_MESH  // 网状网络
    #define SECURITY_MODE       SECURITY_COMMERCIAL
  #if   ( SECURE != 0  )
    #define USE_NWK_SECURITY    1   // true or false
    #define SECURITY_LEVEL      5
  #else
    #define USE_NWK_SECURITY    0   // true or false
    #define SECURITY_LEVEL      0
  #endif
信标与非信标模式

Zigbee网络的工作模式可分为信标(Beaeon)和非信标(Non-beaeon)两种模式。

  • 信标模式

    实现了我网络中所有设备的同步工作和同步休眠。以达到最大程度节省功耗。

    协调器负责以一定的时间间隔(一般在15ms-mins之间)想网络广播发送信标帧。

  • 非信标模式

    只允许终端设备进行周期性休眠,协调器和所有路由器设备必须长期处于工作状态。

    父节点为终端设备子节点焕春数据,终端设备主动向其父节点提取数据的机制。实现终端设备的周期性(周期可设置)休眠。

地址定义

Zigbee设备有两种类型的地址。一种是64位IEEE地址,即MAC地址,另一种是16位网络地址。

  • 64位地址

    全球唯一地址(MAC地址)由设备制造商设置,设备在生命周期一直拥有。

  • 16位网络地址

    设备加入网络后分配的,在网络中唯一。用来在网路哟中鉴别设备和发送数据。协调器的网络地址为0x0000。

地址分配机制,Zigbee 2007 PRo使用随机地址分配机制,对新加入的节点使用随机地址分配,为保证网络内地址分配不重复,使用其余的随机地址再进行分配。当一个节点加入时,将接收到父节点的的随机分配地址然后后产生”设备声明“(包含分配到的网络地址和IEEE地址)发送至网络中的其余节点。如果另一个节点有着同样的网络地址,则通过路由器广播“网络状态-地址冲突”至网络中的所有节点。所有发生网络地址冲突的节点更改自己的网络地址,然后再发起“设备声明”检测新的网络地址是否冲突。

终端设备不会广播“地址冲突”,他们的父节点会帮助完成。如果一个终端设备发生了”地址冲突“,他们的父节点发送”重新加入“消息至终端设设备,并要求他们更改网络地址,然后终端设备再发起”设备声明“检测新的网络地址是否冲突。

当接收到“设备声明”后,关联表和绑定表将被更新使用心得网络地址,但是路由表不会被更新。

在每个路由加如网络之前,寻址方案需要知道和配是一些参数。

  • MAX_DEPTH(最大网络深度)
  • MAX_ROUTERS(最多路由数)
  • MAX_CHILDREN(最多子节点数)
#if ( STACK_PROFILE_ID == ZIGBEEPRO_PROFILE )
  uint8 CskipRtrs[1] = {0};
  uint8 CskipChldrn[1] = {0};
  • Cm(nwkMaxChildren):每个父节点可以联接的子节点的总个数;
  • Rm(nwkMaxRouters):在Cm中可以连接的子节点的总个数;
  • Lm:最大网络深度,协调器深度为0;

这三个参数的值在Z-stack中分别由变量CskipChlarn、CskipRtrs、MAX_NODE_DEPTH决定。这三个变量可以在NWK中的nwk_globals.c和nwk_globals.h两个文件中找到。

寻址

为了向一个在ZigBee网络中的设备发送数据,应用程序通常使用AF_DataRequest()函数。数据包将要发送给一个afAddType_t(在AF.h中定义)类型的目标设备。

typedef struct
{
  union
  {
    uint16      shortAddr;
    ZLongAddr_t extAddr;
  } addr;
  afAddrMode_t addrMode;
  uint8 endPoint;
  uint16 panId;  // used for the INTER_PAN feature
} afAddrType_t;

注意,除了网络地址之外,还要制定地址模式参数。目的地址模式可以设置为以下几个值:

typedef enum
{
  afAddrNotPresent = AddrNotPresent,
  afAddr16Bit      = Addr16Bit,
  afAddr64Bit      = Addr64Bit,
  afAddrGroup      = AddrGroup,
  afAddrBroadcast  = AddrBroadcast
} afAddrMode_t;

因为在ZigBee网络中,数据包可以单点传送(unicast),多点传送(multicast)或则广播传送,所以必须有地址模式参数。一个单点传送数据包只发送给一个设备,多点传送数据包则要传送一组设备。二广播数据则要发送给网络中的所有节点。

  • 单点传送( Unicast)

    Dicast是标准寻址模式,它将数据包发送给一个已经知道网络地址的网络设备。将afAddrMode设置为Addr16Bit并且在数据包中携带目标设备地址。

  • 间接传送( ndirect)

    当应用程序不知道数据包的目标设备在哪里的时候使用的模式。将模式设为AddrNotPresent并且目标地址没有指定。取代它的是从发送设备的栈的绑定表中査找目标设备。这种特点称之为源绑定。当数据向下发送到达栈中,从绑定表中査找并且使用该目标地址。这样,数据包将被处理成为一个标准的单点传送数据包。如果在绑定表中找到多个设备,则向每个设备都发送一个数据包的拷贝。

  • 广播传送( broadcast)

    当应用程序需要将数据包发送给网络的每一个设备时,使用这种模式。地址模式设置为AddrBroadcast。目标地址可以设置为下面广播地址的种:NWK BROADCAST SHORTADDR DEVALL(0xFFF数据包将被传送到网络上的所有设备,包括睡眠中的设备。对于睡眠中的设备,数据包将被保留在其父亲节点直到査询到它,或者消息超时( NWK INDIRECT MSG TIMEOUT在f8 wConifg cfg中NWK BROADCAST SHORTADDR DEVRXON( OXFFFD)数据包将被传送到网络上的所有在空闲时打开接收的设备( RXONWHENIDLE),也就是说,除了睡眠中的所有设备。NWK BROADCAST SHORTADDR DEVZCZR(0xFFC)数据包发送给所有的路由器,包括协调器。

  • 组寻址( Group Addressing)

    当应用程序需要将数据包发送给网络上的一组设备时,使用该模式。地址模式设置为afAddr Group并且addr.shortAddr设置为组|D。在使用这个功能呢之前必须在网络中定义组。(参见Z- -stack Ap|文档中的 aps AddGroup0函数注意组可以用来关联间接寻址。再绑定表中找到的目标地址可能是是单点传送或者是一个组地址。另外,广播发送可以看做是一个组寻址的特例。下面的代码是一个设备怎样加入到个D为1的组当中:

    // Group Table Element
    typedef struct
    {
      uint16 ID;                       // Unique to this table
      uint8  name[APS_GROUP_NAME_LEN]; // Human readable name of group
    } aps_Group_t;
    
设备

一个节点就是一个设备,对应一个无线单片机(CC2530);一个设备有一个射频终端,具有唯一的IEEE地址(64位)和网络地址(16位)在协议栈中不同的设备有相应的配置文件:

  • 协调器配置文件:f8wCoord.cfg
  • 路由器配置文件:f8wRouter.cfg
  • 终端设备配置文件:f8wEndev.cfg
重要设备地址

应用程序可能需要知道它的设备地址和父亲地址。使用下面的函数获取设备地址(在 ZStackAP中定义)

  • NLME GetshortAddr(返回本设备的16位网络地址
  • NLME GetExtAddr0—返回本设备的64位扩展地址
  • 使用下面的函数获取该设备的父亲设备的地址:
  • NLME GetCoordShortAddri0—返回本设备的父亲设备的16位网络地址
  • NLME GetCoord ExtAddrt0——返回本设备的父亲设备的64位扩展地址
如何使用ZigBee协议栈(一般步骤)
  1. 组网:调用协议栈的组网函数、加入网络函数,实现网络的建立与节点的加入。
  2. 发送:发送节点调用协议栈的无线数据发送函数,实现无线数据发送。
  3. 接收:接收节点调用协议栈的无线数据接收函数,实现无线数据接收
协议栈工作流程

在这里插入图片描述

自己添加的应用任务程序在Zstack中的调用过程:

main() → osal_init_system() → osalInitTask() → SampleApp_Init()

  • osal_init_system:初始化操作系统

      // Initialize the Memory Allocation System
      osal_mem_init();
    
      // Initialize the message queue
      osal_qHead = NULL;
    
      // Initialize the timers
      osalTimerInit();
    
      // Initialize the Power Management System
      osal_pwrmgr_init();  
    
      // Initialize the system tasks.
      osalInitTasks();    /*重要 任务初始化函数,我们只关心这个函数*/
    
      // Setup efficient search for the first free block of heap.
      osal_mem_kick();
    
  • osalInitTasks(); 任务初始化函数,

    void osalInitTasks( void )  // 初始化任务,在OSAL_SampleApp.c文件中
    {
      uint8 taskID = 0;
      // 为当前OSAL中各任务分配存储空间,tasksEvents指向任务数组
      tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
      // 给分配的空间清0
      osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
      // 任务优先级由高向低依次排列,高优先级对应的TaskID的值反而小
      macTaskInit( taskID++ ); // macTaskInit(0),初始化网络层任务 用户无需考虑
      nwk_init( taskID++ );
      Hal_Init( taskID++ );
    #if defined( MT_TASK )
      MT_TaskInit( taskID++ );
    #endif
      APS_Init( taskID++ );
    #if defined ( ZIGBEE_FRAGMENTATION )
      APSF_Init( taskID++ );
    #endif
      ZDApp_Init( taskID++ ); // ZDApp_Init(4) 用户需考虑
    #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
      ZDNwkMgr_Init( taskID++ );// 初始化网络管理任务
    #endif
      // 用户创建的任务
      GenericApp_Init( taskID );  // SampleApp_Init_Init(5),用户需考虑 。重要!!!!!
    }
    

    任务初始化,就是为系统个任务分配纯初空间,在为各任务分配TaskID;这里的孙旭要注意。系统主循环函数里 tasksEvents[idx] 和 taskArr[idx] 的 idx 与这里taskUD是一一对应关系。

    指针数组tasksEvents[] 里面最终分别指向的是各任务存储空间,指针数组tasksArr[] 里面最终分别指向的是各任务事件处理韩数,这两个数组里面的各元素孙媳要一一对应,后面任务调会调用相应的事件处理函数。

    SampleApp_Init() 是我们应用协议栈例程的必要函数,用户通常在这里初始化自己的东西。

  • 轮询查询

    在OSAL_SampleApp.c文件中,taskArr[] 事件数组。

      // The order in this table must be identical to the task initialization calls below in osalInitTask.
      const pTaskEventHandlerFn tasksArr[] = {
        macEventLoop,
        nwk_event_loop,
        Hal_ProcessEvent,
      #if defined( MT_TASK )
        MT_ProcessEvent,
      #endif
        APS_event_loop,
      #if defined ( ZIGBEE_FRAGMENTATION )
        APSF_ProcessEvent,
      #endif
        ZDApp_event_loop,
      #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
        ZDNwkMgr_event_loop,
      #endif
        SampleApp_ProcessEvent
      };
    

    Z-Stack中操作系统是基于优先级的轮转查询式操作系统,执行流程图如下:
    在这里插入图片描述

  • osal_start_system() 运行操作系统

    osal_start_system() 最终调用 osal_run_system()

    void osal_run_system( void )
    {
      uint8 idx = 0;
    
      osalTimeUpdate(); // 扫描那个事件被触发了,然后设置相应的标志位
      Hal_ProcessPoll(); // 轮询TIMER 与 UART
    
      do {
        if (tasksEvents[idx])  // Task is highest priority that is ready.
        {
          break;  				// 得到待处理的最高优先级任务索引号
        }
      } while (++idx < tasksCnt);
    
      if (idx < tasksCnt)
      {
        uint16 events;
        halIntState_t intState;
    
        HAL_ENTER_CRITICAL_SECTION(intState);  // 进入临界区,保护
        events = tasksEvents[idx];             // 提取本次需要处理的任务中的事件
        tasksEvents[idx] = 0;  // 清除本次任务的事件
        HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区
    
        activeTaskID = idx;
        events = (tasksArr[idx])( idx, events );  //通过指针调用任务处理函数,关键!!!!
        activeTaskID = TASK_NO_TASK;
    
        HAL_ENTER_CRITICAL_SECTION(intState); // 进入临界区
        tasksEvents[idx] |= events;  // 保存未处理事件 Add back unprocessed events to the current task.
        HAL_EXIT_CRITICAL_SECTION(intState); // 退出临界区
      }
    #if defined( POWER_SAVING )
      else  // Complete pass through all task events with no activity?
      {
        osal_pwrmgr_powerconserve();  // Put the processor/system into sleep
      }
    #endif
    
      /* Yield in case cooperative scheduling is being used. */
    #if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0)
      {
        osal_task_yield();
      }
    #endif
    }
    

    events = tasksEvents[idx] 进入tasksEvents[idx] 数组定义,发现恰好是osalInitTasks() 函数里面分配空间初始化过的tasksEvents。而且taskID一一对应。这就是初始化与调用的关系。taskID把任务联系起来了。

  • SampleApp_Init() 用户应用任务初始化函数

    void SampleApp_Init( uint8 task_id )
    { 
      SampleApp_TaskID = task_id;   //osal分配的任务ID随着用户添加任务的增多而改变
      SampleApp_NwkState = DEV_INIT;//设备状态设定为ZDO层中定义的初始化状态
      SampleApp_TransID = 0;        //消息发送ID(多消息时有顺序之分)
      
      // Device hardware initialization can be added here or in main() (Zmain.c).
      // If the hardware is application specific - add it here.
      // If the hardware is other parts of the device add it in main().
    
     #if defined ( BUILD_ALL_DEVICES )
      // The "Demo" target is setup to have BUILD_ALL_DEVICES and HOLD_AUTO_START
      // We are looking at a jumper (defined in SampleAppHw.c) to be jumpered
      // together - if they are - we will start up a coordinator. Otherwise,
      // the device will start as a router.
      if ( readCoordinatorJumper() )
        zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;
      else
        zgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;
    #endif // BUILD_ALL_DEVICES
    
    //该段的意思是,如果设置了HOLD_AUTO_START宏定义,将会在启动芯片的时候会暂停启动
    //流程,只有外部触发以后才会启动芯片。其实就是需要一个按钮触发它的启动流程。  
    #if defined ( HOLD_AUTO_START )
      // HOLD_AUTO_START is a compile option that will surpress ZDApp
      //  from starting the device and wait for the application to
      //  start the device.
      ZDOInitDevice(0);
    #endif
    
      // 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;//指定目的网络地址为广播地址
    
      // 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;    //延时策略
    
      // Register the endpoint description with the AF
      afRegister( &SampleApp_epDesc );    //向AF层登记描述符
    
      // Register for all key events - This app will handle all key events
      RegisterForKeys( SampleApp_TaskID ); // 登记所有的按键事件
    
      // By default, all devices start out in Group 1
      SampleApp_Group.ID = 0x0001;//组号
      osal_memcpy( SampleApp_Group.name, "Group 1", 7  );//设定组名
      aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//把该组登记添加到APS中
    
    #if defined ( LCD_SUPPORTED )
      HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 ); //如果支持LCD,显示提示信息
    #endif
    }
    
  • SampleApp_ProcessEvent() 用户应用任务的事件处理函数

    //用户应用任务的事件处理函数
    uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
    {
      afIncomingMSGPacket_t *MSGpkt;
      (void)task_id;  // Intentionally unreferenced parameter
    
      if ( events & SYS_EVENT_MSG ) //接收系统消息再进行判断
      {
        //接收属于本应用任务SampleApp的消息,以SampleApp_TaskID标记
        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://接收数据事件,调用函数AF_DataRequest()接收数据
              SampleApp_MessageMSGCB( MSGpkt );//调用回调函数对收到的数据进行处理
              break;
    
            // Received whenever the device changes state in the network
            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;
    
            default:
              break;
          }
    
          // Release the memory 事件处理完了,释放消息占用的内存
          osal_msg_deallocate( (uint8 *)MSGpkt );
    
          // Next - if one is available 指针指向下一个放在缓冲区的待处理的事件,
          //返回while ( MSGpkt )重新处理事件,直到缓冲区没有等待处理事件为止
          MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
        }
    
        // return unprocessed events 返回未处理的事件
        return (events ^ SYS_EVENT_MSG);
      }
    
      // Send a message out - This event is generated by a timer
      //  (setup in SampleApp_Init()).
      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);
      }
    
      // Discard unknown events
      return 0;
    }
    
  • 分析接收数据函数SampleApp_MessageMSGCB

    //接收数据,参数为接收到的数据
    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;
      }
    }
    
  • 分析发送周期信息 SampleApp_SendPeriodicMessage

    //分析发送周期信息 
    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.
      }
    }
    
  • AF_DataRequest 发送函数

    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
    
    整体流程

    在这里插入图片描述

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值