Zigbee通訊之開發篇(基於TI 的Z-Stack)

1.Zigbee協議和Z-Stack

  Zigbee協議和Z-Stack是什麼關係?這可能是初學Zigbee同學想知道的問題。給大家舉個例子吧,我們生活中使用的插排是要符合一定的標準的,現在國家標準是GB2099.3-2008,裏面規定了好多插排的電氣、機械等要求。不同廠家生產的插排,要在中國國內銷售的話,必須符合這個標準。但是生產這個插排的廠家多了去了,像 公牛、philips等等(排除做廣告的嫌疑)。其實Zigbee協議棧規範和Z-Stack的關係也差不多,Z-Stack就是符合Zigbee協議棧規範的一個硬件和軟件平臺,是Zigbee協議棧的一個具體實現。當然,還有其他的具體實現,freakz協議棧和contiki操作系統、TinyOS等等大家要注意,Z-Stack是TI公司提供的協議棧,它是個半開源的協議棧,有些核心代碼是以庫的形式提供的,所以要想深入瞭解協議棧或者想進一步提升編程能力的同學還是找一個全開源的協議棧玩玩吧。比如,freakz協議棧。

2.IAR和Z-Stack

 Z-Stack的整個開發環境IDE使用的是IAR(IAR的版本需要參考Z-Stack Home Sample Application User's Guide。此文檔在TI提供的資料包裏\Documents內)。從來沒有使用過IAR的同學請參考文章最後附件:IAR入門。這只是個簡單的入門指導,想詳細瞭解IAR或者使用過程中遇到什麼問題請使用IAR的help選項。

 我們知道Zigbee設備的分爲Coordinator、Router、Enddevice三種角色,這三種角色在IAR裏怎麼修改吶?我們打開一個TI HA(TI提供的關於智能家居的解決方案),下載地址見下載平臺:http://down.51cto.com/data/2067778

wKioL1Wk53LxIEsoAAFyv-dkGSI060.jpg

從圖中標示的位置可以修改這三種設備角色,其實這裏的選項是修改IAR project配置的地方,這裏TI提供的project裏已經配置好了這三鍾Zigbee設備角色的配置文件。我們這裏只選擇就可以了。

Z-Stack軟件結構有一個很大的特點:使用宏定義來區分是否編譯某一模塊/功能/函數。

wKioL1Wk7nWQmoxpAAGhZuoMECk541.jpg

wKiom1Wk7QPiEbzKAAEYILvI9E8551.jpg

全是根據宏定義來決定是否編譯這個功能,有的是根據是否定義這個宏,有的是根據定義的這個宏的值來決定其什麼作用。這也可以理解,因爲TI提供的是一個通用的基礎開發平臺,需要考慮兼容性,易用性,並且硬件資源有限,只能使用宏定義的方式在程序預編譯階段根據宏定義就可以知道需要哪些功能了。

那麼關鍵的問題來了,這些宏定義在IAR裏是怎麼定義的類?在IAR中有兩種定義方式:

第一種:

wKiom1Wk7tHBhrJaAAG4NBIk_mE550.jpg

在打開的TI project工程目錄的Tools下面,有***.cfg文件,這些是IAR Compiler command-line options。可以在這裏定義宏。

wKiom1Wk71aBvXOYAAIr1o9BUYo509.jpg

定義的方法是“-D 加上你要定義的宏”,例如上面的 -DMAC_CFG_TX_MAX,其實定義的宏就是MAC_CFG_TX_MAX,你搜索整個工程就可以找到在哪裏使用了這個宏。取消定義可以在定義前加上“//”。例如//-DMAC_CFG_TX_DATA_MAX=5。

你可能想知道IAR讀取這些cfg文件需要不需要配置?自己新增加配置文件怎麼辦?

打開菜單欄 project---option

wKioL1Wk8w7TzqMGAAH4PJsKoOM737.jpg

找到C/C++Complier 選項,然後選擇Extra Options,在這裏寫你自己需要引用的cfg文件就可以了。

第二種:

打開菜單欄 project---option 然後找到preprocessor選項。

wKioL1Wk9GSQ33xeAAJl6zDtaeg698.jpg

在這裏也可以定義宏,直接書寫宏的名字即可,例如:“ZTOOL_P1”。取消宏定義可以在前面添加“x”,例如“xZTOOL_P1”。當然也可以定義有值的宏,“LCD_SUPPORTED=DEBUG”。

另外,還要說一下上面截圖中的上面的部分:是添加頭文件包含路徑的,這樣在源代碼包含頭文件時你就不用書寫好長的路徑名稱了,直接寫頭文件名稱就行。


關於這兩種定義宏有什麼區別?要是兩個地方都定義了相同的宏但值不同(有可能是你馬虎定義重複了),這種情況以那個爲準?

關於區別,第一種定義方式其實就是將要定義的某一類功能的宏都放到一個文件中,方便修改、查找。這樣所有的工程都可以通過指定文件的方式來使用這些宏定義或者宏定義的值。比較方便一些,例如:TI工程裏的f8wCoord.cfg這個宏定義配置文件,是針對所有Coordinator的定義,好多不同project想編譯成Coordinator角色的都可以引用這個command-line options文件。

第二種方式定義只是針對具體某個project的,通過第二種方式設置的內容都會存儲在***.ewp文件中,這個就是具體某個project的具體配置。

還有一點需要說明:

wKioL1Wlx1fQmKbFAACoarni4NA973.jpg

上面第一處是IAR關於某一個project的一個配置文件,選中不同的配置文件,IAR就會根據不同的配置進行編譯、鏈接等等一系列動作。這裏的配置主要包括:菜單欄裏 roject---option裏面所有的設置,還有選擇是否編譯某一個具體的文件。

wKioL1WlyIXg5kgVAAHZkcjuFi4900.jpg

顯示爲暗灰色的X號的文件不參與此project的編譯,設置方法爲在project具體某個文件上右鍵--option--Exclude from buid。如上圖所示。

IAR會單獨建立文件夾用於保存不同peoject配置的編譯、鏈接生成的文件。就相當於利用不同的配置實現不同的功能,最明顯的你想編譯一個release版本,編譯一個debug版本,release版本不包括調試信息。你就可以設置兩個配置。新增配置的方法是菜單欄--project---edit configration裏面add即可。

wKioL1WlyeWyQEVkAACjfSXP9fQ752.jpg

這裏是選擇同一個工作空間裏不同的項目的。即IAR管理思路是這樣的:一個workspace裏可以包含好多的project,而一個project又可以存在好多種的配置。具體參考菜單欄---help---IDE Project Management and Building Guide。


wKiom1WlyHOzxGHAAAB3cEnhh_s461.jpg

這是IAR的鏈接器使用的鏈接腳本,使用這個文件制定不同變量或者存儲區域的最終鏈接地址,還有其他一些功能,具體參考菜單欄---help---Linker and Library Reference Guide。

3.Z-Stack軟件結構簡介

 關於Z-Stack結構比較詳細的資料需要看這兩個TI的官方資料:Z-Stack Home Developer's Guide.pdf

Z-Stack Home Sample Application User's Guide.pdf。是比較全面的資料。關於TI Z-Stack的project各個文件夾的作用網絡上已經有大量的資料,這裏就不一一贅述。大家可以到網絡搜索資料學習。我這裏只是簡單說一下基於Z-Stack協議棧開發application的思路和方法。Z-Stack project不僅僅提供了Zigbee協議棧的各層API,還提供了一個基於輪詢調度的OS(osal),還提供了一些硬件資源驅動API。各個API使用說明見 TI安裝包 Document---API裏面有各個API的使用說明。

下面我們重點說說這個OSAL,因爲它是一個簡易的輪詢式操作系統,Z-Stack協議使用它作爲簡單的任務管理、調度、任務間通通訊,使用它使其軟件結構更清晰。另外,我們基於Z-Stack協議的Application設計也要基於此軟件結構。關於這個東東的講解可以參考:http://bbs.feibit.com/thread-16-1-1.html。我這裏舉個例子讓大家好理解這個輪詢操作系統。如果大家有嵌入式實時操作系統的知識,那這個OSAL就比較好理解。其實OSAL並不是實際意義上的操作系統,它只是一個輪詢系統。大家可以想象一個部門有好幾個僱員,只有一個辦公電腦,這個辦公電腦同一時間只能有一位僱員使用,僱員使用辦公電腦需要部門領導審批。部門領導根據僱員年齡的大小進行排序,年齡小的先使用,年齡大的後使用。部門領導負責通知各個僱員在使用辦公電腦時都需要做哪些具體的工作,若某一僱員在得到辦公電腦的使用權時沒有任何事情需要做,那他就將辦公電腦讓給下一個等待的僱員使用。部門領導會根據發生的事件(包括外部事件、僱員之間需要溝通)記錄在一個工作安排薄裏,每一個僱員得到辦公電腦都需要查詢工作安排薄來看自己有哪些工作,工作做完了,再接着查詢,直到沒有自己的工作了,就讓下一個等待的僱員使用辦公電腦。

這下大家可能明白了,若是那個年齡最小的僱員老是有任務做,那其他僱員就沒有機會使用辦公電腦了。所以這個OSAL只是一個簡單的輪詢外部事件的簡單調度系統。想了解嵌入式實時操作系統的相關知識,可以學習一下UCOS的相關資料。

另外,我們還需要了解這樣一種軟件設計思路:Z-Stack作爲一個基礎軟件開發包,爲了易於維護軟件結構設計時是分層的,那各層之間如何通訊?上層需要調用下層的服務時,直接調用下層提供的API接口即可,那下層有一些緊急事件或者有些變化是上層關心的,如何通知到上層吶?上層接收到這樣的消息時有可能需要做不同的操作,這一般怎麼實現吶?這種下層事件發生需要通知上層的情況,需要使用回調函數,下層事件發生,會調用用戶高層註冊的函數來處理下層事件,這就實現了下層到上層的通訊。

所以一般我們需要註冊回調函數,然後底層事件發生會調用我們註冊的函數,註冊函數可以根據傳遞過來的參數做相應處理。

4.兩個基於TI CC2530和Z-Stack平臺的設備Zigbee通訊

 一個Coordinator一個Enddevice,它們之間通訊,我們在Z-Stack提供的project---SimpleLight的基礎上進行修改源代碼做我們的實驗。實現現象如下:Coordinator建立網絡後,Enddevice設備加入網絡,然後Coordinator通過廣播的方式發送字符串“Coordinator send!”,EndDevice收到此字符串後控制LED燈閃爍,並且向Coordinator發送“EndDevice received!\r\n”。Coordinator收到後,通過串口打印出來。使用的硬件平臺爲battery board。

我們選擇CoordinatorEB配置選項。

wKiom1WmDf2y2l_iAACvLl5s4Ks966.jpg

我們移除project中App文件夾中的zcl_samplelight.c、zcl_samplelight.h、zcl_samplelight_data.c,添加Coordinator.c、Coordinator.h這兩個文件,OSAL_GenericApp.c這個文件是OSAL層和Application層之間的接口文件。這個文件主要負責OSAL的task的初始化,添加task event處理函數。

先看Coordinator的代碼:

我們新添加一個task,在Z-Stack中新添加task一般需要以下兩步:1,添加task初始化函數:GenericApp_Init。

wKioL1WmBUXwI8H9AANH8n0uEuA443.jpg

/*********************************************************************
 * @fn      osalInitTasks
 *
 * @brief   This function invokes the initialization function for each task.
 *
 * @param   void
 *
 * @return  none
 */
void osalInitTasks( void )
{
  uint8 taskID = 0;

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

  macTaskInit( taskID++ );
  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++ );
#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )
  ZDNwkMgr_Init( taskID++ );
#endif
  GenericApp_Init( taskID );
}

