SOFT_START and HOLD_AUTO_START方式

如果使用HOLD_AUTO_START选项,那么本工程就会禁止自动启动ZDApp事件处理循环中的ZDO_NETWORK_INIT事件,也就是上电后不自动调用ZDOInitDevice(),需要通过外部事件,或者用户自己调用这个函数,下面我们看看定义了这个函数后,程序的流程是怎么样的。
ZDApp.c文件中,可以看到下面的定义:
#if defined( HOLD_AUTO_START )
devStates_t devState = DEV_HOLD;  // 初始化-不会自动启动
#else
  devStates_t devState = DEV_INIT;  //初始化-没有连接到任何东西
#endif
 
#if defined( ZDO_COORDINATOR ) && !defined( SOFT_START )
  // Set the default to coodinator
  devStartModes_t devStartMode = MODE_HARD;
#else
  devStartModes_t devStartMode = MODE_JOIN;     // Assume joining
  //devStartModes_t devStartMode = MODE_RESUME; // if already "directly joined"
                        // to parent. Set to make the device do an Orphan scan.
#endif
 
#if !defined( ZDO_COORDINATOR ) || defined( SOFT_START )
  static uint8 retryCnt;
#endif
    

启动过程:

1、int main( void )中的 osal_init_system();

2、 osal_init_system();中的 osalInitTasks();

3、osalInitTasks();中的 ZDApp_Init( taskID++ );和SerialApp_Init( taskID );

4、void ZDApp_Init( uint8 task_id )--- ZDOInitDevice( uint16 startDelay )----ZDApp_NetworkInit( uint16 delay ) 

5、UINT16 ZDApp_event_loop( uint8 task_id, UINT16 events )中的

 ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,
                     DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );



在调用用户自己定义的任务初始化函数之前,调用下面的初始函数,看看这里怎么处理, devState 状态的。
void ZDApp_Init( byte task_id )
{
  uint8 capabilities;
 
  // Save the task ID
  ZDAppTaskID = task_id;
 
  // Initialize the ZDO global device short address storage
  ZDAppNwkAddr.addrMode = Addr16Bit;
  ZDAppNwkAddr.addr.shortAddr = INVALID_NODE_ADDR;
  (void)NLME_GetExtAddr();  // Load the saveExtAddr pointer. 加载IEEE地址
 
  // Check for manual "Hold Auto Start"
 //打开电源时,检测到有手工设置SW_1则会设置devState = DEV_HOLD,从而不进行网络初始化
  ZDAppCheckForHoldKey();
 
  // Initialize ZDO items and setup the device - type of device to create.
  ZDO_Init(); //初始化ZDO条目,并设置设备的启动方式是协调器,还是别的
 
  // Register the endpoint description with the AF
  // This task doesn't have a Simple description, but we still need
  // to register the endpoint.
  afRegister( (endPointDesc_t *)&ZDApp_epDesc );
 
#if defined( ZDO_USERDESC_RESPONSE )
  ZDApp_InitUserDesc();
#endif // ZDO_USERDESC_RESPONSE
 
  // set broadcast address mask to support broadcast filtering
  NLME_GetRequest(nwkCapabilityInfo, 0, &capabilities);
  NLME_SetBroadcastFilter( capabilities );
 
  // Start the device? 是否启动设备?如果devState不是DEV_HOLD时,则启动设备,在上面的代码分析中,也可以看到,如果定义了HOLD_AUTO_START宏,则devState等于DEV_HOLD,不会启动设备。如果按下了SW_1devState也等于DEV_HOLD,也不会启动网络。也就是说有两种方式可以设置非自动启动模式,一种是通过按键,一种通过宏定义
  if ( devState != DEV_HOLD )
  {
    ZDOInitDevice( 0 );
  }
  else
  {
//如果定义了HOLD_AUTO_START,则等待延时或外部事件启动网络,并且LED4灯,也就是蓝色的灯闪烁
    // Blink LED to indicate HOLD_START
    HalLedBlink ( HAL_LED_4, 0, 50, 500 );
  }
 
  ZDApp_RegisterCBs();
}
 
void ZDAppCheckForHoldKey( void )
{
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
//通过判断按键来决定是否采用HOLD_AUTO_START方式。当按下SW_BYPASS_START按键,也就是SW1键,将避开自动启动设备,也就是设置 devState = DEV_HOLD
  // Get Keypad directly to see if a HOLD_START is needed.
  // Hold down the SW_BYPASS_START key (see OnBoard.h)
  // while booting to avoid starting up the device.
  if ( HalKeyRead () == SW_BYPASS_START)
  {
    // Change the device state to HOLD on start up
    devState = DEV_HOLD;
  }
#endif // HAL_KEY
}
说明:(1)这里HAL_KEY的初始化在hal_board_cfg.h文件中:
 #ifndef HAL_KEY
 #define HAL_KEY TRUE
 #endif
而对SW_BYPASS_START的初始化在OnBoard.h文件中:
// These Key definitions are unique to this development system.
// They are used to bypass functions when starting up the device.
//
这些键的定义仅适用于本应用例子,可以在设备启动时避开一些功能:
// 避开网络层的NV存储和避开网络初始化
#define SW_BYPASS_NV    HAL_KEY_SW_5     // Bypass Network layer NV restore
#define SW_BYPASS_START HAL_KEY_SW_1  // Bypass Network initialization
因此避开网络层NV存储也可以通过手工方式来完成.
//根据编译选项来设置;比如SimpleApp中的灯节点,预编译了ZDO_COORDINATORREFLECTORSOFT_START,因此会根据这些来选择开启一些函数功能.
void ZDO_Init( void )
{
  // Initialize ZD items REFLECTOR如果定义了这个编译选项则使用源绑定
  #if defined ( REFLECTOR )
  ZDO_EDBind = NULL;
  #endif
 
  // Setup the device - type of device to create.
  ZDODeviceSetup();
}
 
static void ZDODeviceSetup( void )
{
#if defined( ZDO_COORDINATOR ) //如果定义了协调器,协调器初始化
  NLME_CoordinatorInit();
#endif
 
#if defined ( REFLECTOR ) //如果定义了COORDINATOR_BINDING 绑定时使用
  #if defined ( ZDO_COORDINATOR )//定义了REFLECTOR,且定义了协调器
    APS_ReflectorInit( APS_REFLECTOR_PUBLIC );
  #else //编译了REFLECTOR且编译了路由器或终端
    APS_ReflectorInit( APS_REFLECTOR_PRIVATE );
  #endif
#endif
 
#if !defined( ZDO_COORDINATOR ) || defined( SOFT_START )//如果没有定义协调器ZDO_COORDINATOR )则还定义了SOFT_START则进行连接初始化
  NLME_DeviceJoiningInit();
#endif
}
 
uint8 ZDOInitDevice( uint16 startDelay )
{
  //初始化设备网络状态为ZDO_INITDEV_NEW_NETWORK_STATE:新的网络状态.可能意味着ZCD_NV_STARTUP_OPTION不能恢复,或没有任何网络状态恢复
  uint8 networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE;
  uint16 extendedDelay = 0;
 
  devState = DEV_INIT;    // Remove the Hold state
 
  // Initialize leave control logic
//函数读取NV项目ZCD_NV_LEAVE_CTRL的值,ZDApp_LeaveCtrl指向这个值
  ZDApp_LeaveCtrlInit();
 
  // Check leave control reset settings
//设备的断开会造成DEV_HOLD状态,这里面设置的.
  ZDApp_LeaveCtrlStartup( &devState, &startDelay );
 
  // Leave may make the hold state come back
//以上两个函数设置了对设备离开时的控制,如果有延时则延时,没有则
//把设备状态设为DEV_HOLD
 //ZDO_INITDEV_LEAVE_NOT_STARTED:该设备没有在网络中,下次调用才启用.
  if ( devState == DEV_HOLD )
    return ( ZDO_INITDEV_LEAVE_NOT_STARTED );   // Don't join - (one time).
 
#if defined ( NV_RESTORE )
  // Get Keypad directly to see if a reset nv is needed.
  // Hold down the SW_BYPASS_NV key (defined in OnBoard.h)
  // while booting to skip past NV Restore.
  if ( HalKeyRead() == SW_BYPASS_NV )
       //SW_BYPASS_NV按键处于按下状态时,则避开网络层的NV存储
    networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE; //设备网络状态为新的网络状态
  else
  {
// Determine if NV should be restored
 //函数返回的设备网络状态要么是新的网络状态;要么是恢复的网络状态;以此
    //来确定要不要读取NV里相应条目来恢复网络先前状态
    networkStateNV = ZDApp_ReadNetworkRestoreState();
  }
    //如果设备的网络状态为恢复的网络状态
  if ( networkStateNV == ZDO_INITDEV_RESTORED_NETWORK_STATE )
  {
    networkStateNV = ZDApp_RestoreNetworkState();
  }
  else
  {
// Wipe out the network state in NV
 //恢复设备先前的网络状态参数
    //设置devStartMode = MODE_RESUME
    NLME_InitNV();
    NLME_SetDefaultNV();
  }
#endif
 //如果设备的网络状态为新的网络状态,
  if ( networkStateNV == ZDO_INITDEV_NEW_NETWORK_STATE )
  {
//根据预编译来设置设备新的网络状态参数
    ZDAppDetermineDeviceType();
 
    // Only delay if joining network - not restoring network state
    extendedDelay = (uint16)((NWK_START_DELAY + startDelay)
              + (osal_rand() & EXTENDED_JOINING_RANDOM_MASK));
  }
 
  // Initialize device security
  ZDApp_SecInit( networkStateNV );
 
  // Trigger the network start
  ZDApp_NetworkInit( extendedDelay );
 
  return ( networkStateNV );
}
       ZigBee设备的启动,最终是要调用ZDO_StartDevice()函数来实现的。下面看一下是怎么启动这个函数的。在ZDOInitDevice()函数的最后,调用了下面的ZDApp_NetworkInit()函数,在这个函数中,启动了ZDO_NETWORK_INIT事件,这个事件是在ZDApp_event_loop()事件处理函数中进行处理的。在这个事件中调用了启动设备的函数ZDO_StartDevice(),这函数在前面的文章中也已经分析过了。
void ZDApp_NetworkInit( uint16 delay )
{
  if ( delay )
  {
    // Wait awhile before starting the device
    osal_start_timerEx( ZDAppTaskID, ZDO_NETWORK_INIT, delay );
  }
  else
  {
    osal_set_event( ZDAppTaskID, ZDO_NETWORK_INIT );
  }
}
UINT16 ZDApp_event_loop( byte task_id, UINT16 events )
{
................
if ( events & ZDO_NETWORK_INIT )
  {
    // Initialize apps and start the network
    devState = DEV_INIT;
    ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,
                     DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );
 
    // Return unprocessed events
    return (events ^ ZDO_NETWORK_INIT);
  }
.....................
}
 
     下面以SimpleSwitchEB为例子看看当定义了HOLD_AUTO_START选项后,程序的流程是怎么样的。在void SAPI_Init( byte task_id )函数的最后,有下面一句话,
osal_set_event(task_id, ZB_ENTRY_EVENT);下图是编译选项的设置:
Z-Stack中的HOLD_AUTO_START编译选项分析
这将触发ZB_ENTRY_EVENT事件,这个事件的处理在,
UINT16 SAPI_ProcessEvent( byte task_id, UINT16 events )
{
.................................
if ( events & ZB_ENTRY_EVENT )
  {
    uint8 startOptions;
 
// Give indication to application of device startup
//这个函数不处理ZB_ENTRY_EVENT事件
    zb_HandleOsalEvent( ZB_ENTRY_EVENT );
 
// LED off cancels HOLD_AUTO_START blink set in the stack
//关闭协议栈中LED4的闪烁,LED4灯闪烁表明没有正常启动设备或者没有加入网络 关闭栈中的HOLD指示
    HalLedSet (HAL_LED_4, HAL_LED_MODE_OFF);
 
    zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
    if ( startOptions & ZCD_STARTOPT_AUTO_START )
    {
      zb_StartRequest();
    }
    else
{
  //首次使用时,闪烁LED2,指示外部输入,等待启动设备
      // blink leds and wait for external input to config and restart
      HalLedBlink(HAL_LED_2, 0, 50, 500);
    }
 
    return (events ^ ZB_ENTRY_EVENT);
  }
..............................
}
在按键处理函数中,可以看到
void zb_HandleKeys( uint8 shift, uint8 keys )
{
  uint8 startOptions;
  uint8 logicalType;
 
  // Shift is used to make each button/switch dual purpose.
  if ( shift )
  {
    if ( keys & HAL_KEY_SW_1 )
    {
    }
    if ( keys & HAL_KEY_SW_2 )
    {
    }
    if ( keys & HAL_KEY_SW_3 )
    {
    }
    if ( keys & HAL_KEY_SW_4 )
    {
    }
  }
  else
  {
    if ( keys & HAL_KEY_SW_1 )
    {
      if ( myAppState == APP_INIT )
      {
        // In the init state, keys are used to indicate the logical mode.
        // The Switch device is always an end-device
        logicalType = ZG_DEVICETYPE_ENDDEVICE;
        zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType);
 
        // Do more configuration if necessary and then restart device with auto-start bit set
 
        zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
        startOptions = ZCD_STARTOPT_AUTO_START;//下次启动时,自动启动
        zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
        zb_SystemReset();//这里导致设备重启,重启后,产生ZB_ENTRY_EVENT事件,启动网络设备
 
      }
      else
      {
        // Initiate a binding with null destination
        zb_BindDevice(TRUE, TOGGLE_LIGHT_CMD_ID, NULL);
      }
    }
    if ( keys & HAL_KEY_SW_2 )
    {
      if ( myAppState == APP_INIT )
      {
        // In the init state, keys are used to indicate the logical mode.
        // The Switch device is always an end-device
        logicalType = ZG_DEVICETYPE_ENDDEVICE;
        zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType);
 
 
        zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
        startOptions = ZCD_STARTOPT_AUTO_START;
        zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
        zb_SystemReset();
      }
      else
      {
        // Send the command to toggle light
        zb_SendDataRequest( 0xFFFE, TOGGLE_LIGHT_CMD_ID, 0,
                        (uint8 *)NULL, myAppSeqNumber, 0, 0 );
      }
    }
    if ( keys & HAL_KEY_SW_3 )
    {
      // Remove all existing bindings
      zb_BindDevice(FALSE, TOGGLE_LIGHT_CMD_ID, NULL);
    }
    if ( keys & HAL_KEY_SW_4 )
    {
    }
  }
}
    这样SimpleSwitchEB()就作为了非自动启动设备进行了启动了,也就是说必须在定义了HOLD_AUTO_START宏以后,当按键按下后,就会重新启动网络设备。

程序的运行基本流程在上一个学习例子中已经讲过,这节主要讨论一下网络启动的几种方式,