這個初始化函數要在OSAL_GenericApp.c文件裏的void osalInitTasks( void )函數裏添加調用。如上圖所示。

2,在OSAL_GenericApp.c文件裏的pTaskEventHandlerFn tasksArr[]數組裏添加用於task event處理的函數GenericApp_event_loop。

wKioL1WmBevRH21rAANIhS5fKSI783.jpg

// 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
  GenericApp_event_loop
};

注意初始化task和處理task event的函數順序要一致。

我們先來看void GenericApp_Init(byte task_id)函數都做了些什麼?

void GenericApp_Init(byte task_id)
{
    GenericApp_TaskID           = task_id;
    GenericApp_TransID          = 0;
    
    GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
    GenericApp_epDesc.task_id  = &GenericApp_TaskID;
    GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc ;
    GenericApp_epDesc.latencyReq = noLatencyReqs;
    
    
    afRegister( &GenericApp_epDesc ); 
    
    
    
    //UART configuration
      halUARTCfg_t uartConfig;
      uartConfig.configured           = TRUE;
      uartConfig.baudRate             = HAL_UART_BR_115200;
      uartConfig.flowControl          = FALSE;
      uartConfig.callBackFunc         = NULL;
      HalUARTOpen (0, &uartConfig);
      
      
      HalLedSet(HAL_LED_ALL,HAL_LED_MODE_OFF);
     
}