先介绍一些基本概念,然后再来看程序:

 sample application compiled as a Coordinator will form a network on one of the channels specified by the DEFAULT_CHANLIST. The Coordinator will establish a random Pan ID based on its own IEEE address or on ZDAPP_CONFIG_PAN_ID if it is not defined as  0xFFFF. A sample application compiled as a Router or End Device will try to join a network on one of the channels specified by  DEFAULT_CHANLIST. If ZDAPP_CONFIG_PAN_ID is not defined as 0xFFFF, the Router will be constrained to join only the Pan ID thusly defined. Note the unexpected result achieved because of the difference in behavior between a Coordinator and a Router or End Device when  ZDAPP_CONFIG_PAN_ID is not defined as  0xFFFF. If ZDAPP_CONFIG_PAN_ID is defined as a valid value less than or equal to 0x3FFF, then the Coordinator will only attempt to establish a network with the specified Pan Id. Thus, if the Coordinator is constrained to one channel, and the specified Pan Id has already been established on that channel, the newly starting Coordinator will make
successive changes until it achieves a unique Pan Id. A Router or End Device newly joining will have no way of knowing the value of the “de-conflicted” Pan Id established, and will therefore join only the Pan Id specified. A similarly challenging scenario arises when the permitted channel mask allows more than one channel and the Coordinator cannot use the first channel because of a Pan Id conflict – a Router or End Device will join the specified Pan Id on the first channel scanned, if allowed.

协调器在启动后将会在DEFAULT_CHANLIST制定的信道上形成一个网络。协调器将会根据它自己的物理地址生成一个随机的PAN ID或者在ZDAPP_CONFIG_PAN_ID不为oxffff的时候生成ZDAPP_CONFIG_PAN_ID指定的PAN ID。路由器和中断将会在启动后尝试加入信道为DEFAULT_CHANLIST的网络。如果ZDAPP_CONFIG_PAN_ID 不为oxffff,路由器将会加入指定的PAN ID。注意当ZDAPP_CONFIG_PAN_ID不是制定为0xffff时对于协调器,路由器,终端将会出现一些意想不到的一些结果。因为如果之前有一个协调器在制定的信道和PAN ID上已经建立了一个网络,而新的协调器在建立一个网络的时候由于不能重新使用之前的PAN ID ,它将重新选择一个PAN ID建立网络,但是路由器,终端并不知道PAN ID 已经改变,仍会加入指定的PAN ID 中。同样的情况也会出现在当允许的信道标志位允许多个信道存在时候,这个时候新形成的协调器由于之前的PAN ID 冲突会重新在一个信道上形成指定PAN ID的网络,而路由器和终端不知道已经改变,所以会出现加入网路错误。

Auto Start
A device will automatically start trying to form or join a network as part of the BSP power-up sequence. If the device should wait on a timer or other external event before joining, then HOLD_AUTO_START must be defined. In order to manually start the join process at a later time, invoke ZDApp_StartUpFromApp().
 自动开始

作为板级支持包上电顺序的一部分,设备将会自动开始形成或加入网络,如果设备在加入之前必须等待一段时间或是等待外部事件,HOLD_AUTO_START这个必须被定义。为了之后人工开始加入过程,应该调用ZDApp_StartUpFromApp()函数。

*********************************

注:ZDApp_StartUpFromApp()原型:

* @fn      ZDApp_StartUpFromApp()
 *
 * @brief   Start the device.  This function will only start a device
          that is in the Holding state.//这个函数只用于启动处于保持状态的设备。
 *
 * @param   mode - ZDAPP_STARTUP_COORD - Start up as coordinator only
                 ZDAPP_STARTUP_ROUTER - Start up as router only
                 ZDAPP_STARTUP_AUTO - Startup in auto, look for coord,
                                       if not found, become coord.//三种不同的启动模式。
 *
 * @return  TRUE if started, FALSE if in the wrong mode
 */
ZStatus_t ZDApp_StartUpFromApp( uint8 mode )
{
  ZStatus_t ret = ZFailure;

  if ( devState == DEV_HOLD )
  {
    // Start the device's joining process
    if ( ZDOInitDevice( 0 ) == ZDO_INITDEV_NEW_NETWORK_STATE )
    {
#if defined( SOFT_START )
      if ( mode == ZDAPP_STARTUP_COORD )
      {
        devStartMode = MODE_HARD;     // Start as a coordinator
        ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_COORDINATOR;
      }
      else
      {
        if ( mode == ZDAPP_STARTUP_ROUTER )
        {
          softStartAllowCoord = FALSE;  // Don't allow coord to start
          continueJoining = TRUE;
        }
        devStartMode = MODE_JOIN;     // Assume joining
      }
#endif  // SOFT_START
    }
    ret = ZSuccess;
  }

  return ( ret );
}

*************************************************************
1.3.2 Soft Start
To save on device types required to form the network, all router devices can be compiled as a Coordinator with SOFT_START defined. If the auto start behavior is required, then AUTO_SOFT_START must also be defined.

软启动

为了保存形成网络需要的设备类型,所有的路由设备可以定义SOFT_START 被编译为一个协调器。如果需要自启动,那么必须定义AUTO_SOFT_START。

 
1.3.3 Network Restore
Devices that have successfully joined a network can “restore the network” (instead of reforming by OTA messages) even after losing power or battery. This automatic restoration can be enabled by defining NV_RESTORE and/or NV_INIT.

网络恢复

成功加入网络的设备即使在没有掉电之后还可以恢复网络(不需要通过OTA信息来重新形成网络),自动恢复的功能需要定义 NV_RESTORE and/or NV_INIT来使能。
1.3.4 Join Notification
The device is notified of the status of the network formation or join (or any change in network state) with the ZDO_STATE_CHANGE message mentioned above.

加入告知
网络形成或加入的状态信息通过ZDO_STATE_CHANGE消息来通知设备。

 

下面来看具体的程序:

#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

HOLD_AUTO_START如果定义了将会使应用来启动设备;

ZDOInitDevice(0)的具体函数如下:(不要觉得头晕,想搞懂,没办法,是吧,恩恩,加油)

 Application Functions
 */

 

uint8 ZDOInitDevice( uint16 startDelay )
{
  uint8 networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE;
  uint16 extendedDelay = 0;

  devState = DEV_INIT;    // Remove the Hold state

********************

注:为什么要清楚之前的状态:

#if defined( HOLD_AUTO_START )
  devStates_t devState = DEV_HOLD;
#else
  devStates_t devState = DEV_INIT;
#endif

********************

  // Initialize leave control logic
  ZDApp_LeaveCtrlInit();//

  // Check leave control reset settings
  ZDApp_LeaveCtrlStartup( &devState, &startDelay );//

  // Leave may make the hold state come back
  if ( devState == DEV_HOLD )
    return ( ZDO_INITDEV_LEAVE_NOT_STARTED );   // Don't join - (one time).

#if defined ( NV_RESTORE ) 
  // Get Keypad directly to see if a reset nv is needed.
  // Hold down the SW_BYPASS_NV key (defined in OnBoard.h)
  // while booting to skip past NV Restore.
  if ( HalKeyRead() == SW_BYPASS_NV )
    networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE;
  else
  {
    // Determine if NV should be restored
    networkStateNV = ZDApp_ReadNetworkRestoreState();//
  }

  if ( networkStateNV == ZDO_INITDEV_RESTORED_NETWORK_STATE )
  {
    networkStateNV = ZDApp_RestoreNetworkState();
  }
  else
  {
    // Wipe out the network state in NV
    NLME_InitNV();//
    NLME_SetDefaultNV();//
    ZDAppSetupProtoVersion();//
  }
#endif

  if ( networkStateNV == ZDO_INITDEV_NEW_NETWORK_STATE )
  {
    ZDAppDetermineDeviceType();//

    // Only delay if joining network - not restoring network state
    extendedDelay = (uint16)((NWK_START_DELAY + startDelay)
              + (osal_rand() & EXTENDED_JOINING_RANDOM_MASK));
  }

  // Trigger the network start
  ZDApp_NetworkInit( extendedDelay );//

  return ( networkStateNV );
}

 

  // 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