1,初始化了task的ID GenericApp_TaskID的值。

2,定義了一個endPointDesc_t端點描述符。

3,初始化了串口,用於串口輸出。

4,初始化所有LED爲OFF狀態。

我們再來看uint16 GenericApp_event_loop( uint8 task_id, uint16 events )函數。

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

  (void)task_id;  // Intentionally unreferenced parameter

  if ( events & SYS_EVENT_MSG )
  {
    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID )) )
    {
      switch ( MSGpkt->hdr.event )
      {
        case AF_INCOMING_MSG_CMD:
        GenericApp_MessageMSGCB( MSGpkt );    
        break; 
        case ZDO_STATE_CHANGE:
             GenericApp_NwkState = (devStates_t) (MSGpkt->hdr.status);
             if(GenericApp_NwkState == DEV_ZB_COORD)
             {
                osal_start_timerEx(GenericApp_TaskID,SEND_BROADCAST_MESSAGE,2000);
             }
             break; 
        default:
          break;
      }
     
      // Release the memory
      osal_msg_deallocate( (uint8 *)MSGpkt );
    }

    // return unprocessed events
           
    return (events ^ SYS_EVENT_MSG);
  }
  
  //if need send brodcast message
  if(events & SEND_BROADCAST_MESSAGE )
  {
      GenericApp_SendTheMessage( );
      osal_start_timerEx(GenericApp_TaskID,SEND_BROADCAST_MESSAGE,5000);
      return (events ^ SEND_BROADCAST_MESSAGE);
  }

  // Discard unknown events
  return 0;
}