设置flash命令的目标地址
  SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup;
  SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT;
  SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP;//地址为组

            ......

            ......

  // By default, all devices start out in Group 1 默认情况下所有设备开始时为第一组 
  SampleApp_Group.ID = 0x0001;//组ID,为啥要设置组ID
  osal_memcpy( SampleApp_Group.name, "Group 1", 7  );
  aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );//加入组。

****************

注:

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


aps_Group_t SampleApp_Group;

 

aps_AddGroup();这个函数好像不开源。
*********************************

 上面只是我在看程序过程中遇到的一些细节性问题,下面

把这个程序的整体流程说一下,好理清思路:

 

现在假设整个网络初始化成功。

当网络状态改变后,每个设备将调用osal_start_timerEx( SampleApp_TaskID,
                              SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
                              SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );

这个函数说明将在SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT后发送SAMPLEAPP_SEND_PERIODIC_MSG_EVT消息,该消息发送到系统后,系统处理SAMPLEAPP_SEND_PERIODIC_MSG_EVT事件:

if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
  {
    // Send the periodic message
    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()的原型如下:

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.
  }
}

当设备接收到其他设备发送的消息的时候,将会触发case AF_INCOMING_MSG_CMD:
          SampleApp_MessageMSGCB( MSGpkt );
          break;

SampleApp_MessageMSGCB()原型如下:

void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
  uint16 flashTime;

  switch ( pkt->clusterId )//选择命令,是周期性发送消息命令,还是SAMPLEAPP_FLASH_CLUSTERID命令,命令选择了执行不同的动作。
  {
    case SAMPLEAPP_PERIODIC_CLUSTERID:
      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; 
  }
}

同时程序中还注册了按键事件:

按下按键sw1时有:

 if ( keys & HAL_KEY_SW_1 )
  {
   
    SampleApp_SendFlashMessage( SAMPLEAPP_FLASH_DURATION );//这个发送的目标是初始化中的组地址,当然组中成员可由按下sw2来决定是否取消或进入该组。
  }

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.
  }
}

按下sw2时,决定这个设备是加入还是退出组1。

 if ( keys & HAL_KEY_SW_2 )
  {
   
    aps_Group_t *grp;
    grp = aps_FindGroup( SAMPLEAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );
    if ( grp )
    {
      // Remove from the group
      aps_RemoveGroup( SAMPLEAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );
    }
    else
    {
      // Add to the flash group
      aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );
    }
  }
}

 







 

 在ZDApp.c文件中,可以看到下面的定义:  ...看源代码。。

#if defined( HOLD_AUTO_START ) 
devStates_t   devState = DEV_HOLD; // 初始化-不会自动启动

 #else 。。。。。。。。。

在调用用户自己定义的任务初始化函数之前,调用下面的初始函数,看看这里怎么处理,devState状态的。

void ZDApp_Init( byte task_id ) {。)


// Initialize ZDO items and setup the device - type of device to create. 
ZDO_Init(); //初始化ZDO条目,并设置设备的启动方式是协调器,还是别的 

// Register the endpoint description with the AF 
// This task doesn't have a Simple description, but we still need // to register the endpoint. 

afRegister( (endPointDesc_t *)&ZDApp_epDesc ); #if defined( ZDO_USERDESC_RESPONSE ) ZDApp_InitUserDesc(); 
#endif // ZDO_USERDESC_RESPONSE 
// set broadcast address mask to support broadcast filtering NLME_GetRequest(nwkCapabilityInfo, 0, &capabilities); NLME_SetBroadcastFilter( capabilities ); 
// Start the device? 是否启动设备?如果devState不是DEV_HOLD时,则启动设备,在上面的代码分析中,也可以看到,如果定义了HOLD_AUTO_START宏,则devState等于DEV_HOLD,不会启动设备。如果按下了SW_1键devState也等于DEV_HOLD,也不会启动网络。也就是说有两种方式可以设置非自动启动模式,一种是通过按键,一种通过宏定义 if ( devState != DEV_HOLD ) { 
ZDOInitDevice( 0 ); } else { 
//如果定义了HOLD_AUTO_START,则等待延时或外部事件启动网络,并且LED4灯,也就是蓝色的灯闪烁 
// Blink LED to indicate HOLD_START HalLedBlink ( HAL_LED_4, 0, 50, 500 ); } 
ZDApp_RegisterCBs(); } /* ZDO_Init() */ 
void ZDAppCheckForHoldKey( void ) { 
#if (defined HAL_KEY) && (HAL_KEY == TRUE) 
//通过判断按键来决定是否采用HOLD_AUTO_START方式。当按下
SW_BYPASS_START按键,也就是SW1键,将避开自动启动设备,也就是设置 devState = DEV_HOLD 
// Get Keypad directly to see if a HOLD_START is needed. // Hold down the SW_BYPASS_START key (see OnBoard.h) // while booting to avoid starting up the device. if ( HalKeyRead () == SW_BYPASS_START) { 
// Change the device state to HOLD on start up devState = DEV_HOLD; } 
#endif // HAL_KEY } 
说明:(1)这里HAL_KEY的初始化在hal_board_cfg.h文件中: #ifndef HAL_KEY 
#define HAL_KEY TRUE

#endif 
而对SW_BYPASS_START的初始化在OnBoard.h文件中: // These Key definitions are unique to this development system. // They are used to bypass functions when starting up the device. 
//这些键的定义仅适用于本应用例子,可以在设备启动时避开一些功能: //避开网络层的NV存储和避开网络初始化 
#define SW_BYPASS_NV HAL_KEY_SW_5 // Bypass Network layer NV restore #define SW_BYPASS_START HAL_KEY_SW_1 // Bypass Network initialization 因此避开网络层NV存储也可以通过手工方式来完成. 
//根据编译选项来设置;比如SimpleApp中的灯节点,预编译了ZDO_COORDINATOR和REFLECTOR和SOFT_START,因此会根据这些来选择开启一些函数功能. void ZDO_Init( void ) { 
// Initialize ZD items REFLECTOR如果定义了这个编译选项则使用“源绑定”, #if defined ( REFLECTOR ) ZDO_EDBind = NULL; #endif 
// Setup the device - type of device to create. ZDODeviceSetup(); } 
static void ZDODeviceSetup( void ) { 
#if defined( ZDO_COORDINATOR ) //如果定义了协调器,协调器初始化 NLME_CoordinatorInit(); #endif 
#if defined ( REFLECTOR ) //如果定义了COORDINATOR_BINDING 绑定时使用 
#if defined ( ZDO_COORDINATOR )//定义了REFLECTOR,且定义了协调器 APS_ReflectorInit( APS_REFLECTOR_PUBLIC ); #else //编译了REFLECTOR且编译了路由器或终端 APS_ReflectorInit( APS_REFLECTOR_PRIVATE ); #endif #endif 
#if !defined( ZDO_COORDINATOR ) || defined( SOFT_START )//如果没有定义协调器ZDO_COORDINATOR ),则还定义了SOFT_START则进行连接初始化 NLME_DeviceJoiningInit(); #endif } 
uint8 ZDOInitDevice( uint16 startDelay ) { 
//初始化设备网络状态为ZDO_INITDEV_NEW_NETWORK_STATE:新的网络状态.可能意味着ZCD_NV_STARTUP_OPTION不能恢复,或没有任何网络状态恢复 uint8 networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE; uint16 extendedDelay = 0;

devState = DEV_INIT; // Remove the Hold state // Initialize leave control logic 
//函数读取NV项目ZCD_NV_LEAVE_CTRL的值,ZDApp_LeaveCtrl指向这个值 ZDApp_LeaveCtrlInit(); 
// Check leave control reset settings 
//设备的断开会造成DEV_HOLD状态,这里面设置的. ZDApp_LeaveCtrlStartup( &devState, &startDelay ); // Leave may make the hold state come back 
//以上两个函数设置了对设备离开时的控制,如果有延时则延时,没有则 //把设备状态设为DEV_HOLD 
//ZDO_INITDEV_LEAVE_NOT_STARTED:该设备没有在网络中,下次调用才启用. if ( devState == DEV_HOLD ) 
return ( ZDO_INITDEV_LEAVE_NOT_STARTED ); // Don't join - (one time). #if defined ( NV_RESTORE ) 
// Get Keypad directly to see if a reset nv is needed. 
// Hold down the SW_BYPASS_NV key (defined in OnBoard.h) // while booting to skip past NV Restore. if ( HalKeyRead() == SW_BYPASS_NV ) 
//SW_BYPASS_NV按键处于按下状态时,则避开网络层的NV存储 
networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE; //设备网络状态为新的网络状态 else { 
// Determine if NV should be restored 
//函数返回的设备网络状态要么是新的网络状态;要么是恢复的网络状态;以此 //来确定要不要读取NV里相应条目来恢复网络先前状态 networkStateNV = ZDApp_ReadNetworkRestoreState(); } 
//如果设备的网络状态为恢复的网络状态 
if ( networkStateNV == ZDO_INITDEV_RESTORED_NETWORK_STATE ) { 
networkStateNV = ZDApp_RestoreNetworkState(); } else { 
// Wipe out the network state in NV //恢复设备先前的网络状态参数 
//设置devStartMode = MODE_RESUME NLME_InitNV(); 
NLME_SetDefaultNV(); } 
#endif 
//如果设备的网络状态为新的网络状态, 
if ( networkStateNV == ZDO_INITDEV_NEW_NETWORK_STATE ) 


//根据预编译来设置设备新的网络状态参数 ZDAppDetermineDeviceType(); 
// Only delay if joining network - not restoring network state 
extendedDelay = (uint16)((NWK_START_DELAY + startDelay) + (osal_rand() & EXTENDED_JOINING_RANDOM_MASK)); } 
// Initialize device security 
ZDApp_SecInit( networkStateNV ); // Trigger the network start 
ZDApp_NetworkInit( extendedDelay ); return ( networkStateNV ); } 
ZigBee设备的启动,最终是要调用ZDO_StartDevice()函数来实现的。下面看一下是怎么启动这个函数的。在ZDOInitDevice()函数的最后,调用了下面的ZDApp_NetworkInit()函数,在这个函数中,启动了ZDO_NETWORK_INIT事件,这个事件是在ZDApp_event_loop()事件处理函数中进行处理的。在这个事件中调用了启动设备的函数ZDO_StartDevice(),这函数在前面的文章中也已经分析过了。 
void ZDApp_NetworkInit( uint16 delay ) { 
if ( delay ) { 
// Wait awhile before starting the device 
osal_start_timerEx( ZDAppTaskID, ZDO_NETWORK_INIT, delay ); } else { 
osal_set_event( ZDAppTaskID, ZDO_NETWORK_INIT ); } } 
UINT16 ZDApp_event_loop( byte task_id, UINT16 events ) { 
................ if ( events & ZDO_NETWORK_INIT ) { 
// Initialize apps and start the network devState = DEV_INIT; 
ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode, 
DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER ); // Return unprocessed events 
return (events ^ ZDO_NETWORK_INIT); }

..................... } 
下面以SimpleSwitchEB为例子看看当定义了HOLD_AUTO_START选项后,程序的流程是怎么样的。在void SAPI_Init( byte task_id )函数的最后,有下面一句话, 
osal_set_event(task_id, ZB_ENTRY_EVENT);下图是编译选项的设置: 


 
这将触发ZB_ENTRY_EVENT事件,这个事件的处理在, UINT16 SAPI_ProcessEvent( byte task_id, UINT16 events ) { 
................................. if ( events & ZB_ENTRY_EVENT ) { 
uint8 startOptions; 
// Give indication to application of device startup //这个函数不处理ZB_ENTRY_EVENT事件 zb_HandleOsalEvent( ZB_ENTRY_EVENT ); 
// LED off cancels HOLD_AUTO_START blink set in the stack 
//关闭协议栈中LED4的闪烁,LED4灯闪烁表明没有正常启动设备或者没有加入网络关闭栈中的HOLD指示 
HalLedSet (HAL_LED_4, HAL_LED_MODE_OFF); 
zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions ); 
if ( startOptions & ZCD_STARTOPT_AUTO_START )


zb_StartRequest(); } else { 
//首次使用时,闪烁LED2,指示外部输入,等待启动设备 // blink leds and wait for external input to config and restart HalLedBlink(HAL_LED_2, 0, 50, 500); } 
return (events ^ ZB_ENTRY_EVENT); }  
.............................. } 
在按键处理函数中,可以看到 
void zb_HandleKeys( uint8 shift, uint8 keys ) { 
uint8 startOptions; uint8 logicalType; 
// Shift is used to make each button/switch dual purpose. if ( shift ) { 
if ( keys & HAL_KEY_SW_1 ) { } 
if ( keys & HAL_KEY_SW_2 ) { } 
if ( keys & HAL_KEY_SW_3 ) { } 
if ( keys & HAL_KEY_SW_4 ) { } } else { 
if ( keys & HAL_KEY_SW_1 ) { 
if ( myAppState == APP_INIT ) { 
// In the init state, keys are used to indicate the logical mode. // The Switch device is always an end-device 
logicalType = ZG_DEVICETYPE_ENDDEVICE; 
zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType);


// Do more configuration if necessary and then restart device with auto-start bit set zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions ); 
startOptions = ZCD_STARTOPT_AUTO_START;//下次启动时,自动启动 zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions ); 
zb_SystemReset();//这里导致设备重启,重启后,产生ZB_ENTRY_EVENT事件,启动网络设备 } else { 
// Initiate a binding with null destination 
zb_BindDevice(TRUE, TOGGLE_LIGHT_CMD_ID, NULL); } } 
if ( keys & HAL_KEY_SW_2 ) { 
if ( myAppState == APP_INIT ) { 
// In the init state, keys are used to indicate the logical mode. // The Switch device is always an end-device 
logicalType = ZG_DEVICETYPE_ENDDEVICE; 
zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType); zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions ); 
startOptions = ZCD_STARTOPT_AUTO_START; 
zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions ); zb_SystemReset(); } else { 
// Send the command to toggle light 
zb_SendDataRequest( 0xFFFE, TOGGLE_LIGHT_CMD_ID, 0, (uint8 *)NULL, myAppSeqNumber, 0, 0 ); } } 
if ( keys & HAL_KEY_SW_3 ) { 
// Remove all existing bindings 
zb_BindDevice(FALSE, TOGGLE_LIGHT_CMD_ID, NULL); } 
if ( keys & HAL_KEY_SW_4 ) {

} } } 
这样SimpleSwitchEB()就作为了非自动启动设备进行了启动了,也就是说必须在定义了HOLD_AUTO_START宏以后,当按键按下后,就会重新启动网络设备。 




 

使用的协议栈版本信息: ZigBee2006\ZStack-1.4.3-1.2.1
Zigbee网络设备启动流程—协调器(非自启动模式HOLD_AUTO_START)
—以SimpleApp的灯节点SimpleControllerEB为例.

灯节点按K1则作为协调器启动,按K2则作为路由器启动.这里以按K1作为协调器启动为例.

1、灯节点预编译信息
通过project->options->c/c++compiler->extraOptions可以看到灯节点所带的配置文件为:
-f $PROJ_DIR$\..\..\..\Tools\CC2430DB\f8wCoord.cfg
-f $PROJ_DIR$\..\..\..\Tools\CC2430DB\f8wConfig.cfg