這個函數裏主要處理了兩個系統event,一個是ZDO_STATE_CHANGE事件,當Zigbee網絡發生變化時(有新設備加入)產生此事件,在此事件裏我們啓動了一個定時事件SEND_BROADCAST_MESSAGE,用於廣播Zigbee信息。另外還有一個AF_INCOMING_MSG_CMD事件,當接收到Zigbee信息包時,會產生此事件,在此事件處理函數中將Coordinator接收到的信息通過串口打印出來。

所需要的函數如下:

void GenericApp_MessageMSGCB(afIncomingMSGPacket_t *pkt)
{
    unsigned char buffer[20] ;
    
    unsigned char frame_end[2] = {'\r','\n'};
    
    switch(pkt->clusterId)
    {
        case GENERICAPP_CLUSTERID:
             osal_memcpy(buffer,pkt->cmd.Data,20);
             HalUARTWrite(0, buffer, 20);
             HalUARTWrite(0, frame_end, 2);
            
             break;
      
    }
}

void GenericApp_SendTheMessage(void)
{
    unsigned char *theMessageData = "Coordinator send!";
    afAddrType_t my_DstAddr;
    my_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast;
    my_DstAddr.endPoint = GENERICAPP_ENDPOINT;
    my_DstAddr.addr.shortAddr =0xFFFF ;
    
    AF_DataRequest(&my_DstAddr,&GenericApp_epDesc,GENERICAPP_CLUSTERID,osal_strlen((char *)theMessageData)+1,theMessageData,&GenericApp_TransID,AF_DISCV_ROUTE,AF_DEFAULT_RADIUS);
}