即编译了ZDO_COORDINATOR和RTR_NWK.

通过project->options->c/c++compiler->Preprocessor->Defined symbols可以看到灯节点预编译包含了:
CC2430EB; HOLD_AUTO_START; SOFT_START; REFLECTOR; NV_INIT; LCD_SUPPORTED
xNV_RESTORE; xZTOOL_P1; xMT_TASK; xMT_SAPI_FUNC; xMT_SAPI_CB_FUNC

可以看到编译了HOLD_AUTO_START和SOFT_START.
因而初始化:
devState = DEV_HOLD(见基本问题说明3.编译了HOLD_AUTO_START)
devStartMode = MODE_JOIN(见基本问题说明4.编译了SOFT_START)
zgDeviceLogicalType = DEVICE_LOGICAL_TYPE=ZG_DEVICETYPE_SOFT(见基本问题说明5.编译了SOFT_START)
ZDO_Config_Node_Descriptor.LogicalType=NODETYPE_ROUTER(见基本问题说明6.编译了SOFT_START和RTR_NWK)

2、具体流程
main()->osal_init_system()->osalInitTasks()->ZDApp_Init()
进入ZDApp_Init()函数:
**************************************
void ZDApp_Init( byte task_id )
{
uint8 capabilities;

// Save the task ID
ZDAppTaskID = task_id;

// Initialize the ZDO global device short address storage
ZDAppNwkAddr.addrMode = Addr16Bit;
ZDAppNwkAddr.addr.shortAddr = INVALID_NODE_ADDR;//0xFFFE
(void)NLME_GetExtAddr(); 
// Load the saveExtAddr pointer.

// Check for manual"Hold Auto Start"
//检测到有手工置位SW_1则会设置devState = DEV_HOLD,从而避开网络初始化
ZDAppCheckForHoldKey();

// Initialize ZDO items and setup the device - type of device to create.
ZDO_Init(); 
//通过判断预编译来开启一些函数功能


// Register the endpoint description with the AF
// This task doesn't have a Simple description, but we still need
// to register the endpoint.

afRegister( (endPointDesc_t *)&ZDApp_epDesc );

#if defined( ZDO_USERDESC_RESPONSE )
ZDApp_InitUserDesc();
#endif
 // ZDO_USERDESC_RESPONSE

// set broadcast address mask to support broadcast filtering
NLME_GetRequest(nwkCapabilityInfo, 0, &capabilities);
NLME_SetBroadcastFilter( capabilities );

// Start the device?
if ( devState != DEV_HOLD )
{
ZDOInitDevice( 0 );
}
/*如果devState=DEV_HOLD,则不会调用ZDOInitDevice()来初始化网络
即不组网也不入网.LED4闪烁等待应用程序来开启设备并初始化网络
*/
else
{
// Blink LED to indicate HOLD_START
HalLedBlink ( HAL_LED_4, 0, 50, 500 );
}

ZDApp_RegisterCBs();
}
**************************************
ZDApp_Init()中一些函数可以见主要函数说明1.记录下灯节点里ZDO_Init()这个函数.
*********************
void ZDO_Init( void )
{
// Initialize ZD items
#if defined ( REFLECTOR )
ZDO_EDBind = NULL;
#endif

// Setup the device - type of device to create.
ZDODeviceSetup();
}
*********************
灯节点预编译了REFLECTOR,使用源绑定机制.进入ZDODeviceSetup():
*********************
//根据编译选项来设置功能函数;
static void ZDODeviceSetup( void )
{
#if defined( ZDO_COORDINATOR ) //编译了ZDO_COORDINATOR
NLME_CoordinatorInit();
#endif

#if defined ( REFLECTOR ) //编译了REFLECTOR
#if defined ( ZDO_COORDINATOR ) //编译了REFLECTOR且编译了ZDO_COORDINATOR
APS_ReflectorInit( APS_REFLECTOR_PUBLIC );
#else //编译了REFLECTOR且编译了ZR或ED
APS_ReflectorInit( APS_REFLECTOR_PRIVATE ); 
#endif
#endif

//没有编译ZDO_COORDINATOR或者编译了SOFT_START
#if !defined( ZDO_COORDINATOR ) || defined( SOFT_START )
NLME_DeviceJoiningInit();
#endif
}
*********************
灯节点编译了ZDO_COORDINATOR、RTR_NWK、REFLECTOR和SOFT_START.因而启动的功能函数有:
NLME_CoordinatorInit(); APS_ReflectorInit( APS_REFLECTOR_PUBLIC );NLME_DeviceJoiningIni()
回到ZDApp_Init(),因为灯节点编译了HOLD_AUTO_START,则devState = DEV_HOLD,也就可以不用管有没有手工置位SW1了.因此不会进入ZDOInitDevice()来初始化设备(建网或入网).而是通过LED4的闪烁来指示这是一个非自动启动模式,等待应用程序来初始化设备.ZDApp_Init()初始化结束后再进入SAPI_Init():
**************************************
void osalInitTasks( void )
{
…………
macTaskInit( taskID++ );
nwk_init( taskID++ );
Hal_Init( taskID++ );
#if defined( MT_TASK )
MT_TaskInit( taskID++ );
#endif
APS_Init( taskID++ );
ZDApp_Init( taskID++ );
SAPI_Init( taskID );
}
**************************************
看下SAPI_Init():
**************************************
void SAPI_Init( byte task_id )
{
…………
if ( HalKeyRead () == HAL_KEY_SW_1)
{
//期间如果按下SW1键,则禁止自动启动和NV存储功能,更新配置重启
// If SW5 is pressed and held while powerup, force auto-start and nv-restore off and reset
startOptions = ZCD_STARTOPT_CLEAR_STATE | ZCD_STARTOPT_CLEAR_CONFIG; //默认启动状态和配置
//startOptions作为默认启动项配置,存到NV条目ZCD_NV_STARTUP_OPTION中
zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
zb_SystemReset(); //复位系统
}

#endif // HAL_KEY
#ifdef LCD_SUPPORTED
Print8(HAL_LCD_LINE_2 ,20,"Simple",1);
#endif
// Set an event to start the application
osal_set_event(task_id, ZB_ENTRY_EVENT);
}
**************************************
按键SW1(个人记录中按键有点乱,具体查看映射函数)功能和SW_BYPASS_NV、SW_BYPASS_START类似,置位可以避开一些功能.
可以看到触发sapi_TaskID的ZB_ENTRY_EVENT事件,来看下对ZB_ENTRY_EVENT的处理:
**************************************
if ( events & ZB_ENTRY_EVENT ) /*sapi启动事件*/
{
uint8 startOptions;
// Give indication to application of device startup
zb_HandleOsalEvent( ZB_ENTRY_EVENT );

// LED off cancels HOLD_AUTO_START blink set in the stack
HalLedSet (HAL_LED_4, HAL_LED_MODE_OFF); 
//关闭指示HOLD_AUTO_START的LED4

//读配置
zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
if ( startOptions & ZCD_STARTOPT_AUTO_START ) //自动启动
{
zb_StartRequest();
}
else
{
//LED2闪烁指示需要外部输入来配置信息再重启设备
// blink leds and wait for external input to config and restart
HalLedBlink(HAL_LED_2, 0, 50, 500);
#if defined SENSOR
…………
}

return (events ^ ZB_ENTRY_EVENT );
}
**************************************
从程序中可以看到首先读NV条目ZCD_NV_STARTUP_OPTION所存储的值,如果是ZCD_STARTOPT_AUTO_START则调用zb_StartRequest()来请求启动设备.NV条目ZCD_NV_STARTUP_OPTION的值在zgInit()中初始化为默认值(暂时估计为0,见基本问题说明9),因此这里不会调用zb_StartRequest()启动设备,而是LED2闪烁来指示需要外部输入来控制.
作为一个单一设备,main()函数中各项功能已经初始化完成并进入了系统主循环函数osal_start_system(),要不然也不会处理ZB_ENTRY_EVENT事件,只是这个时候还没有启动作为一个网络设备的功能.接下来就通过按键来启动,灯节点按下K1,按键处理函数处理如下:
**************************************
if ( keys & HAL_KEY_SW_1 ) 
{
/*初始化myAppState = APP_INIT,等灯设备逻辑类型确定并成功建网/入网,
zb_StartRequest()会产生回调函数zb_StartConfirm(),如果成功则设置
myAppState = APP_START,因而再按K1执行允许绑定功能.
*/
if ( myAppState == APP_INIT )
{
// In the init state, keys are used to indicate the logical mode.
// Key 1 starts device as a coordinator

zb_ReadConfiguration( ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType );
if ( logicalType != ZG_DEVICETYPE_ENDDEVICE )
{
logicalType = ZG_DEVICETYPE_COORDINATOR; //协调器
zb_WriteConfiguration(ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType);
}

// Do more configuration if necessary and then restart device with auto-start bit set
// write endpoint to simple desc...dont pass it in start req..then reset

zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
startOptions = ZCD_STARTOPT_AUTO_START;
zb_WriteConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
zb_SystemReset();

}
else
{
// Initiate a binding
zb_AllowBind( myAllowBindTimeout ); //默认为10S,myAllowBindTimeout=10
}
}
**************************************
按K1选择逻辑类型logicalType = ZG_DEVICETYPE_COORDINATOR,写入NV条目ZCD_NV_LOGICAL_TYPE,这里改变了ZDO全局变量zgDeviceLogicalType的值.灯节点中zgDeviceLogicalType被初始化为zgDeviceLogicalType = DEVICE_LOGICAL_TYPE=ZG_DEVICETYPE_SOFT(可选类型),而通过按K1,
zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR.(见基本问题说明5).
然后把启动选项startOptions = ZCD_STARTOPT_AUTO_START的值写入NV条目ZCD_NV_STARTUP_OPTION中.下次读取NV条目ZCD_NV_STARTUP_OPTION的值就为ZCD_STARTOPT_AUTO_START(因此会在对ZB_ENTRY_EVENT的处理中触发进入zb_StartRequest()).
最后重启系统.重新经过流程main()->osal_init_system()->osalInitTasks()->ZDApp_Init()->SAPI_Init()->触发sapi_TaskID的ZB_ENTRY_EVENT事件
此时
zb_ReadConfiguration( ZCD_NV_STARTUP_OPTION, sizeof(uint8), &startOptions );
if ( startOptions & ZCD_STARTOPT_AUTO_START )//自动启动
{
zb_StartRequest();
}
再读取NV条目ZCD_NV_STARTUP_OPTION值时为ZCD_STARTOPT_AUTO_START,因而进入zb_StartRequest():
**************************************
* @fn zb_StartRequest
*
* @brief The zb_StartRequest function starts the ZigBee stack. When the
* ZigBee stack starts, the device reads configuration parameters
* from Nonvolatile memory and the device joins its network. The
* ZigBee stack calls the zb_StartConrifm callback function when
* the startup process completes.
*
* @param none
*
* @return none
*/
void zb_StartRequest()
{
uint8 logicalType;

// Start the device
// start delay = min(NWK_START_DELAY, zgStartDelay) + rand() - only for fresh start, not restore
if ( zgStartDelay < NWK_START_DELAY )
zgStartDelay = 0;
else
zgStartDelay -= NWK_START_DELAY;

// check that bad combinations of compile flag definitions and device type
/*
// Values for ZCD_NV_LOGICAL_TYPE (zgDeviceLogicalType)
#define ZG_DEVICETYPE_COORDINATOR 0x00(协调器)
#define ZG_DEVICETYPE_ROUTER 0x01(路由器)
#define ZG_DEVICETYPE_ENDDEVICE 0x02(终端)
#define ZG_DEVICETYPE_SOFT 0x03(可选择类型)
*/
//读取存储在NV里的设备ZCD_NV_LOGICAL_TYPE
zb_ReadConfiguration( ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType );

//检查错误的参数.
//如:logicalType>0x02(可选择类型选择后不是协调器就是路由器);定义
//了协调器,但logicalType为终端设备;
//只可能是路由器时logicalType不为路由器;未编译RTR_NWK功能却定义了
//logicalType为协调器;只可能为终端时logicalType却未定义为终端;
if ( ( logicalType > ZG_DEVICETYPE_ENDDEVICE ) ||
#if defined( RTR_NWK )
#if defined( ZDO_COORDINATOR )
// Only RTR or Coord possible
( logicalType == ZG_DEVICETYPE_ENDDEVICE ) ||
#else
// Only RTR possible
( logicalType != ZG_DEVICETYPE_ROUTER ) ||
#endif
#else
#if defined( ZDO_COORDINATOR )
// Error
( 1 ) ||
#else
// only ED possible
( logicalType != ZG_DEVICETYPE_ENDDEVICE ) ||
#endif
#endif
( 0 ) )
{
// error configuration
SAPI_SendCback( SAPICB_START_CNF, ZInvalidParameter, 0 ); //发送错误报告 
}
else //设备逻辑类型参数都正确则调用ZDOInitDevice()
{
ZDOInitDevice(zgStartDelay);
}

return;
}

**************************************
读取设备存储在NV条目ZCD_NV_LOGICAL_TYPE中的逻辑类型参数(这里灯节点按K1逻辑类型参数为ZG_DEVICETYPE_COORDINATOR),并检验是否有错误,正确则调用ZDOInitDevice(zgStartDelay).看下ZDOInitDevice():
**************************************
uint8 ZDOInitDevice( uint16 startDelay )
{
//初始化设备网络状态为ZDO_INITDEV_NEW_NETWORK_STATE:新的网络状态.
//可能意味着ZCD_NV_STARTUP_OPTION不能恢复,或没有任何网络状态恢复
uint8 networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE;
uint16 extendedDelay = 0;

devState = DEV_INIT; // Remove the Hold state

// Initialize leave control logic
//函数读取NV项目ZCD_NV_LEAVE_CTRL的值,ZDApp_LeaveCtrl指向这个值
ZDApp_LeaveCtrlInit();

// Check leave control reset settings
ZDApp_LeaveCtrlStartup( &devState, &startDelay );

// Leave may make the hold state come back
//以上两个函数设置了对设备离开时的控制,如果有延时则延时,没有则
//把设备状态设为DEV_HOLD
if ( devState == DEV_HOLD )
//ZDO_INITDEV_LEAVE_NOT_STARTED:该设备没有在网络中,下次调用才启用.
return ( ZDO_INITDEV_LEAVE_NOT_STARTED ); 
// Don't join - (one time).

#if defined ( NV_RESTORE )
// Get Keypad directly to see if a reset nv is needed.
// Hold down the SW_BYPASS_NV key (defined in OnBoard.h)
// while booting(引导) to skip past NV Restore.
if ( HalKeyRead() == SW_BYPASS_NV )
//SW_BYPASS_NV按键处于按下状态时,则避开网络层的NV存储
networkStateNV = ZDO_INITDEV_NEW_NETWORK_STATE; //设备网络状态为新的网络状态

else
{
// Determine if NV should be restored
//函数返回的设备网络状态要么是新的网络状态;要么是恢复的网络状态;以此
//来确定要不要读取NV里相应条目来恢复网络先前状态
networkStateNV = ZDApp_ReadNetworkRestoreState();
}

//如果设备的网络状态为恢复的网络状态
if ( networkStateNV == ZDO_INITDEV_RESTORED_NETWORK_STATE )
{
//恢复先前的网络状态,把 devStartMode = MODE_RESUME!!!!
networkStateNV = ZDApp_RestoreNetworkState();
}
else //如果设备的网络状态为新的网络状态,在下面进行处理
{
// Wipe out(清除) the network state in NV
NLME_InitNV();
NLME_SetDefaultNV();//设置默认NV条目
}
#endif

//如果设备的网络状态为新的网络状态
if ( networkStateNV == ZDO_INITDEV_NEW_NETWORK_STATE )
{
//根据预编译来设置设备新的网络状态参数
ZDAppDetermineDeviceType();/*!!!!*/

// Only delay if joining network - not restoring network state
extendedDelay = (uint16)((NWK_START_DELAY + startDelay)
+ (osal_rand() & EXTENDED_JOINING_RANDOM_MASK));
}

// Initialize device security
ZDApp_SecInit( networkStateNV );

// Trigger the network start
ZDApp_NetworkInit( extendedDelay );

return ( networkStateNV );
}
**************************************
两种情况,编译了NV_RESTORE和未编译.见自启动模式记录的一些说明.灯节点没有编译NV_RESTORE因此networkStateNV == ZDO_INITDEV_NEW_NETWORK_STATE,调用ZDAppDetermineDeviceType():
**************************************
void ZDAppDetermineDeviceType( void )
{

if ( zgDeviceLogicalType == ZG_DEVICETYPE_ENDDEVICE )
return;

#if defined ( SOFT_START )
if ( zgDeviceLogicalType == ZG_DEVICETYPE_COORDINATOR )
{
devStartMode = MODE_HARD; // Start as a coordinator
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_COORDINATOR;
}
else
{
if ( zgDeviceLogicalType == ZG_DEVICETYPE_ROUTER )
{
softStartAllowCoord = FALSE; // Don't allow coord to start
continueJoining = TRUE;
}

devStartMode = MODE_JOIN; // Assume joining
}
#endif // SOFT_START
}
**************************************
灯节点编译过SOFT_START,且通过按K1设置了zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR,因而这里执行:
devStartMode = MODE_HARD; // Start as a coordinator
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_COORDINATOR;