Enddevice的代碼和這個基本上差不多。

我們在CoordinatorEB配置下添加Enddevice.c,並將其設置爲不參與buid。方法是在Enddevice.c文件上右鍵----option---Exclude from buid。然後切換配置到EnddeviceEB配置選項,然後將Coordinator.c設置成不參與buid狀態。

task初始化函數:void GenericApp_Init(byte task_id)

void GenericApp_Init(byte task_id)
{
    GenericApp_TaskID           = task_id;
    GenericApp_TransID          = 0;
    
    GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;
    GenericApp_epDesc.task_id  = &GenericApp_TaskID;
    GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc ;
    GenericApp_epDesc.latencyReq = noLatencyReqs;
    
    
    afRegister( &GenericApp_epDesc ); 
    
    
   HalLedSet(HAL_LED_ALL,HAL_LED_MODE_OFF);
}

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

  (void)task_id;  // Intentionally unreferenced parameter

  if ( events & SYS_EVENT_MSG )
  {
    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID )) )
    {
      switch ( MSGpkt->hdr.event )
      {
        case AF_INCOMING_MSG_CMD:
        GenericApp_MessageMSGCB( MSGpkt );    
        break; 
        
        default:
          break;
      }
     
      // Release the memory
      osal_msg_deallocate( (uint8 *)MSGpkt );
    }

    // return unprocessed events
           
    return (events ^ SYS_EVENT_MSG);
  }
  

  // Discard unknown events
  return 0;
}
void GenericApp_SendTheMessage(void)
{
    unsigned char *theMessageData = "EndDevice received!\r\n";
    
    afAddrType_t my_DstAddr;
    my_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
    my_DstAddr.endPoint = GENERICAPP_ENDPOINT;
    my_DstAddr.addr.shortAddr =0x0000 ;
    
    AF_DataRequest(&my_DstAddr,&GenericApp_epDesc,GENERICAPP_CLUSTERID,osal_strlen((char *)theMessageData)+1,theMessageData,&GenericApp_TransID,AF_DISCV_ROUTE,AF_DEFAULT_RADIUS);
}


static unsigned char led_state = 1;
void GenericApp_MessageMSGCB(afIncomingMSGPacket_t *pkt)
{
    unsigned char buffer[20] ;
   
    
    switch(pkt->clusterId)
    {
        case GENERICAPP_CLUSTERID:
             osal_memcpy(buffer,pkt->cmd.Data,osal_strlen("Coordinator send!")+1);
             
             if(osal_memcmp(buffer,"Coordinator send!",osal_strlen("Coordinator send!")+1))
             {
                
                if(led_state)
                {   
                    led_state = !led_state ;
                    HalLedSet(HAL_LED_4,HAL_LED_MODE_ON);
                }
                else
                {
                    led_state = !led_state ;
                    HalLedSet(HAL_LED_4,HAL_LED_MODE_OFF);
                }
                
                
                GenericApp_SendTheMessage();
             }
            
             break;
      
    }
}

想使用串口,還需要定義宏:

wKiom1WmD2mSUnOaAAJdpwfVFnI871.jpg

將上述代碼編譯後分別下載到兩個Zigbee開發板上,Enddevice板上的LED燈會閃爍,Coordinator的串口會輸出:“EndDevice received!\r\n”。說明程序正常運行。

通過上面的簡單實驗,你可能對TI的Z-Stack有了一定的感性認識,但是對代碼和原理還不是特別清楚。

沒關係,這是第一步,有了感性認識,再結合TI提供的開發文檔和源代碼,我們對原理也會有一定的認識的。


下面我們來看一個重要的函數:

/*********************************************************************
 * @fn      AF_DataRequest
 *
 * @brief   Common functionality for invoking APSDE_DataReq() for both
 *          SendMulti and MSG-Send.
 *
 * input parameters
 *
 * @param  *dstAddr - Full ZB destination address: Nwk Addr + End Point.
 * @param  *srcEP - Origination (i.e. respond to or ack to) End Point Descr.
 * @param   cID - A valid cluster ID as specified by the Profile.
 * @param   len - Number of bytes of data pointed to by next param.
 * @param  *buf - A pointer to the data bytes to send.
 * @param  *transID - A pointer to a byte which can be modified and which will
 *                    be used as the transaction sequence number of the msg.
 * @param   options - Valid bit mask of Tx options.
 * @param   radius - Normally set to AF_DEFAULT_RADIUS.
 *
 * output parameters
 *
 * @param  *transID - Incremented by one if the return value is success.
 *
 * @return  afStatus_t - See previous definition of afStatus_... types.
 */
uint8 AF_DataRequestDiscoverRoute = DISC_ROUTE_NETWORK;
afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,
                           uint16 cID, uint16 len, uint8 *buf, uint8 *transID,
                           uint8 options, uint8 radius )

這個函數是用來發送Zigbee無線數據的。我們看看這個函數都需要哪些參數?

dstAddr:包括網絡地址和端點號。

srcEP: 端點描述符

cID:  指定一個ClusterID。

len:  發送數據的長度。

*buf:  需要發送的數據內容。


我們知道Zigbee要發送信息,需要知道對方的短地址,端點號,還需要指定一個Cluster ID,這些信息和程序是怎麼關聯起來的吶?光說這些名詞感覺有點抽象。

好吧,我們就一起分析一下。

我們在程序裏定義了一個endPointDesc_t GenericApp_epDesc;

這個結構體變量中都有哪些內容吶?

GenericApp_epDesc.endPoint = GENERICAPP_ENDPOINT;

GenericApp_epDesc.task_id  = &GenericApp_TaskID;

GenericApp_epDesc.simpleDesc =(SimpleDescriptionFormat_t *)&GenericApp_SimpleDesc ;

有端點號,taskID,還有一個簡單描述符(這個後面再說)。

你看,通過這個端點描述符把端點號和taskID聯繫在了一起。

接着,我們使用afRegister( &GenericApp_epDesc );向AF層註冊了這個端點描述符。也就是說AF層收到發給相應端點號的數據時,會通過OSAL註冊一個系統event,OSAL輪詢調度會調用與之關聯的taskID,這裏就是GenericApp_event_loop。在這個函數裏我們看到有如下代碼:

wKioL1WmHLDRhBqJAAEZRP8vv5s108.jpg判斷這個系統event如果是AF_INCOMING_MSG_CMD,也就是收到了AF層發來的信息,就調用GenericApp_MessageMSGCB( MSGpkt );在這個函數裏:

wKiom1WmG0XC6RYgAAD08lzWupA515.jpg

判斷是哪個cluster ID,根據不同的cluster ID做不同的動作。

總結一下就是:短地址用於確定發送給哪個Zigbee設備,端點號用於確定你發給這個Zigbee設備的哪個端口,在發送給這個端口中使用Cluster ID來區分不同的功能。

簡單描述符:

const SimpleDescriptionFormat_t GenericApp_SimpleDesc =
{
  GENERICAPP_ENDPOINT,               //  int Endpoint; 
  GENERICAPP_PROFID,                //  uint16 AppProfId[2];
  GENERICAPP_DEVICEID,              //  uint16 AppDeviceId[2];
  GENERICAPP_DEVICE_VERSION,        //  int   AppDevVer:4;
  GENERICAPP_FLAGS,                 //  int   AppFlags:4;
  GENERICAPP_MAX_CLUSTERS,          //  uint8  AppNumInClusters;
  (cId_t *)GenericApp_ClusterList,  //  uint8 *pAppInClusterList;
  0,                                //  uint8  AppNumInClusters;
  (cId_t *)NULL                     //  uint8 *pAppOutClusterList;
};