然后跳回ZDOInitDevice()进入ZDApp_NetworkInit( extendedDelay )触发ZDAppTaskID的ZDO_NETWORK_INIT事件.看下对ZDO_NETWORK_INIT处理:
**************************************
UINT16 ZDApp_event_loop( byte task_id, UINT16 events )
{
…………
if ( events & ZDO_NETWORK_INIT )
{
// Initialize apps and start the network
devState = DEV_INIT;
ZDO_StartDevice( (uint8)ZDO_Config_Node_Descriptor.LogicalType, devStartMode,
DEFAULT_BEACON_ORDER, DEFAULT_SUPERFRAME_ORDER );

// Return unprocessed events
return (events ^ ZDO_NETWORK_INIT);
}
…………
}
**************************************
这里两个参数上面已经设置为:
devStartMode = MODE_HARD; // Start as a coordinator
ZDO_Config_Node_Descriptor.LogicalType = NODETYPE_COORDINATOR;
进入ZDO_StartDevice()
**************************************
void ZDO_StartDevice( byte logicalType, devStartModes_t startMode, byte beaconOrder, byte superframeOrder )
{
ZStatus_t ret;

ret = ZUnsupportedMode;

#if defined(ZDO_COORDINATOR) 
if ( logicalType == NODETYPE_COORDINATOR )
{
if ( startMode == MODE_HARD )//MODE_HARD 
{
devState = DEV_COORD_STARTING;//Started as Zigbee Coordinator
//建网
ret = NLME_NetworkFormationRequest( zgConfigPANID, zgDefaultChannelList,
zgDefaultStartingScanDuration, beaconOrder,
superframeOrder, false );
}
…………
**************************************
调用了NLME_NetworkFormationRequest()来建立网络.对NLME_NetworkFormationRequest()的调用会产生一个回调函ZDO_NetworkFormationConfirmCB()(参见主要函数说明3),接下来的回调函数流程与[Zigbee网络设备启动流程—协调器(自启动模式)]记录中的一样.
记录下最终应用层任务sapi_TaskID对ZDO_STATE_CHANGE事件的处理:
**************************************
UINT16 SAPI_ProcessEvent( byte task_id, UINT16 events )
{
…………
case ZDO_STATE_CHANGE: /*网络状态变化*/
//作为网络设备成功开启,通知应用层
// If the device has started up, notify the application
if (pMsg->status == DEV_END_DEVICE ||
pMsg->status == DEV_ROUTER ||
pMsg->status == DEV_ZB_COORD )
{
SAPI_StartConfirm( ZB_SUCCESS );
}
else if (pMsg->status == DEV_HOLD ||
pMsg->status == DEV_INIT)
{
SAPI_StartConfirm( ZB_INIT );
}
break;
…………
}
**************************************
调用了SAPI_StartConfirm():
**************************************
void SAPI_StartConfirm( uint8 status )
{
#if defined ( MT_SAPI_CB_FUNC )
/* First check if MT has subscribed for this callback. If so , pass it as
a event to MonitorTest and return control to calling function after that */
if ( SAPICB_CHECK( SPI_CB_SAPI_START_CNF ) )
{
zb_MTCallbackStartConfirm( status );
}
else
#endif //MT_SAPI_CB_FUNC
{
zb_StartConfirm( status );
}
}
**************************************
调用了zb_MTCallbackStartConfirm(),如果编译过MT_SAPI_CB_FUNC则也会调用zb_MTCallbackStartConfirm()通知MT.看下zb_StartConfirm()
**************************************
void zb_StartConfirm( uint8 status )
{
// If the device sucessfully started, change state to running
if ( status == ZB_SUCCESS )
{
myAppState = APP_START;
}
else
{
// Try again later with a delay
osal_start_timerEx(sapi_TaskID, MY_START_EVT, myStartRetryDelay);
}
}
**************************************
最终改变myAppState值来通知应用层网络设备启动成功.


3、协调器(非自启动模式HOLD_AUTO_START)—以SimpleApp的灯节点SimpleControllerEB按K1作为协调器启动为例.假设初始化成功,网络建立成功.
程序大致流程:
main()->osal_init_system()->osalInitTasks()->ZDApp_Init()->SAPI_Init()->触发sapi_TaskID的ZB_ENTRY_EVENT事件->等待按键->按K1写设备逻辑类型和启动选项到NV->zb_SystemReset()->main()->osal_init_system()->osalInitTasks()->ZDApp_Init()->SAPI_Init()->触发sapi_TaskID的ZB_ENTRY_EVENT事件->zb_StartRequest()->ZDOInitDevice()->ZDApp_NetworkInit()->触发ZDAppTaskID的ZDO_NETWORK_INIT->ZDO_StartDevice()->NLME_NetworkFormationRequest()->网络建立成功ZDO_NetworkFormationConfirmCB->触发ZDAppTaskID的ZDO_NETWORK_START->ZDApp_NetworkStartEvt()->触发ZDAppTaskID的ZDO_STATE_CHANGE_EVT->ZDO_UpdateNwkStatus()->触发sapi_TaskID的ZDO_STATE_CHANGE事件->SAPI_StartConfirm()->zb_StartConfirm()->设置myAppState = APP_START通知应用层网络设备开启成功,使再按K1时执行允许绑定功能.


***************************************
***************************************

说明:
1、本文为个人学习笔记,纯属个人理解,错误不可避免,仅供参考.随时更新
2、细节基本不管,遇到问题再作分析,程序注释为个人原始注释内容,记录有些仓促. 
3、欢迎交流,转载请注明出处,谢谢!

2010.7.09 ~XF

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值