這裏麪包含了端點號、profileID、deviceID等內容,這些是和profile的規範有關,zclSampleLight_InClusterList 和zclSampleLight_OutClusterList這些和binding有關係。就是建立綁定關係時,需要相應的Cluster一致。

5.ZCLHA是什麼?

 

ZCL的全稱是Zigbee Cluster Library。

ZCL在zigbee中相當於Cluster功能倉庫的作用。開發者開發新的Profile就必須加入相應的ZCL簇功能函數到該Profile中。

也就是說ZCL相當於一個存儲命令集合的倉庫。節點與節點之間,利用ZCL的命令來進行通信。

wKioL1WmI57wn6gfAAHarqd0wRs375.jpg

在Zigbee中,一個簇羣就是一個容器,在容器中以命令結構體包含了一個或多個屬於某個應用剖面的屬性/消息,不管應用剖面如何,相同的設備(比如開關)擁有相同的定義和功能。屬性是設備的變量或特性,能夠設置或獲得。比如設置自動調溫器的加熱點。ZCL提供了一種機制,利用這種機制設備能夠將變化異步地報告給屬性(attribute),比如當空氣變熱時自動控溫器服務器就將室溫改變報告給他的客戶端,這個過程不需要客戶端發起請求。

ZCL採用客戶端/服務器模塊的模式,一般儲存簇屬性的作爲服務器,影響或操作屬性的作爲客戶端。然而如果需要,屬性也可以呈現在客戶端上。例如,設備通過讀寫屬性的命令來操作屬性,這些命令從客戶端設備發送到服務器設備;對這些命令的應答從服務器設備發送到客戶端設備;但是報告屬性命令是從服務器發送到客戶端。cluster ID是每個簇的標誌,由剖面分配,在內部使用的是邏輯簇ID,所以還有一個Cluster ID轉換表。

Z-Stack中這一部分的代碼在project的Profile文件夾中,使用Z-Stack的ZCL API請參閱TI提供的資料Document/api/Z-Stack ZCL API.pdf.

HA的全稱是home Automation,家庭自動化。這個是Zigbee聯盟專門爲智能家居領域指定的一個規範,這個是基於Zigbee協議棧的一個應用層的規範。這個規範主要規定了智能家居產品的屬性和動作,以及收到相應數據後應該做的動作。不同廠家生產的基於Zigbee的智能家居產品都可以兼容,只要他們做的產品符合HA的規範。

上個圖:wKioL1WnLWWhh4RxAAOzOTgJFBU716.jpg

上述部分說的比較簡略,詳細內容請參閱:文章後面的附件:ZigBee_Cluster_Library_Public.pdfZigBee Home AutomationPublicApplicationProfile.pdf

6.Zigbee抓包工具(SmartRF Packet Sniffer)的使用

 Zigbee Radio層採用2.4G無線傳輸信息,我們希望利用工具抓到空中數據包用於學習、分析無線數據包的格式。另外,抓包還可以分析實際遇到的問題。

TI給我們提供了空中抓包方案,需要一個硬件CC2531 USB Dongle,還需要安裝一個SmartRF Packet Sniffer的抓包軟件。具體抓包軟件使用方法請參見 SmartRF Packet Sniffer軟件菜單欄---help---User’s Manual。這裏不再贅述,也可以到網上搜索相關資料。


wKiom1WmJiziunKIAA8hK8O-T_g554.jpg上面是一張我使用上述工具抓到的數據包,可以分析Zigbee協議各個層的數據內容,協議內容。結合Zigbee Specification可以加深對Zigbee協議的理解,同樣,也可以分析實際應用中遇到的問題。


7.總結

 Zigbee定義了從物理介質傳輸、網絡層、應用層還有不同領域的應用規範。可以說Zigbee協議棧是一個比較大的通訊協議集合。針對Zigbee的認識和學習,我的建議是首先根據你的應用目的利用TI平臺提供的各種API實現想要的功能。慢慢加深對Zigbee的認識。針對Zigbee的ZCL和HA詳細的內容我也在學習當中,上述內容純屬個人見解,如有錯誤,歡迎指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章