Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

[ZigBee 嵌入式系統] ZigBee 應用實作 - 使用 TI Z-Stack Firmware

4 803 vues

Publié le

Publié dans : Ingénierie
  • Soyez le premier à commenter

[ZigBee 嵌入式系統] ZigBee 應用實作 - 使用 TI Z-Stack Firmware

  1. 1. Chien-Jung Li Apr. 2014 ZigBee應用實作 — 使用TI Z-Stack Firmware
  2. 2. 2 大綱  SmartRF05EB牛刀小試:使用PER Test範例展示  SampleApp範例展示  BasicApp:瞭解OSAL的基本運作  BasicApp:OSAL的任務間通訊(IPC)機制  FlyApp:組織PAN網路實驗  擴充FlyApp  FlyApp:Cluster與Binding  解析SampleApp  解析SimpleApp:使用 Simple API  ZigBee Cluster Library
  3. 3. SmartRF05EB牛刀小試
  4. 4. 4 準備好你的硬體
  5. 5. 5 安裝相關開發軟體  …hiver.teamProject Repository0_ZigBee模組TI Software  swrc176y - SmartRF Studio 7 v1.15.0  swrc044s - SmartRF Flash Programmer v1.12.7  swrc045y - SmartRF Packet Sniffer v2.17.1  swrc096e - zSensorMonitor v1.3.2  hiver.teamProject Repository0_ZigBee模組TI SDK and Examples  swrc126g - Zstack CC2530 2.5.1a (預設裝在C:Texas Instruments資料夾下)  swrc135b - cc2530 SW example.zip 解開至C:Texas Instruments  IAR for 8051 (這大家應該都有了/記得compiler版本要升到8.10.4)
  6. 6. 6 SmartRF05EB測試程式 (I) – PER Test  用IAR開啟 C:Texas Instrumentsswrc135b - cc2530 SW exampleidecc2530_sw_examples.eww  我們接下來要使用官方的「封包錯誤率測試應用程式」 來測試一下SmartRF05EB
  7. 7. 7 SmartRF05EB測試程式 (II) – PER Test  先Rebuild試試看,你會發現錯誤  進option換掉linker檔 再rebuild一次 C:Program Files (x86)IAR Systems Embedded Workbench 6.08051configdevices Texas Instrumentslnk51ew_cc2530F256.xcl
  8. 8. 8 SmartRF05EB測試程式 (III) – PER Test  將兩片板子分別接到host, 然後下載  使用板子上的Button1與 Joystick 將 一 片 板 子 設 為 Transmitter,另一片設為 Receiver 。 發 射 端 按 下 Joystick中鍵後即進入PER 測試模式  RSSI是接收功率強度  接收板可拿到遠處觀察
  9. 9. 範例應用程式:SampleApp
  10. 10. 10 測試SampleApp應用程式  準備SmartRF05EB 2片  用Jumper將1片設為Coordinator,另1片設為Router  連線完成後,按Coordinator的SW1會廣播訊息給Group1裝置,收到 訊息者會toggle LED1。  按Router的SW2會使改變自身是否屬於「Group1」。若Router脫離 Group1,那麼Coordinator按SW1時,Router將不會有任何反應 P18_9 P18_11 相連則為 協調器
  11. 11. 11 SampleApp Project – 使用DemoEB  C:Texas InstrumentsZStack-CC2530- 2.5.1aProjectszstackSamplesSampleAppCC2530DBSampleApp.eww 分別燒錄兩塊板子 1片設為Coordinator 1片設為Router
  12. 12. 12 SampleApp.c /***************************************************************************** Filename: SampleApp.c Description: Sample Application (no Profile). *****************************************************************************/ /**************************************************************************** This application isn't intended to do anything useful, it is intended to be a simple example of an application's structure. This application sends it's messages either as broadcast or broadcast filtered group messages. The other (more normal) message addressing is unicast. Most of the other sample appli- cations are written to support the unicast message model. Key control: SW1: Sends a flash command to all devices in Group 1. SW2: Adds/Removes (toggles) this device in and out of Group 1. This will enable and disable the reception of the flash command. *****************************************************************************/ /************************* INCLUDES **************************/ … 略 … /************************* MACROS ***************************/ /************************* CONSTANTS ************************/
  13. 13. BasicApp 瞭解OSAL的基本運作
  14. 14. 14 BasicApp  目標:  BasicApp是一個簡化過的應用程式範例,我們先將 無線傳輸功能抽掉,將它當成是一般的嵌入式系統 來使用。  Firmware內載作業系統抽象層OSAL,我們將學習如 何使用OSAL來協助我們完成應用程式。  此外,我們還會試著使用HAL API來測試一下硬體。  將BasicApp_wo_RF.zip解壓縮到 C:Texas InstrumentsZStack-CC2530-2.5.1aProjectszstackSamples
  15. 15. 15 系統運作原理 Endpoint App 240 User App Endpoint App 239 User App Endpoint App 1 User App Endpoint App 0 ZDO Application Framework (AF) ZigBee Stack (Z-stack) MAC (TI-MAC) PHY Hardware OSAL HAL I/O
  16. 16. 16 開發應用程式的三個必要檔案  以BasicApp為例:  OSAL_BasicApp.c  BasicApp.h  BasicApp.c 1. 程式進入點 main()@ZMain.c 2. 作業系統初始化 osal_init_system()@OSAL.c 3. Tasks/應用程式初始化 OSAL_<AppName>.c
  17. 17. 17 OSAL_BasicApp.c // Filename: OSAL_BasicApp.c #include "ZComDef.h" #include "hal_drivers.h" #include "OSAL.h" #include "OSAL_Tasks.h" #include "BasicApp.h" /********* GLOBAL VARIABLES ********/ // The order in this table must be identical to the task // initialization calls below in osalInitTask. const pTaskEventHandlerFn tasksArr[] = { Hal_ProcessEvent, BasicApp_ProcessEvent }; const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] ); uint16 *tasksEvents; /********* FUNCTIONS **********/ /******************************************************* * @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)); Hal_Init( taskID++ ); BasicApp_Init( taskID ); }
  18. 18. 18 BasicApp.h // BasicApp.h #ifndef BasicApp_H #define BasicApp_H #include "ZComDef.h" /******* CONSTANTS ********/ // These constants are only for example and should be // changed to the device's needs #define BasicApp_ENDPOINT 10 #define BasicApp_PROFID 0x0F04 #define BasicApp_DEVICEID 0x0001 #define BasicApp_DEVICE_VERSION 0 #define BasicApp_FLAGS 0 #define BasicApp_MAX_CLUSTERS 1 #define BasicApp_CLUSTERID 1 // Application Events (OSAL) - These are bit weighted definitions. #define BasicApp_SEND_MSG_EVT 0x0001 /********* FUNCTIONS *********/ /* Task Initialization for the Generic Application */ extern void BasicApp_Init( byte task_id ); /* Task Event Processor for the Generic Application */ extern UINT16 BasicApp_ProcessEvent( byte task_id, UINT16 events ); #endif /* BasicApp_H */
  19. 19. 19 BasicApp.c // BasicApp.c /******* INCLUDES ********/ #include "OSAL.h" #include "AF.h" #include "ZDApp.h" //#include "ZDObject.h" //#include "ZDProfile.h" #include "BasicApp.h" #include "DebugTrace.h" #if !defined( WIN32 ) #include "OnBoard.h" #endif /* HAL */ #include "hal_lcd.h" #include "hal_led.h" #include "hal_key.h" #include "hal_uart.h" /******* GLOBAL VARIABLES *******/ // This list should be filled with Application specific Cluster IDs. const cId_t BasicApp_ClusterList[BasicApp_MAX_CLUSTERS] = { BasicApp_CLUSTERID }; const SimpleDescriptionFormat_t BasicApp_SimpleDesc = { BasicApp_ENDPOINT, // int Endpoint; BasicApp_PROFID, // uint16 AppProfId[2]; BasicApp_DEVICEID, // uint16 AppDeviceId[2]; BasicApp_DEVICE_VERSION, // int AppDevVer:4; BasicApp_FLAGS, // int AppFlags:4; BasicApp_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)BasicApp_ClusterList, // byte *pAppInClusterList; BasicApp_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)BasicApp_ClusterList // byte *pAppInClusterList; }; endPointDesc_t BasicApp_epDesc; /******* LOCAL VARIABLES *******/ byte BasicApp_TaskID; // Task ID for internal task/event processing // This variable will be received when // BasicApp_Init() is called. afAddrType_t BasicApp_DstAddr;
  20. 20. 20 /******* LOCAL FUNCTIONS *******/ static void BasicApp_HandleKeys( byte shift, byte keys ); static void BasicApp_MessageMSGCB( afIncomingMSGPacket_t *pckt ); /******* PUBLIC FUNCTIONS *****/ /********************************************************************* * @fn BasicApp_Init * @brief Initialization function for the Basic App Task. * This is called during initialization and should contain * any application specific initialization (ie. hardware * initialization/setup, table initialization, power up * notificaiton ... ). * @param task_id - the ID assigned by OSAL. This ID should be * used to send messages and set timers. * @return none */ void BasicApp_Init( uint8 task_id ) { BasicApp_TaskID = task_id; // Register for all key events - This app will handle all key events RegisterForKeys( BasicApp_TaskID ); // Update the display #if defined ( LCD_SUPPORTED ) HalLcdWriteString( "BasicApp", HAL_LCD_LINE_1 ); #endif }
  21. 21. 21 /*********************************************************************************************** * @fn BasicApp_ProcessEvent * @brief Generic Application Task event processor. This function is called to process * all events for the task. Events include timers, messages and any other user * defined events. * @param task_id - The OSAL assigned task ID. * @param events - events to process. This is a bit map and can contain more than one event. * @return none */ uint16 BasicApp_ProcessEvent( uint8 task_id, uint16 events ) { afIncomingMSGPacket_t *MSGpkt; (void)task_id; // Intentionally unreferenced parameter if ( events & SYS_EVENT_MSG ) { MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( BasicApp_TaskID ); while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) { case KEY_CHANGE: BasicApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys ); break; case AF_INCOMING_MSG_CMD: BasicApp_MessageMSGCB( MSGpkt ); break; default: break; }
  22. 22. 22 // Release the memory osal_msg_deallocate( (uint8 *)MSGpkt ); // Next MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( BasicApp_TaskID ); } // return unprocessed events return (events ^ SYS_EVENT_MSG); } // Send a message out - This event is generated by a timer // (setup in BasicApp_Init()). if ( events & BasicApp_SEND_MSG_EVT ) { return (events ^ BasicApp_SEND_MSG_EVT); } // Discard unknown events return 0; }
  23. 23. 23 /********************************************************************* * @fn BasicApp_HandleKeys * @brief Handles all key events for this device. * @param shift - true if in shift/alt. * @param keys - bit field for key events. Valid entries: HAL_KEY_SW_4, HAL_KEY_SW_3, HAL_KEY_SW_2, HAL_KEY_SW_1 * @return none */ static void BasicApp_HandleKeys( uint8 shift, uint8 keys ) { // 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 ( keys & HAL_KEY_SW_2 ) { } if ( keys & HAL_KEY_SW_3 ) { } if ( keys & HAL_KEY_SW_4 ) { } } }
  24. 24. 24 /******** LOCAL FUNCTIONS *******/ /********************************************************************* * @fn BasicApp_MessageMSGCB * @brief Data message processor callback. This function processes * any incoming data - probably from other devices. So, based * on cluster ID, perform the intended action. * @param none * * @return none */ static void BasicApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { switch ( pkt->clusterId ) { case BasicApp_CLUSTERID: // "the" message #if defined( LCD_SUPPORTED ) HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" ); #elif defined( WIN32 ) WPRINTSTR( pkt->cmd.Data ); #endif break; } }
  25. 25. 25 練習1:使用LED驅動程式  打開HAL API手冊,找到LED Service。  練習使用 HalLedSet() HalLedBlink() HalLedEnterSleep() HalLedExitSleep() // @fn BasicApp_HandleKeys static void BasicApp_HandleKeys( uint8 shift, uint8 keys ) { // Shift is used to make each button/switch dual purpose. if ( shift ) { ...略... } else { if ( keys & HAL_KEY_SW_1 ) { } if ( keys & HAL_KEY_SW_2 ) { } if ( keys & HAL_KEY_SW_3 ) { } if ( keys & HAL_KEY_SW_4 ) { } } }
  26. 26. 26 練習2:使用LCD驅動程式  打開HAL API手冊,找到LCD Service。  練習使用 HalLcdWriteString() HalLcdWriteValue() HalLcdWriteScreen() HalLcdWriteStringValue() HalLcdWriteStringValueValue() HalLcdDisplayPercentBar() // @fn BasicApp_HandleKeys static void BasicApp_HandleKeys( uint8 shift, uint8 keys ) { // Shift is used to make each button/switch dual purpose. if ( shift ) { ...略... } else { if ( keys & HAL_KEY_SW_1 ) { } if ( keys & HAL_KEY_SW_2 ) { } if ( keys & HAL_KEY_SW_3 ) { } if ( keys & HAL_KEY_SW_4 ) { } } }
  27. 27. 27 練習3:在LCD顯示LED狀態  練習使用 HalLedSet() HalLedGetState() if ( keys & HAL_KEY_SW_1 ) { HalLedSet(HAL_LED_1, HAL_LED_MODE_TOGGLE); if(HalLedGetState()&0x01) HalLcdWriteString("LED1:ON", HAL_LCD_LINE_1); else HalLcdWriteString("LED1:OFF", HAL_LCD_LINE_1); }
  28. 28. 28 練習4:使用RTC // number of seconds since 0 hrs, 0 minutes, 0 seconds, on the // 1st of January 2000 UTC typedef uint32 UTCTime; // To be used with typedef struct { uint8 seconds; // 0-59 uint8 minutes; // 0-59 uint8 hour; // 0-23 uint8 day; // 0-30 uint8 month; // 0-11 uint16 year; // 2000+ } UTCTimeStruct; #include "OSAL_Clock.h" /***** GLOBAL VARIABLES *****/ UTCTimeStruct utc; UTCTime utcSecs; /***** LOCAL FUNCTIONS *****/ static void showTimeonLCD(UTCTimeStruct *utc); void BasicApp_Init( uint8 task_id ) { ... 略 ... utc.seconds = 20; utc.minutes = 25; utc.hour = 13; utc.day = 23; utc.month = 2; utc.year = 2014; if ((utc.hour < 24) && (utc.minutes < 60) && (utc.seconds < 60) && (utc.month < 12) && (utc.day < 31) && (utc.year > 1999) && (utc.year < 2136)) { /* check for leap year */ if ((utc.month != 1) || (utc.day < (IsLeapYear( utc.year ) ? 29 : 28))) { /* Numbers look reasonable, convert to UTC */ utcSecs = osal_ConvertUTCSecs( &utc ); } } if ( utcSecs == 0 ) { /* Bad parameter(s) */ } else { /* Parameters accepted, set the time */ osal_setClock( utcSecs ); } }
  29. 29. 29 static void BasicApp_HandleKeys( uint8 shift, uint8 keys ) { // Shift is used to make each button/switch dual purpose. if ( shift ) { ... 略 ... } else { if ( keys & HAL_KEY_SW_1 ) { utcSecs = osal_getClock(); osal_ConvertUTCTime( &utc, utcSecs ); showTimeonLCD(&utc); } ... 略 ... } } /***** LOCAL FUNCTIONS *****/ void showTimeonLCD(UTCTimeStruct *utc) { char timeStr[17]; timeStr[0] = (utc->year/1000) + 0x30; timeStr[1] = ((utc->year%1000)/100) + 0x30; timeStr[2] = ((utc->year%100)/10) + 0x30; timeStr[3] = (utc->year%10) + 0x30; timeStr[4] = '/'; timeStr[5] = ((utc->month+1)/10) + 0x30; timeStr[6] = ((utc->month+1)%10) + 0x30; timeStr[7] = '/'; timeStr[8] = ((utc->day+1)/10) + 0x30; timeStr[9] = ((utc->day+1)%10) + 0x30; timeStr[10] = ' '; timeStr[11] = ((utc->hour)/10) + 0x30; timeStr[12] = ((utc->hour)%10) + 0x30; timeStr[13] = ':'; timeStr[14] = ((utc->minutes)/10) + 0x30; timeStr[15] = ((utc->minutes)%10) + 0x30; timeStr[16] = '0'; HalLcdWriteString(timeStr, HAL_LCD_LINE_3); }
  30. 30. 30 練習5:使用ADC讀值並顯示  支援7~12位元解析度(頻寬4~30kHz),取樣速度4 MHz。 共8個ADC通道可使用(P0埠),支援單端與差動類比輸入。 #include "hal_adc.h" if ( keys & HAL_KEY_SW_2 ) { uint16 sample_catched = 0; sample_catched = HalAdcRead(HAL_ADC_CHANNEL_7, HAL_ADC_RESOLUTION_8); HalLcdWriteValue(100, 10, HAL_LCD_LINE_1); HalLcdWriteStringValue("Read", sample_catched, 10, HAL_LCD_LINE_1); } HalAdcRead(HAL_ADC_CHANNEL_TEMP, HAL_ADC_RESOLUTION_14); HalAdcRead(HAL_ADC_CHANNEL_VDD, HAL_ADC_RESOLUTION_14);
  31. 31. 31 練習6:NV記憶體  CC2530的記憶體結構
  32. 32. 32 NV記憶體API osal_nv_item_init() init an item in non-volatile memory osal_nv_read() read item osal_nv_write() write item osal_offsetof() calculate memory offset // OSAL NV item IDs #define ZCD_NV_EXTADDR 0x0001 #define ZCD_NV_BOOTCOUNTER 0x0002 #define ZCD_NV_STARTUP_OPTION 0x0003 #define ZCD_NV_START_DELAY 0x0004 ... 略 ... // NV Items Reserved for applications (user app) // 0x0401 – 0x0FFF #define ZCD_NV_APP_SAVE1 0x0401 #define ZCD_NV_APP_SAVE2 0x0404 /***** GLOBAL VARIABLES *****/ uint8 nv_init_data = 0xA2; static void BasicApp_HandleKeys( uint8 shift, uint8 keys ) { uint8 nv_data; ... 略 ... if ( keys & HAL_KEY_SW_3 ) { osal_nv_item_init(ZCD_NV_APP_SAVE1,1,NULL); osal_nv_read(ZCD_NV_APP_SAVE1,0,1,&nv_data); HalLcdWriteStringValue("Read", nv_data, 16, HAL_LCD_LINE_1); } if ( keys & HAL_KEY_SW_4 ) { nv_data = 0xB3; osal_nv_write(ZCD_NV_APP_SAVE1,0,1,&nv_data); }
  33. 33. 33 練習7:設定事件與事件處理器  打開OSAL API手冊。  OSAL所提供的API,相當於是PC上的系統呼叫,功能非 常多樣與強大,但有些功能是留給框架使用的,我們 在開發App時,大概沒機會用到。  OSAL是屬於「事件驅動式」的系統,我們首先試一下 跟App開發有關的「事件設定API」。  只要可以設定並觸發事件,這一切將變得更有趣。 osal_set_event() osal_start_timerEx() osal_start_reload_timer() osal_stop_timerEx()
  34. 34. 34 定義App專屬的事件  打開BasicApp.h,設定「事件的名字跟識別碼」  BasicApp.c:在按鍵處理函式調用OSAL的API來觸發事件。 /********* CONSTANTS *********/ ... 略 ... // Application Events (OSAL) - These are bit weighted definitions. #define BasicApp_SEND_MSG_EVT 0x0001 #define BasicApp_TOGGLE_LED1_EVT 0x0001 #define BasicApp_TOGGLE_LED2_EVT 0x0002 #define BasicApp_TOGGLE_LED3_EVT 0x0004 #define BasicApp_COUNT_LCD_EVT 0x0008 if ( keys & HAL_KEY_SW_1 ) { osal_set_event(BasicApp_TaskID, BasicApp_TOGGLE_LED1_EVT); } if ( keys & HAL_KEY_SW_2 ) { osal_start_timerEx(BasicApp_TaskID, BasicApp_TOGGLE_LED2_EVT, 5000); } if ( keys & HAL_KEY_SW_3 ) { osal_start_reload_timer(BasicApp_TaskID, BasicApp_TOGGLE_LED3_EVT, 250); } if ( keys & HAL_KEY_SW_4 ) { osal_start_reload_timer(BasicApp_TaskID, BasicApp_COUNT_LCD_EVT, 1000); }
  35. 35. 35 修改事件處理器  修改BasicApp_ProcessEvent( ) uint8 mycounts = 0;先在global var加入一全域變數 if ( events & BasicApp_TOGGLE_LED1_EVT ) { HalLedSet(HAL_LED_1, HAL_LED_MODE_TOGGLE); return (events ^ BasicApp_TOGGLE_LED1_EVT); } if ( events & BasicApp_TOGGLE_LED2_EVT ) { HalLedSet(HAL_LED_2, HAL_LED_MODE_TOGGLE); return (events ^ BasicApp_TOGGLE_LED2_EVT); } if ( events & BasicApp_TOGGLE_LED3_EVT ) { HalLedSet(HAL_LED_3, HAL_LED_MODE_TOGGLE); return (events ^ BasicApp_TOGGLE_LED3_EVT); } if ( events & BasicApp_COUNT_LCD_EVT ) { mycounts++; HalLcdWriteStringValue ("Counts:", mycounts, 10, 1); return (events ^ BasicApp_COUNT_LCD_EVT); }
  36. 36. 36 使用osal_stop_timerEx()取消事件觸發 // Shift is used to make each button/switch dual purpose. if ( shift ) { if ( keys & HAL_KEY_SW_1 ) { } if ( keys & HAL_KEY_SW_2 ) { osal_stop_timerEx(BasicApp_TaskID, BasicApp_TOGGLE_LED2_EVT); } if ( keys & HAL_KEY_SW_3 ) { osal_stop_timerEx(BasicApp_TaskID, BasicApp_TOGGLE_LED3_EVT); } if ( keys & HAL_KEY_SW_4 ) { osal_stop_timerEx(BasicApp_TaskID, BasicApp_COUNT_LCD_EVT); } } else {
  37. 37. 37 練習8、9、10  練習8:定時取樣 設計一應用程式,按下起始鈕後,每0.5秒讀一次ADC取樣值,並將結果 顯示在LCD。按下停止鈕後,停止取樣與顯示。  練習9:換一顆「事件驅動」的腦袋  按下起始鈕後,每0.5秒遞增變數counts並將其值顯示在LCD上。  當計數值等於30,停止計數,LCD會自動在1秒鐘後將計數值顯示為0。  在計數期間,當counts為5的倍數時,觸發一個事件來toggle LED1。 為 了 練 習 「 事 件 驅 動 」 的 理 念 , 請 用 「 事 件 」 的 觀 念 來 處 理 , 不 要 在 BasicApp_COUNT_LCD_EVT的事件處理程序裡面直接調用HalLedSet()。你應該要調用 「事件設定」函式來做!  練習10:如何重複? 如何不使用osal_start_reload_timer()而達到重複設定事件的效果? 請寫一支程式展示效果。
  38. 38. 38 練習11:紅綠燈系統  試試看,參考你之前做好的紅綠燈子系統,你能用 「事件驅動」的方法讓它在BasicApp中運作起來嗎?
  39. 39. BasicApp OSAL的任務間通訊(IPC)機制
  40. 40. 40 一個App自己的世界  BasicApp.h定義的事件  在BasicApp.c中,使用OSAL系統呼叫來設定事件  若你有第二個App (FooApp),你可以在BasicApp.c中調用 來觸發FooApp應用的事件嗎?  如果BasicApp還需要夾帶一段資料給FooApp.c呢? // Application Events (OSAL) - These are bit weighted definitions. #define BasicApp_TOGGLE_LED1_EVT 0x0001 #define BasicApp_TOGGLE_LED2_EVT 0x0002 #define BasicApp_TOGGLE_LED3_EVT 0x0004 #define BasicApp_COUNT_LCD_EVT 0x0008 osal_set_event(BasicApp_TaskID, BasicApp_TOGGLE_LED1_EVT); osal_set_event(FooApp_TaskID, FooApp_SOME_EVT);
  41. 41. 41 有了IPC機制,再多Apps也不怕!  BasicApp是1個App,對OSAL而言,它是個「以事件驅動 為中心的狀態機 (或許稱為Delegator更貼切)」,其實只 是一個事件處理函式,這個函式就是排給OSAL的task。  系統若存在2個以上的Apps BasicApp_ProcessEvent() BasicApp_HandleKeys() BasicApp_MessageMSGCB()
  42. 42. 42 練習:兩個Apps  BasicApp: 這個應用程式是一個控制器,它會以IPC的方式傳送訊息(指令或資料)給 系統中的另外一個應用程式CountApp。  CountApp: 收到START指令後,它會開始計數並在LCD第一行顯示計數值。 收到STOP指令時,CountApp將停止計數,LCD第一行顯示將靜止。 收到WRITE指令後,它會將BasicApp傳過來的字串資料寫到LCD的第二行。 收到CLEAR指令後,它會將LCD的第二行清除。
  43. 43. 43 先準備BasicAppComm.h  BasicAppComm.h 所 準 備 的 東 西 , 是 要 讓 BasicApp 跟 CountApp彼此都認識的事情。  第一個是「訊息的格式」:basicAppMsg_t  第二個是「指令的定義」:指令識別碼 typedef struct { osal_event_hdr_t hdr; uint8 appCmd; uint8 appDataLen; uint8 *appData; } basicAppMsg_t; /********************************************************************* * CONSTANTS */ // Application CMD #define CountApp_START_COUNT_CMD 0x01 #define CountApp_STOP_COUNT_CMD 0x02 #define CountApp_WRITE_LCD_CMD 0x03 #define CountApp_CLEAR_LCD_CMD 0x04
  44. 44. 44 將BasicApp複製為CountApp  因為我們是基於框架來開發應用程式,因此應用程式 的「殼」看起來會很相似。將BasicApp.h / BasicApp.c 複製為CountApp.h / CountApp.c,接著打開兩個新檔案 將程式中「BasicApp」字樣全部用「CountApp」取代。  首先修改OSAL_BasicApp.c,它負責告訴OSAL要載入哪 些應用程式: /* OSAL_BasicApp.c */ /* INCLUDES */ ...略... #include "BasicApp.h" #include "CountApp.h" /* GLOBAL VARIABLES */ const pTaskEventHandlerFn tasksArr[] = { Hal_ProcessEvent, BasicApp_ProcessEvent, CountApp_ProcessEvent }; const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] ); uint16 *tasksEvents; /* FUNCTIONS */ /* @fn osalInitTasks */ void osalInitTasks( void ) { uint8 taskID = 0; ... 略 ... Hal_Init( taskID++ ); BasicApp_Init( taskID++ ); CountApp_Init( taskID ); }
  45. 45. 45 搞定BasicApp  BasicApp.h (加事件定義)  BasicApp.c #define BasicApp_ENDPOINT 10 // Application Events (OSAL) #define BasicApp_SEND_MSG_EVT 0x0001 #include "BasicAppComm.h" ...略... /* LOCAL FUNCTIONS */ static void BasicApp_send_IPCMsg(uint8 task_id, uint8 appCMD, uint8 *pBuf, uint8 dataLen); ...略... uint16 BasicApp_ProcessEvent( uint8 task_id, uint16 events ) { ... 略 ... if ( events & BasicApp_SEND_MSG_EVT ) { BasicApp_send_IPCMsg(2, CountApp_START_COUNT_CMD, "", 0); return (events ^ BasicApp_SEND_MSG_EVT); } ... 略 ... }
  46. 46. 46 /* LOCAL FUNCTIONS */ void BasicApp_send_IPCMsg(uint8 task_id, uint8 appCMD, uint8 *pBuf, uint8 dataLen) { basicAppMsg_t *msg; /* Build and send the message to the APP */ msg = (basicAppMsg_t *)osal_msg_allocate(sizeof(basicAppMsg_t) + (dataLen)); if ( msg ) { /* Build and send message up the app */ msg->hdr.event = BasicApp_MSG; msg->hdr.status = 0; msg->appCmd = appCMD; msg->appDataLen = dataLen; if (dataLen == 0) { msg->appData = (uint8*)(msg); } else { msg->appData = (uint8*)(msg+1); osal_memcpy( msg->appData, pBuf, dataLen); } osal_msg_send( task_id, (uint8 *)msg ); } } 用FileSeek搜尋*.h檔關鍵字「OSAL System Message IDs/Events」
  47. 47. 47 static void BasicApp_HandleKeys( uint8 shift, uint8 keys ) { ... 略 ... if ( keys & HAL_KEY_SW_1 ) { osal_set_event(BasicApp_TaskID, BasicApp_SEND_MSG_EVT); } if ( keys & HAL_KEY_SW_2 ) { BasicApp_send_IPCMsg(2, CountApp_STOP_COUNT_CMD, "", 0); } if ( keys & HAL_KEY_SW_3 ) { uint8 msg_str[] = "Show me!"; uint8 msg_len = (sizeof(msg_str)/sizeof(uint8)); BasicApp_send_IPCMsg(2, CountApp_WRITE_LCD_CMD, msg_str, msg_len); } if ( keys & HAL_KEY_SW_4 ) { BasicApp_send_IPCMsg(2, CountApp_CLEAR_LCD_CMD, "", 0); } } }
  48. 48. 48 搞定CountApp  CountApp.h #ifndef CountApp_H #define CountApp_H /* INCLUDES */ #include "ZComDef.h" /* CONSTANTS */ // Application Events (OSAL) - These are bit weighted definitions. #define CountApp_COUNT_LCD_EVT 0x0001 /* FUNCTIONS */ /* * Task Initialization for the Generic Application */ extern void CountApp_Init( byte task_id ); /* * Task Event Processor for the Generic Application */ extern UINT16 CountApp_ProcessEvent( byte task_id, UINT16 events ); #endif /* CountApp_H */
  49. 49. 49  CountApp.c #include "BasicAppComm.h" #include "CountApp.h" ... 略 ... /* GLOBAL VARIABLES */ uint8 mycounts = 0; ... 略 ... /* LOCAL FUNCTIONS */ static void CountApp_HandleBasicAppMSG(basicAppMsg_t *pkt ); void CountApp_Init( uint8 task_id ) { CountApp_TaskID = task_id; }
  50. 50. 50 /* @fn CountApp_ProcessEvent */ uint16 CountApp_ProcessEvent( uint8 task_id, uint16 events ) { ... 略 ... if ( events & SYS_EVENT_MSG ) { MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( CountApp_TaskID ); while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) { case BasicApp_MSG: CountApp_HandleBasicAppMSG( (basicAppMsg_t *)MSGpkt ); break; ... 略 ... } osal_msg_deallocate( (uint8 *)MSGpkt ); MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( CountApp_TaskID ); } return (events ^ SYS_EVENT_MSG); } if ( events & CountApp_COUNT_LCD_EVT ) { HalLcdWriteStringValue ("Counts:", mycounts, 10, 1); mycounts++; osal_start_timerEx(CountApp_TaskID, CountApp_COUNT_LCD_EVT, 1000); return (events ^ CountApp_COUNT_LCD_EVT); } return 0; }
  51. 51. 51 /********************************************************************* * @fn CountApp_HandleBasicAppMSG * @brief Data message processor callback. This function processes * incoming data from BasicApp. * @param Incoming message * @return none */ static void CountApp_HandleBasicAppMSG(basicAppMsg_t *pkt ) { switch ( pkt->appCmd ) { case CountApp_START_COUNT_CMD: osal_set_event(CountApp_TaskID, CountApp_COUNT_LCD_EVT); break; case CountApp_STOP_COUNT_CMD: osal_stop_timerEx(CountApp_TaskID, CountApp_COUNT_LCD_EVT); break; case CountApp_WRITE_LCD_CMD: HalLcdWriteString((char*)(pkt->appData), HAL_LCD_LINE_2); break; case CountApp_CLEAR_LCD_CMD: HalLcdWriteString("", HAL_LCD_LINE_2); break; } }
  52. 52. 組織PAN網路實驗 範例應用程式:FlyApp
  53. 53. 53 目標  接下來的實驗,我們會試著組建一個ZigBee的PAN,然 後一步步地接觸descriptors、application framework、 binding等重要的概念。  Starting a network  Routing  Packet Sniffer  Descriptors  Application Framework  Binding  ZDO APIs  Callbacks  Multiple Endpoints  Mobility
  54. 54. 54 通訊協定層
  55. 55. 55 組建PAN網路  我們將使用FlyApp作為範例,並隨實驗過程慢慢地修改 這個應用程式以符合需求。以下是所需硬體:  需要Coordinator、Router以及End-Device三塊板子  需要CC2531 USB Dongle作為Packet Sniffer  用IAR打開FlyApp Project並設定channel與PAN ID 打開f8wConfig.cfg設定channel跟PAN ID (見下頁表格)。實際應用中,你可 以打開很多個channel讓stack自己去搜索。但為了簡化實驗,我們在這裡 只打開一個channel就好,以便讓Sniffer可容易找到通道。
  56. 56. 56 在f8wConfig.cfg組態檔設定PAN ID  PAN_ID = 0xFFFF & device = Coordinator:  Device uses IEEE address to choose a PAN_ID (last 2 bytes)  PAN_ID = 0xFFFF & device = Router 或 End Device  Device will join any available PAN  PAN_ID ≠ 0xFFFF & device = Coordinator  Device will use the set value for the PAN_ID  PAN_ID ≠ 0xFFFF & device = Router 或 End Device  Device will ONLY join a PAN that has this PAN_ID
  57. 57. 57 設定f8wConfig.cfg  根據下表任意選取一組workgroup的通道與PAN ID設定 進行實驗: Workgroup Channel PAN ID Frequency (MHz) 1 12 (0x0C) 0x0AAA 2410 2 13 (0x0D) 0x0BEE 2415 3 14 (0x0E) 0x0CEE 2420 4 15 (0x0F) 0x0DEE 2425 5 16 (0x10) 0x0EEE 2430 6 17 (0x11) 0x0EFF 2435 7 18 (0x12) 0x0BAB 2440
  58. 58. 58  將Automatic Polling功能關掉 為簡化實驗,請先關掉Automatic Polling功能。若這個功能啟用的話,End Device會週期性地對Coordinator發出data request,這樣會干擾我們在 Sniffer想觀察的東西。所以,請在f8wConfig.cfg中令DPOLL_RATE=0 (ED用)。  在板子實體上標記它們的角色 用便利貼寫下Coordinator、Router以及End Device,並貼在各塊板子上做 識別。
  59. 59. 59 修改OSAL_FlyApp.c /**** INCLUDES ****/ ... 略 #include "nwk.h" #include "APS.h" #include "ZDApp.h" #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) #include "ZDNwkMgr.h" #endif #if defined ( ZIGBEE_FRAGMENTATION ) #include "aps_frag.h" #endif const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, 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 FlyApp_ProcessEvent }; mac_api.h nwk.h APS.h aps_frag.h ZDApp.h ZDMwkMgr.h
  60. 60. 60 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++ ); 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 FlyApp_Init( taskID ); }
  61. 61. 61 回顧一下FlyApp.h (不用改) /**** Filename: FlyApp.h ****/ ...略 #include "ZComDef.h" /**** CONSTANTS ****/ #define FlyApp_ENDPOINT 10 #define FLYAPP_PROFID 0x0F04 #define FLYAPP_DEVICEID 0x0001 #define FLYAPP_DEVICE_VERSION 0 #define FLYAPP_FLAGS 0 #define FLYAPP_MAX_CLUSTERS 1 #define FLYAPP_CLUSTERID 1 // Send Message Timeout #define FLYAPP_SEND_MSG_TIMEOUT 5000 // Every 5 seconds // Application Events (OSAL) - These are bit weighted definitions. #define FLYAPP_SEND_MSG_EVT 0x0001 /**** FUNCTIONS ****/ /* Task Initialization for the Generic Application */ extern void FlyApp_Init( byte task_id ); /* Task Event Processor for the Generic Application */ extern UINT16 FlyApp_ProcessEvent( byte task_id, UINT16 events );
  62. 62. 62 修改FlyApp.c /**** INCLUDES ****/ #include "OSAL.h" #include "AF.h" #include "ZDApp.h" #include "ZDObject.h" #include "ZDProfile.h" #include "FlyApp.h" #include "DebugTrace.h" ... 略 ... /***** LOCAL VARIABLES *****/ byte FlyApp_TaskID; // Task ID for internal task/event processing // This variable will be received when // FlyApp_Init() is called. devStates_t FlyApp_NwkState; byte FlyApp_TransID; // This is the unique message ID (counter) afAddrType_t FlyApp_DstAddr; /**** LOCAL FUNCTIONS ****/ static void FlyApp_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg ); static void FlyApp_HandleKeys( byte shift, byte keys ); static void FlyApp_MessageMSGCB( afIncomingMSGPacket_t *pckt ); static void FlyApp_SendTheMessage( void ); const cId_t FlyApp_ClusterList[FLYAPP_MAX_CLUSTERS] = { FLYAPP_CLUSTERID }; const SimpleDescriptionFormat_t FlyApp_SimpleDesc = { FLYAPP_ENDPOINT, // int Endpoint; FLYAPP_PROFID, // uint16 AppProfId[2]; FLYAPP_DEVICEID, // uint16 AppDeviceId[2]; FLYAPP_DEVICE_VERSION, // int AppDevVer:4; FLYAPP_FLAGS, // int AppFlags:4; FLYAPP_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)FlyApp_ClusterList, // byte *pAppInClusterList; FLYAPP_MAX_CLUSTERS, // byte AppNumInClusters; (cId_t *)FlyApp_ClusterList // byte *pAppInClusterList; }; endPointDesc_t FlyApp_epDesc;
  63. 63. 63 void FlyApp_Init( uint8 task_id ) { FlyApp_TaskID = task_id; FlyApp_NwkState = DEV_INIT; FlyApp_TransID = 0; // 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(). FlyApp_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent; FlyApp_DstAddr.endPoint = 0; FlyApp_DstAddr.addr.shortAddr = 0; // Fill out the endpoint description. FlyApp_epDesc.endPoint = FLYAPP_ENDPOINT; FlyApp_epDesc.task_id = &FlyApp_TaskID; FlyApp_epDesc.simpleDesc = (SimpleDescriptionFormat_t *)&FlyApp_SimpleDesc; FlyApp_epDesc.latencyReq = noLatencyReqs; // Register the endpoint description with the AF afRegister( &FlyApp_epDesc ); // Register for all key events - This app will handle all key events RegisterForKeys( FlyApp_TaskID ); // Update the display #if defined ( LCD_SUPPORTED ) HalLcdWriteString( "FlyApp", HAL_LCD_LINE_1 ); #endif ZDO_RegisterForZDOMsg( FlyApp_TaskID, End_Device_Bind_rsp ); ZDO_RegisterForZDOMsg( FlyApp_TaskID, Match_Desc_rsp ); } 見 ZDProfile.h 手冊 Z-Stack API.pdf
  64. 64. 64 uint16 FlyApp_ProcessEvent( uint8 task_id, uint16 events ) { afIncomingMSGPacket_t *MSGpkt; afDataConfirm_t *afDataConfirm; // Data Confirmation message fields byte sentEP; ZStatus_t sentStatus; byte sentTransID; // This should match the value sent (void)task_id; // Intentionally unreferenced parameter if ( events & SYS_EVENT_MSG ) { MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( FlyApp_TaskID ); while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) { case ZDO_CB_MSG: FlyApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt ); break; case KEY_CHANGE: FlyApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys ); break;
  65. 65. 65 case AF_DATA_CONFIRM_CMD: // This message is received as a confirmation of a data packet sent. // The status is of ZStatus_t type [defined in ZComDef.h] // The message fields are defined in AF.h afDataConfirm = (afDataConfirm_t *)MSGpkt; sentEP = afDataConfirm->endpoint; sentStatus = afDataConfirm->hdr.status; sentTransID = afDataConfirm->transID; (void)sentEP; (void)sentTransID; // Action taken when confirmation is received. if ( sentStatus != ZSuccess ) { // The data wasn't delivered -- Do something } break; case AF_INCOMING_MSG_CMD: FlyApp_MessageMSGCB( MSGpkt ); break; case ZDO_STATE_CHANGE: FlyApp_NwkState = (devStates_t)(MSGpkt->hdr.status); if ( (FlyApp_NwkState == DEV_ZB_COORD) || (FlyApp_NwkState == DEV_ROUTER) || (FlyApp_NwkState == DEV_END_DEVICE) ) { // Start sending "the" message in a regular interval. osal_start_timerEx( FlyApp_TaskID, FLYAPP_SEND_MSG_EVT, FLYAPP_SEND_MSG_TIMEOUT ); } break; default: break; }
  66. 66. 66 // Release the memory osal_msg_deallocate( (uint8 *)MSGpkt ); // Next MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( FlyApp_TaskID ); } // while ( MSGpkt ) // return unprocessed events return (events ^ SYS_EVENT_MSG); } // Send a message out - This event is generated by a timer // (setup in FlyApp_Init()). if ( events & FLYAPP_SEND_MSG_EVT ) { // Send "the" message FlyApp_SendTheMessage(); // Setup to send message again osal_start_timerEx( FlyApp_TaskID, FLYAPP_SEND_MSG_EVT, FLYAPP_SEND_MSG_TIMEOUT ); // return unprocessed events return (events ^ FLYAPP_SEND_MSG_EVT); } // Discard unknown events return 0; }
  67. 67. 67 static void FlyApp_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg ) { switch ( inMsg->clusterID ) { case End_Device_Bind_rsp: if ( ZDO_ParseBindRsp( inMsg ) == ZSuccess ) { // Light LED HalLedSet( HAL_LED_4, HAL_LED_MODE_ON ); } #if defined( BLINK_LEDS ) else { // Flash LED to show failure HalLedSet ( HAL_LED_4, HAL_LED_MODE_FLASH ); } #endif break; case Match_Desc_rsp: ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg ); if ( pRsp ) { if ( pRsp->status == ZSuccess && pRsp->cnt ) { FlyApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit; FlyApp_DstAddr.addr.shortAddr = pRsp->nwkAddr; // Take the first endpoint, Can be changed to search through endpoints FlyApp_DstAddr.endPoint = pRsp->epList[0]; // Light LED HalLedSet( HAL_LED_4, HAL_LED_MODE_ON ); } osal_mem_free( pRsp ); } break; } }
  68. 68. 68 static void FlyApp_HandleKeys( uint8 shift, uint8 keys ) { zAddrType_t dstAddr; // Shift is used to make each button/switch dual purpose. if ( shift ) { ... 略 ... } else { if ( keys & HAL_KEY_SW_1 ) { // Since SW1 isn't used for anything else in this application... #if defined( SWITCH1_BIND ) // we can use SW1 to simulate SW2 for devices that only have one switch, keys |= HAL_KEY_SW_2; #elif defined( SWITCH1_MATCH ) // or use SW1 to simulate SW4 for devices that only have one switch keys |= HAL_KEY_SW_4; #endif } if ( keys & HAL_KEY_SW_2 ) { HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); // Initiate an End Device Bind Request for the mandatory endpoint dstAddr.addrMode = Addr16Bit; dstAddr.addr.shortAddr = 0x0000; // Coordinator ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(), FlyApp_epDesc.endPoint, FLYAPP_PROFID, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FALSE ); }
  69. 69. 69 if ( keys & HAL_KEY_SW_3 ) { } if ( keys & HAL_KEY_SW_4 ) { HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); // Initiate a Match Description Request (Service Discovery) dstAddr.addrMode = AddrBroadcast; dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR; ZDP_MatchDescReq( &dstAddr, NWK_BROADCAST_SHORTADDR, FLYAPP_PROFID, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FALSE ); } } }
  70. 70. 70 /********************************************************************* * @fn FlyApp_MessageMSGCB * @brief Data message processor callback. This function processes * any incoming data - probably from other devices. So, based * on cluster ID, perform the intended action. * @param none * @return none */ static void FlyApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { switch ( pkt->clusterId ) { case FLYAPP_CLUSTERID: // "the" message #if defined( LCD_SUPPORTED ) HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" ); #elif defined( WIN32 ) WPRINTSTR( pkt->cmd.Data ); #endif break; } }
  71. 71. 71 /********************************************************************* * @fn FlyApp_SendTheMessage * @brief Send "the" message. * @param none * @return none */ static void FlyApp_SendTheMessage( void ) { char theMessageData[] = "Hello World"; if ( AF_DataRequest( &FlyApp_DstAddr, &FlyApp_epDesc, FLYAPP_CLUSTERID, (byte)osal_strlen( theMessageData ) + 1, (byte *)&theMessageData, &FlyApp_TransID, AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) { // Successfully requested to be sent. } else { // Error occurred in request to send. } }
  72. 72. 72 燒錄Coord, Router, 與 ED  燒錄Coordinator、Router與End Device
  73. 73. 73  打開Coordinator,以Sniffer觀察封包 Coordinator會先掃描通道。因此時其他裝置都還沒開啟,LCD上會顯示 Energy Level Scan Failed 。 預 設 掃 描 的 energy level 可 以 透 過 參 數 ZDNWKMGR_ACCEPTABLE_ENERGY_LEVEL來設定。在Sniffer記下主動掃描 期間的beacon request,Coordinator應該把自己的地址assign為0x0000。  打開Router 開啟Router後,Coordinator會指定一個short address給Router,請在Sniffer 中找出這個短地址為何(可觀察解析封包中的association欄位)。
  74. 74. 74  打開End Device 這是第一個加入網路的End Device,Coordinator會指定一個short address 給End Device,請在Sniffer中找出這個短地址為何。  綁定(Binding) 兩種綁定方式:按下SW2為手動綁定,按下SW4是自動綁定。 在綁定之後,裝置就會開始傳一些東西。 8-1: 按下End Device的SW4(自動綁定),若綁定成功LED1就會亮起。 8-2: End Device同時被綁至Router跟Coordinator,而且每5秒會傳送「Hello World」給對方。 Coord跟Router的LCD會出現Hello World。 8-3: 使用自動綁定時因為End Device只能夠存一個目的地址,因為 Router可能是最後回應的一 個,因此End Device傳出去的訊息終點是跑到Router (也可能會發生Coordinator最後回應, 所以End Device的訊息的終點會跑到Coordinator去)。
  75. 75. 程式碼探索:擴充FlyApp
  76. 76. 76 程式碼探索  維持f8wConfig.cfg中channel跟PAN ID設定,並確認End Device的Polling是關閉的  先trace一下FlyApp.c應用程式的邏輯 開啟檔案FlyApp.h,找到以下參數之定義 FLYAPP_ENDPOINT = FLYAPP_PROFID = FLYAPP_DEVICEID = FLYAPP_CLUSTERID = 如果各應用之間要能溝通,那麼這些ID的設定要相同才行(實際上, profile ID是由ZigBee聯盟提供)。  打開FlyApp.c 在 GLOBAL VARIABLES 區 塊 中 有 FlyApp_ClusterList 以 及 FlyApp Simple Descriptor (FlyApp_SimpleDesc) 的結構定義。
  77. 77. 77  FlyApp.c應用程式中的主要函式 兩個API (開放由OSAL調用): FlyApp_Init() – 由OSAL_FlyApp.c 的osalInitTasks()調用 FlyApp_ProcessEvent() – 由OSAL調用 四個local函數 (宣告為static): FlyApp_ProcessZDOMsgs() FlyApp_HandleKeys() FlyApp_MessgaeMSGCB() FlyApp_SendTheMessage()  FlyApp_Init() :  當FlyApp在OSAL_FlyApp.c被初始化時,OSAL會先執行FlyApp_Init()。  定義Endpoint Descriptor結構、向AF層註冊此endpoint的description  向OSAL註冊按鍵callback、負責寫東西到LCD、向ZDO註冊2個綁定類型 的callbacks,以及負責做end device綁定回應與match descriptor回應。  在程式碼中,LCD Write上面的”if defined”敘述則是編譯器preprocessor 的定義,用來引入或引出LCD的驅動程式。
  78. 78. 78 void FlyApp_Init( uint8 task_id ) { FlyApp_TaskID = task_id; FlyApp_NwkState = DEV_INIT; FlyApp_TransID = 0; // 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(). FlyApp_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent; FlyApp_DstAddr.endPoint = 0; FlyApp_DstAddr.addr.shortAddr = 0; // Fill out the endpoint description. FlyApp_epDesc.endPoint = FLYAPP_ENDPOINT; FlyApp_epDesc.task_id = &FlyApp_TaskID; FlyApp_epDesc.simpleDesc = (SimpleDescriptionFormat_t *)&FlyApp_SimpleDesc; FlyApp_epDesc.latencyReq = noLatencyReqs; // Register the endpoint description with the AF afRegister( &FlyApp_epDesc ); // Register for all key events - This app will handle all key events RegisterForKeys( FlyApp_TaskID ); // Update the display #if defined ( LCD_SUPPORTED ) HalLcdWriteString( "FlyApp", HAL_LCD_LINE_1 ); #endif ZDO_RegisterForZDOMsg( FlyApp_TaskID, End_Device_Bind_rsp ); ZDO_RegisterForZDOMsg( FlyApp_TaskID, Match_Desc_rsp ); } 見 ZDProfile.h 手冊 Z-Stack API.pdf
  79. 79. 79  FlyApp_ProcessEvent():  這是FlyApp的run-time部分。當系統事件(SYS_EVENT_MSG)或應用事件 (FLYAPP_SEND_MSG_EVT) 發 生 時 , FlyApp_ProcessEvent() 會 被 系 統 callback,然後透過一段判斷程式來判斷到底是哪一種事件被接收了 (它是事件委派delegating events的實作)。 Message 執行 ZDO callback FlyApp_ProcessZDOMsgs() Key change FlyApp_HandleKeys() AF_DATA_CONFIRM_CMD 傳送訊息的確認 AF_INCOMING_MSG_CMD FlyApp_MessgaeMSGCB() ZDO_STATE_CHANGE 綁定成功後,即開始發送訊息要用的OSAL timer來 觸發事件 FLYAPP_SEND_MSG_EVT (由OSAL的timer過期所觸發),訊息就會被送出去, 然後OSAL timer會被重新設置而持續地每5秒鐘觸 發一次訊息發送事件。
  80. 80. 80 uint16 FlyApp_ProcessEvent( uint8 task_id, uint16 events ) { afIncomingMSGPacket_t *MSGpkt; afDataConfirm_t *afDataConfirm; // Data Confirmation message fields byte sentEP; ZStatus_t sentStatus; byte sentTransID; // This should match the value sent (void)task_id; // Intentionally unreferenced parameter if ( events & SYS_EVENT_MSG ) { MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( FlyApp_TaskID ); while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) { case ZDO_CB_MSG: FlyApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt ); break; case KEY_CHANGE: FlyApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys ); break;    
  81. 81. 81 case AF_DATA_CONFIRM_CMD: // This message is received as a confirmation of a data packet sent. // The status is of ZStatus_t type [defined in ZComDef.h] // The message fields are defined in AF.h afDataConfirm = (afDataConfirm_t *)MSGpkt; sentEP = afDataConfirm->endpoint; sentStatus = afDataConfirm->hdr.status; sentTransID = afDataConfirm->transID; (void)sentEP; (void)sentTransID; HalLedSet ( HAL_LED_1, HAL_LED_MODE_TOGGLE ); // Action taken when confirmation is received. if ( sentStatus != ZSuccess ) { // The data wasn't delivered -- Do something } break; case AF_INCOMING_MSG_CMD: FlyApp_MessageMSGCB( MSGpkt ); break; case ZDO_STATE_CHANGE: FlyApp_NwkState = (devStates_t)(MSGpkt->hdr.status); if ( (FlyApp_NwkState == DEV_ZB_COORD) || (FlyApp_NwkState == DEV_ROUTER) || (FlyApp_NwkState == DEV_END_DEVICE) ) { // Start sending "the" message in a regular interval. osal_start_timerEx( FlyApp_TaskID, FLYAPP_SEND_MSG_EVT, FLYAPP_SEND_MSG_TIMEOUT ); } break; default: break; }  
  82. 82. 82 // Release the memory osal_msg_deallocate( (uint8 *)MSGpkt ); // Next MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( FlyApp_TaskID ); } // return unprocessed events return (events ^ SYS_EVENT_MSG); } // Send a message out - This event is generated by a timer // (setup in FlyApp_Init()). if ( events & FLYAPP_SEND_MSG_EVT ) { // Send "the" message FlyApp_SendTheMessage(); // Setup to send message again osal_start_timerEx( FlyApp_TaskID, FLYAPP_SEND_MSG_EVT, FLYAPP_SEND_MSG_TIMEOUT ); // return unprocessed events return (events ^ FLYAPP_SEND_MSG_EVT); } // Discard unknown events return 0; }
  83. 83. 83  FlyApp_ProcessZDOMsgs()  當ED綁定請求成功時會點亮LED4,若請求失敗則會閃爍LED4。綁定資 訊會自動地被儲存。若是match descriptor request綁定成功,一樣會點 亮LED4並存下綁定資訊。 static void FlyApp_ProcessZDOMsgs( zdoIncomingMsg_t *inMsg ) { switch ( inMsg->clusterID ) { case End_Device_Bind_rsp: if ( ZDO_ParseBindRsp( inMsg ) == ZSuccess ) { // Light LED HalLedSet( HAL_LED_4, HAL_LED_MODE_ON ); } #if defined( BLINK_LEDS ) else { HalLedSet ( HAL_LED_4, HAL_LED_MODE_FLASH ); // Flash LED to show failure } #endif break; case Match_Desc_rsp: { ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg ); if ( pRsp ) { if ( pRsp->status == ZSuccess && pRsp->cnt ) { FlyApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit; FlyApp_DstAddr.addr.shortAddr = pRsp->nwkAddr; // Take the first endpoint, Can be changed to search through endpoints FlyApp_DstAddr.endPoint = pRsp->epList[0]; HalLedSet( HAL_LED_4, HAL_LED_MODE_ON ); // Light LED } osal_mem_free( pRsp ); } } break; } }  API手冊   見上一章p.54 / ZDProfile.h  API手冊
  84. 84. 84  FlyApp_HandleKeys()  若SW2被按下: 發出end device bind request (手動綁定)。  若SW4被按下: 發出match descriptor request (自動綁定)。 static void FlyApp_HandleKeys( uint8 shift, uint8 keys ) { zAddrType_t dstAddr; if ( shift ) { // Shift is used to make each button/switch dual purpose. 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 ) { // Since SW1 isn't used for anything else in this application... #if defined( SWITCH1_BIND ) // we can use SW1 to simulate SW2 for devices that only have one switch, keys |= HAL_KEY_SW_2; #elif defined( SWITCH1_MATCH ) // or use SW1 to simulate SW4 for devices that only have one switch keys |= HAL_KEY_SW_4; #endif }
  85. 85. 85 if ( keys & HAL_KEY_SW_2 ) { HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); // Initiate an End Device Bind Request for the mandatory endpoint dstAddr.addrMode = Addr16Bit; dstAddr.addr.shortAddr = 0x0000; // Coordinator ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(), FlyApp_epDesc.endPoint, FLYAPP_PROFID, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FALSE ); } if ( keys & HAL_KEY_SW_3 ) { } if ( keys & HAL_KEY_SW_4 ) { HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); // Initiate a Match Description Request (Service Discovery) dstAddr.addrMode = AddrBroadcast; dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR; ZDP_MatchDescReq( &dstAddr, NWK_BROADCAST_SHORTADDR, FLYAPP_PROFID, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FLYAPP_MAX_CLUSTERS, (cId_t *)FlyApp_ClusterList, FALSE ); } } }  API手冊  API手冊  API手冊 afStatus_t ZDP_EndDeviceBindReq( ... { ... // LocalCoordinator + SrcExtAddr + ep + ProfileID + NumInClusters + NumOutClusters. len = 2 + Z_EXTADDR_LEN + 1 + 2 + 1 + 1; len += (NumInClusters + NumOutClusters) * sizeof ( uint16 );
  86. 86. 86  FlyApp_MessgaeMSGCB()  分析收進來的訊息,並顯示於LCD上。 static void FlyApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { switch ( pkt->clusterId ) { case FLYAPP_CLUSTERID: // "the" message #if defined( LCD_SUPPORTED ) HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" ); #elif defined( WIN32 ) WPRINTSTR( pkt->cmd.Data ); #endif break; } }
  87. 87. 87  FlyApp_SendTheMessage()  建構訊息並以無線發送出(透過AF_DataRequest)字串”Hello World”。 static void FlyApp_SendTheMessage( void ) { char theMessageData[] = "Hello World"; if ( AF_DataRequest( &FlyApp_DstAddr, &FlyApp_epDesc, FLYAPP_CLUSTERID, (byte)osal_strlen( theMessageData ) + 1, (byte *)&theMessageData, &FlyApp_TransID, AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) { // Successfully requested to be sent. HalLedSet ( HAL_LED_2, HAL_LED_MODE_TOGGLE ); } else { // Error occurred in request to send. } }  API手冊 #include char *s1 = "ABCDE" ; char s2[] = "ABCDE" ; [結果] strlen(s1) = 5 strlen(s2) = 5 [說明] strlen():回傳字串長度(“不”含哨兵字元) [比較] sizeof(s2) = 6 (“有”包括哨兵字元) sizeof(s1) = 1 (sizeof 回傳的是指標的大小為1 byte) 另外, strlen()會讀到第一個哨兵字元為止 也就是ascii-code十六進位值00 所以萬一你想要讓動態產生的字元陣列可以讀取長度的話 請務必在最後面加一個0
  88. 88. 88  Build FlyApp並下載到各塊板子 (上一個實驗已build好) 打開Sniffer然後依照Coord、Router與ED的順序開啟電源。在Sniffer中觀察 相關的association process。 在End Device按下SW4(自動綁定),綁定成功後LED4會點亮。然後End Device每5秒鐘會發送”Hello World”字串給其他板子。因為我們是用自動 綁定,所以只會看見到router的流量(或只有到Coord)。 Match_Desc_req
  89. 89. 89 練習:修改LCD顯示訊息  除了顯示收到的Hello World字串外,如果能讓LCD顯示 收到訊息的次數就更好了  檢視FlyApp.c中的FlyApp_MessageMSGCB()  在FlyApp.c的區域變數區塊加入變數count  在HalLcdWriteScreen()之下加入以下程式碼 /***** LOCAL VARIABLES *****/ uint16 count = 0; static void FlyApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { ... HalLcdWriteScreen( (char*)pkt->cmd.Data, "rcvd" ); count++; HalLcdWriteValue( count, 10, HAL_LCD_LINE_3 ); ... }
  90. 90. 90 練習:增加App-level ACK  我們實驗中,裝置都擺的很靠近,但是在真實應用時, 訊息從一個裝置傳送到另外一個裝置可能要經過好幾 個hop才會送達。MAC層的確認訊息(ACK)只會指示第1 個hop是否成功,沒辦法指示訊息是否正確送到好幾個 hops外的另一個裝置。因此,你可能會需要使用應用層 的確認(ACK),來處理這件事情。  檢視FlyApp.c的FlyApp_SendTheMessage()  修改AF_DataRequest()的option中倒數第二個引數 AF.h定義了options參數的bitmaps,務必要確認當你加入AF_ACK_REQUEST option時,不要消除掉已經存在的AF_DISCV_ROUTE option (所以用OR的)。 if ( AF_DataRequest( &FlyApp_DstAddr, &FlyApp_epDesc, FLYCAPP_CLUSTERID, (byte)osal_strlen( theMessageData ) + 1, (byte *)&theMessageData, &FlyApp_TransID, AF_ACK_REQUEST | AF_DISCV_ROUTE, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
  91. 91. 91 以Sniffer觀察
  92. 92. Cluster與Binding 範例應用程式:FlyApp
  93. 93. 93 目標  改變Router傳送”Hello World”的週期 (目前為5秒發一次)  新增一個控制發送週期的cluster (命令)  使用中央綁定來完成 (centralized binding)  ZigBee裝置的「網路探索」與「綁定功能」是由兩個跟 使用者App無關的東西來管理:AF與ZDO 為了瞭解裝置是如何探索網路與其他裝置進行溝通,我們必須要 了解AF跟ZDO是如何一起工作來完成綁定。 Coord Router “Hello World” ED ED發訊號以控制Router發送速度 自動綁定
  94. 94. 94 AF與ZDO  Application Framework (AF)  AF主要是在處理收進來的訊息,並將訊息進行解多工送到相對應的 endpoint(或app)。  使用者的App在初始化之時,一定要向AF註冊,這樣子AF才知道要將收進 來的訊息分派給誰。  AF所需知道的裝置資訊,通通都塞在Endpoint Descriptor這個資料結構裡面。  ZigBee Device Object (ZDO)  ZDO是一個必要的應用元件,它負責建立、探索、加入網路以及如何綁定 與安全性管理等。  ZDO一定屬於Endpoint 0。ZDO降低了客戶需要自行開發網路管理的程式, 但是在產品開發的過程中,也許有一些ZDO的功能是需要做一些修改的, 但除非必要最好不要亂改。
  95. 95. 95 Clusters (I)  Clusters由一個16-bits ID定義,它們是application objects。  cluster定義了application的意義(用途)。  Clusters將指令(commands)與資料(data)、屬性(attribute) 封裝在一起。  指令引起action,屬性追蹤cluster的狀態。  Clusters只有在某個特定profile中才有意義。  Cluster除了做identifier以外,他還有方向性。SimpleDescriptor 描述cluster時又拆分成input跟output兩種list。 Department of Electronic Engineering, NTUT
  96. 96. 96 Clusters (II)  Commands: Public profiles使用ZCL,可透過通用指令組, 使「獲得(get)」或「設定(set)」屬性變得簡單。  Attributes: 屬性是一個16-bits的數字,它可以是0x0000 到 0xFFFF 之 間 的 任 何 值 。 在 ZCL 中 , 它 們 傾 向 於 從 0x0000開始編起。  我們的範例使用private profile,沒有用到ZCL。在真正 碰到ZCL之前,我們先把cluster視為是單純的”命令集” 即可。 EP_X EP_Y Cluster List (命令集) Cluster List (命令集) OUT IN
  97. 97. 97 實驗過程  FlyApp_HandleKeys()函式使用了ZDO綁定函式:  自 動 綁 定 是 透 過 呼 叫 ZDP_MatchDescReq() 函 數 來 初 始 化 , 你 要 將 Endpoint Descriptor作為輸入餵給這個函數。初始化完畢後,自動尋找 機制會使用ZigBee定義好的網路探索機制對不同的節點找出匹配 clusters的應用元件。  自動綁定的情況下,對綁定請求的回應會使用ZDO_STATE_CHANGE事件 傳回給應用,它提供給應用通訊時所需要的資訊。  先開啟Coord 跟Router,然後在Router 按下SW4先跟 Coord綁在一起。若成功,Router就會每5秒傳一次Hello World給Coord。  在 Router 新 增 一 個 control cluster 並 使 用 centralized binding來跟一個ED綁在一起。ED也有相同的control cluster來控制Router的訊息發送週期。
  98. 98. 98 改變Reporting的週期  FlyApp原本的程式是ED會週期性地發資料給Router跟 Coord,這個週期是在編譯的時候設定的。  現在我們要加入程式來允許ED (remote)可以動態地設定 Router (sensor)的reporting時間。為了要達成這個目的, 我們要先了解一種新的訊息類性,稱為Cluster。  除了第一種綁定的data clusters (RouterCoord與ED Coord)機制,我們還會加入第二種綁定機制—使用 Centralized binding。我們會自創configuration cluster, 讓 ED 來 控 制 Router 的 reporting 速 度 (End Device  Router)。
  99. 99. 99  修改FlyApp.h 將 更改成  修改FlyApp.c,加入local變數SendMsgTimeout  在FlyApp_Init()起始處,將此變數初始化為預設值  更新FlyApp_ProcessEvent()的expired timer(有兩處) // Send Message Timeout #define FLYAPP_SEND_MSG_TIMEOUT 5000 // Every 5 seconds #define FLYAPP_SEND_MSG_TIMEOUT_DEFAULT 5000 /***** LOCAL VARIABLES *****/ uint16 SendMsgTimeout; void FlyApp_Init( uint8 task_id ) { SendMsgTimeout = FLYAPP_SEND_MSG_TIMEOUT_DEFAULT; FlyApp_TaskID = task_id; osal_start_timerEx( FlyApp_TaskID, FLYAPP_SEND_MSG_EVT, FLYAPP_SEND_MSG_TIMEOUT ); osal_start_timerEx( FlyApp_TaskID, FLYAPP_SEND_MSG_EVT, SendMsgTimeout );
  100. 100. 100  Build並下載至各板子後,使用自動綁定 開啟Sniffer,然後依序打開Coord、Router,等到Router綠色LED亮起之後 再打開ED。按下ED SW4進行自動綁定,綁定成功後你可以暫停Packet Sniffer然後觀察APS payload。 裝置執行綁定時會呼叫自動尋找函數並初始化Match Descriptor Request 的傳輸。這個Match Descriptor Request由網內所有Cluster匹配裝置在 Match Descriptor Response單播回Requester。要分析Match Descriptor Request的APS payload會有點困難,因為它在Sniffer裡面並沒有依照field被 拆開,所以要自己根據fields定義來看 Match Descriptor Request – APS Payload 2 bytes – Transaction ID (incremented automatically) 2 bytes – Destination Address (0xFFFF – broadcast) 2 bytes – Profile ID (0x0F04 – found in FlyApp.h) 1 byte – Number of input Clusters 2 byte array – Input Cluster List [] 1 byte – Number of output Clusters 2 byte array – Output Cluster List[] Match Descriptor Response – APS Payload 2 bytes – Transaction ID (incremented automatically) 1 byte – Status 2 bytes – Sender Network Address 1 byte – Match counter (Number of matches) 1 byte array[] – Endpoint ID of match
  101. 101. 101  加入Control Cluster:修改FlyApp.h 在App裡加入第2個Endpoint(控制訊息)。第2個Endpoint的目標是管理一個 新的Cluster,用來控制Router中send timer的timeout period。 我們會在同一個FlyApp的.c與.h檔來實現這個新的Endpoint (實際上最好是一個ep對一個App)。 雖然也可以用不同的檔案去隔離不同的Endpoints,或是在原本舊的Endpoint裡實現data Cluster 與control Cluster,不過我們還是以建立一個新的Endpoint來管理control Cluster。除了可以簡化 整個邏輯之外,也可以利用現有ZDO支援函數而不需要做大幅度的程式修改。 在FlyApp.h增加要用來更新sender report period的新Endpoint ID跟新Cluster, 我們稱其為FLYAPP_TIMEOUT_CLUSTER。在FlyApp.h下增加下列定義 /********************************************************************* * CONSTANTS */ ... #define FLYAPP_CTRL_ENDPOINT 11 #define FLYAPP_MAX_CTRL_CLUSTERS 1 #define FLYAPP_TIMEOUT_CLUSTER 8
  102. 102. 102  在FlyApp.c的全域變數區段增加cluster list 增加的cluster list等一下在本地建立綁定表時會用到。 /********************************************************************* * GLOBAL VARIABLES */ // This list is for our new timeout control cluster const cId_t FlyApp_ClusterCtrlList[FLYAPP_MAX_CTRL_CLUSTERS] = { FLYAPP_TIMEOUT_CLUSTER };
  103. 103. 103  新增Descriptor 新增第2個Simple Descriptor跟Endpoint Descriptor以便跟舊的data cluster做 區分。新的cluster我們也會向AF註冊,然後在綁定時用到。 // Simple Descriptor for Control Clusters const SimpleDescriptionFormat_t FlyApp_SimpleCtrlDesc = { FLYAPP_CTRL_ENDPOINT, // int Endpoint; FLYAPP_PROFID, // uint16 AppProfId[2]; FLYAPP_DEVICEID, // uint16 AppDeviceId[2]; FLYAPP_DEVICE_VERSION, // int AppDevVer:4; FLYAPP_FLAGS, // int AppFlags:4; #if !defined(COORDINATOR) // Router and End Device 0, // no input clusters (cId_t *) NULL, FLYAPP_MAX_CTRL_CLUSTERS, // byte AppNumOutClusters; (cId_t *)FlyApp_ClusterCtrlList, // byte *pAppOutClusterList; #endif #if defined(COORDINATOR) // Coordinator FLYAPP_MAX_CTRL_CLUSTERS, // byte AppNumInClusters; (cId_t *)FlyApp_ClusterCtrlList, // byte *pAppInClusterList; 0, // no output clusters (cId_t *) NULL, #endif }; // Endpoint Descriptor for Control Clusters #if !defined(COORDINATOR) endPointDesc_t FlyApp_ctrlEpDesc; #endif
  104. 104. 104  加入Preprocessor定義 分 別 打 開 Coordinator 、 Router 跟 End Device 的 Project Options 為 每 個 Workspace進行組態,在C/C++ compiler選單下面點選Preprocessor頁籤。 在Defined symbols list新增「COORDINATOR」到Coordinator’s options、新 增「ROUTER」到Router options以及新增「ENDDEVICE」到End Device options。  初始化並向AF註冊新的Descriptor 在FlyApp.c的FlyApp_Init()最後加入 #if !defined(COORDINATOR) FlyApp_ctrlEpDesc.endPoint = FLYAPP_CTRL_ENDPOINT; FlyApp_ctrlEpDesc.task_id = & FlyApp_TaskID; FlyApp_ctrlEpDesc.simpleDesc = (SimpleDescriptionFormat_t *)&FlyApp_SimpleCtrlDesc; FlyApp_ctrlEpDesc.latencyReq = noLatencyReqs; afRegister( & FlyApp_ctrlEpDesc ); #endif
  105. 105. 105  確定編譯不會出錯  目前我們還沒有增加任何新的 binding。  為了觀察binding process,先暫時關掉資料傳輸來簡化Sniffer view,將 ZDO_STATE_CHANGE事件處理中的osal_start_timerEx()呼叫先註釋掉, 這樣就不會見到週期性的traffic。  Build並燒錄Coord、Router跟End Device。在適當的channel跑Sniffer, 然後依序打開Coord、Router以及End Device。在End Device上按下SW4 來執行自動綁定。使用Packet Sniffer來看一下。
  106. 106. 106 使用Centralized Binding (I)  現在我們要使用Centralized binding來將Router跟End Device綁在一 起,讓End Device可以動態控制Router的report period。  此機制是在選擇的裝置按下按鍵後,在規定的timeout period內進 行綁定。  Coord在timeout period內會收集End Device Bind Request messages並 且依據profile ID與cluster ID的匹配來建立綁定表元素。預設的End device binding timeout (APS_DEFAULT_MAXBINDING_TIME)是16秒(定 義在ZGlobals.h),但是可以到f8wConfig.cfg加入設定來修改。  FlyApp裡面End Device Bind的例子程式碼是透過按下SW2來執行的。 FlyApp.c的key handler呼叫ZDProfile.c的ZDP_EndDeviceBindReq(), 他會收集所有App的endpoint資訊並且發送給Coordinator。
  107. 107. 107 使用Centralized Binding (II)  當 Coord 在 timeout 時 間 內 收 到 2 個 matching ED Bind Requests,他會在請求裝置建立source binding entries。  Coord會跑的程序(假設在ZDO ED Bind Requests時有找到 matches): 1) 發送一個ZDO Unbind Request給第一個device。因為End Device Bind是一種 toggle process,所以unbind會先被送出去以移除任何可能存在的bind entry。 2) 等待ZDO Unbind Response,如果response status是ZDP_NO_ENTRY,那麼便發 送一個ZDO Bind Request來使binding entry存入source device。如果response status是ZDP_SUCCESS,則繼續處理第一個裝置的cluster ID。 3) 等待ZDO Bind Response。當收到時,繼續第一個device的其他cluster ID。 4) 當第一個device搞定後,對第二的device做同樣的程序。 5) 當第二個device也搞定後,發送ZDO End Device Bind Response message給第一 還有第二的裝置。
  108. 108. 108  啟用REFLECTOR option。 為了讓上述程序發生,要先開啟REFLECTOR編譯選項啟用local binding storage。在Router以及End Device的workspace options加入這個compile option。到C/C++ Compile選擇Preprocessor頁籤,在Defined Symbols box裡 面加入REFLECTOR。  發送Bind Request 在 FlyApp_HandleKeys() 的 SW1 處 理 程 式 加 入 一 些 敘 述 來 使 用 ZDP_EndDeviceBindReq() (請確認你傳遞了正確的Endpoint ID)執行binding process。 下頁程式碼實現了當SW1在Router跟End Device被按下,完成了新的 control endpoints 之 間 的 centralized binding 。 如 果 binding 成 功 , FlyApp_ProcessZDOMsgs()會點亮LED4來指示這個程序是成功或是失敗的。
  109. 109. 109 static void FlyApp_HandleKeys( uint8 shift, uint8 keys ) { zAddrType_t dstAddr; // Shift is used to make each button/switch dual purpose. if ( shift ) { // 略 } else { if ( keys & HAL_KEY_SW_1 ) { #if !defined(COORDINATOR) HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF ); dstAddr.addrMode = Addr16Bit; dstAddr.addr.shortAddr = 0x0000; // Coordinator ZDP_EndDeviceBindReq( &dstAddr, NLME_GetShortAddr(), FlyApp_ctrlEpDesc.endPoint, FLYAPP_PROFID, FLYAPP_MAX_CTRL_CLUSTERS, (cId_t *)FlyApp_ClusterCtrlList, FLYAPP_MAX_CTRL_CLUSTERS, (cId_t *)FlyApp_ClusterCtrlList, FALSE ); #endif // Since SW1 isn't used for anything else in this application... #if defined( SWITCH1_BIND ) // we can use SW1 to simulate SW2 for devices that only have one switch, keys |= HAL_KEY_SW_2; #elif defined( SWITCH1_MATCH ) // or use SW1 to simulate SW4 for devices that only have one switch keys |= HAL_KEY_SW_4; #endif }
  110. 110. 110  Build然後download。 依照正常的build/load與start-up process開啟。現在觀察,當你在Router按 下SW1時的binding process,然後在16秒內按下ED的SW1。在Sniffer觀察 這個綁定的結果。 註:這個實驗你要先按Router再按ED才能binding。如果你想要改變這個順序,你就要把ED的 periodic polling重新開啟才可以(因為你若先按ED,但是ED之後都不會再去問Parent有沒有訊息 要給它,當然不會bind成功)。 如果你的binding成功,在Router跟ED上的LED4都會點亮。因為我們剛剛 先關掉OSAL timer逾時觸發,所以並不會有任何Hello World訊息會被傳送。  重新啟用週期性的資料傳輸。 將註釋掉的osal_start_timerEx()恢復  重新Build並下載
  111. 111. 111 增加Timeout Cluster的收發程式  接著我們要增加一段Timeout cluster的發送程式,新增 ED 的 control key handler 以 及 增 加 Router 的 Timeout receive程式碼,最後測試驗證之。  現在我們已經在End Device跟Router之間建立了綁定, 我們現在要增加一些code來使用這個綁定關係以發送訊 號給Router,讓他可以調整他的reporting period。注意, 這個程式碼只對End Device 適用。  發送Control Information 要發送資料給一個device需要呼叫AF_DataRequest()函數,因此我們需要 建立第二個local function來發送這個新的訊息。將下面的local function declaration加入FlyApp.c。 /********* LOCAL FUNCTIONS *********/ ...略 static void FlyApp_SendTheMessage( void ); #if defined(ENDDEVICE) static void FlyApp_SendTheControl( uint16 reportPeriod ); #endif
  112. 112. 112  新增FlyApp_SendTheControl()函式 將下列程式FlyApp_SendTheControl()加到FlyApp.c的後面 #if defined(ENDDEVICE) /********************************************************************* * @fn FlyApp_SendTheControl * @brief Send a control message. * @param reportPeriod: timeout of the timer * @return none */ static void FlyApp_SendTheControl( uint16 reportPeriod ) { afAddrType_t addrPlaceholder; addrPlaceholder.addrMode = afAddrNotPresent; if ( AF_DataRequest( &addrPlaceholder, & Flypp_ctrlEpDesc, FLYAPP_TIMEOUT_CLUSTER, (byte)sizeof(reportPeriod), (byte *)&reportPeriod, &FlyApp_TransID, AF_DISCV_ROUTE | AF_ACK_REQUEST, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) { // Successfully requested to be sent. } else { // Error occurred in request to send. } } #endif addrPlaceHolder是destination address的place holder。 在address mode使用afAddrNotPresent讓應用自己 在 binding table 裡 面 根 據 commandId 去 look-up address , 指 向 要 發 送 的 目 地 去 。 然 後 stack software就可以使用那個address來傳送訊息到正確 的destination。
  113. 113. 113  在FlyApp_HandleKeys()增加Key Handler程式 增加SW3的key handler code來發送control information。這會用來切換 Router 1秒或5秒的report period。 static void FlyApp_HandleKeys( uint8 shift, uint8 keys ) { zAddrType_t dstAddr; static volatile uint8 defaultTime=FALSE; ... if ( keys & HAL_KEY_SW_3 ) { #if defined (ENDDEVICE) if(defaultTime) { FlyApp_SendTheControl( 5000 ); } else { FlyApp_SendTheControl( 1000 ); } defaultTime ^= 1; // toggle value #endif }
  114. 114. 114  在FlyApp_MessageMSGCB()更新Router的Timer ED以所設計的timing control message送給Router。我們要在Router上增加 一 些 code 來 處 理 這 個 訊 息 。 App 會 透 過 一 個 system 事 件 (AF_INCOMING_MSG_CMD)來得知incoming message通知。當收到此通知 時,我們的App會呼叫FlyApp_MessageMSGCB()來分析與處理這個訊息。 在這個函數裡,我們檢查incoming packet的ClusterId來判斷我們收到的 message是甚麼類型。 static void FlyApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { switch ( pkt->clusterId ) { case FLYAPP_CLUSTERID: // "the" message ...略 break; #if !defined (COORDINATOR) case FLYAPP_TIMEOUT_CLUSTER: // update timeout variable for outgoing "CLUSTERID" message SendMsgTimeout = *((uint16 *)(pkt->cmd.Data)); break; #endif } }
  115. 115. 115  Build然後下載你的project到這三個devices 驗證一下你的改變能如預期般工作,End Device現在應該具動態調整 Router report time的能力。 1) 開啟Coordinator。 2) 開啟Router。 3) 按下Router的SW4來初始化與Coordinator的自動綁定。Router會開始 送messages給Coordinator,以預設的5秒鐘間隔。 4) 打開End Device。 5) 按下Router的SW1,然後在16秒內按下 End Device的SW1來初始化這 個控制應用的centralized binding。 6) 按下End Device的SW3,這會無線發送control message去改變Router 的reporting period (1或5秒)。現在看Coordinator的LCD,你會看到傳 送速率變化。
  116. 116. 116 App開發總結 (I)  在f8wConfig.cfg設定channel跟PAN ID  DPOLL_RATE=0 (ED)只是實驗用,實際情況是要設定的  綁定方式: SW1與SW2為中央綁定、SW4為自動綁定(實作時就直接參考範例程式碼 的作法),實際上還有另外兩種綁定方法:輔助綁定(assisted binding,第 三方設備輔助)、自設計應用綁定(application binding)。  兩個OSAL會回調的Callback  FlyApp_Init()  由OSAL_FlyApp.c 的osalInitTasks()調用  它的內容主要是在系統初始化時註冊一堆東西(EP, Callbacks等)  FlyApp_ProcessEvent()  由OSAL調用,它是一個事件委派器(delegator)  續下頁
  117. 117. 117 App開發總結 (II)  FlyApp_ProcessEvent()  事件來源: • SYS_EVENT_MSG系統事件 (可在 ZComDef.h 自訂系統事件) • ZDO_CB_MSG (要用ZDO_RegisterForZDOMsg()註冊App,ClusterId在ZDProfile.h) • KEY_CHANGE (要用RegisterForKeys()註冊App,實作在OnBoard.c) • AF_DATA_CONFIRM_CMD (要用afRegister()註冊ep,全域系統訊息定義在ZComDef.h) • AF_INCOMING_MSG_CMD (要用afRegister()註冊ep收OTA,App訊息事件可用Cluster實作) • ZDO_STATE_CHANGE (全域系統訊息定義在ZComDef.h) • 應用自定義的事件 (定義在FlyApp.h) • 以上兩類事件,處理完後return時要把events ^ 掉。  事件會分派給「本地Callback函式」去處理,回調函式內容要自己做  App的OTA訊息事件用「Cluster」實作,FlyApp_MessageMSGCB()也是 delegator,把收到的Cluster再分給自己設計的Callback函式處理  Cluster要掛在Endpoint Descriptor上 (EP要向AF註冊,這樣AF才知道 OTA訊息收到後要流給誰)  發送OTA訊息:調用AF_DataRequest()
  118. 118. 解析SampleApp
  119. 119. 119 SampleApp Source Code  功能: 1. 硬體jumper決定裝置啟動為Coord或Router (P18_9-11=Coord) 2. SW1廣播給Group1 EP,該EP會使LED1閃爍 3. SW2解除Group1  程式碼  OSAL_SampleApp.c  SampleAppHw.h  SampleApp.h  SampleApp.c
  120. 120. 120 SampleApp運行的基本流程 SampleApp_Init() SampleApp.c osalInitTasks() OSAL_SampleApp.c osal_init_system() OSAL.c main() ZMain.c SampleApp_ProcessEvent()
  121. 121. 121 OSAL_SampleApp.c 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 }; const uint8 tasksCnt = sizeof( tasksArr ) / sizeof( tasksArr[0] ); uint16 *tasksEvents; 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 SampleApp_Init( taskID ); }
  122. 122. 122 SampleApp.h /********** CONSTANTS ************/ #define SAMPLEAPP_ENDPOINT 20 #define SAMPLEAPP_PROFID 0x0F08 #define SAMPLEAPP_DEVICEID 0x0001 #define SAMPLEAPP_DEVICE_VERSION 0 #define SAMPLEAPP_FLAGS 0 #define SAMPLEAPP_MAX_CLUSTERS 2 #define SAMPLEAPP_PERIODIC_CLUSTERID 1 #define SAMPLEAPP_FLASH_CLUSTERID 2 // Send Message Timeout #define SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT 5000 // Every 5 seconds // Application Events (OSAL) - These are bit weighted definitions. #define SAMPLEAPP_SEND_PERIODIC_MSG_EVT 0x0001 // Group ID for Flash Command #define SAMPLEAPP_FLASH_GROUP 0x0001 // Flash Command Duration - in milliseconds #define SAMPLEAPP_FLASH_DURATION 1000 /********** FUNCTIONS ***********/ /* Task Initialization for the Generic Application */ extern void SampleApp_Init( uint8 task_id ); /* Task Event Processor for the Generic Application */ extern UINT16 SampleApp_ProcessEvent( uint8 task_id, uint16 events );
  123. 123. 123 SampleApp.c /********** GLOBAL VARIABLES ***********/ // This list should be filled with Application specific Cluster IDs. const cId_t SampleApp_ClusterList[SAMPLEAPP_MAX_CLUSTERS] = { SAMPLEAPP_PERIODIC_CLUSTERID, SAMPLEAPP_FLASH_CLUSTERID }; const SimpleDescriptionFormat_t SampleApp_SimpleDesc = { SAMPLEAPP_ENDPOINT, // int Endpoint; SAMPLEAPP_PROFID, // uint16 AppProfId[2]; SAMPLEAPP_DEVICEID, // uint16 AppDeviceId[2]; SAMPLEAPP_DEVICE_VERSION, // int AppDevVer:4; SAMPLEAPP_FLAGS, // int AppFlags:4; SAMPLEAPP_MAX_CLUSTERS, // uint8 AppNumInClusters; (cId_t *)SampleApp_ClusterList, // uint8 *pAppInClusterList; SAMPLEAPP_MAX_CLUSTERS, // uint8 AppNumOutClusters; (cId_t *)SampleApp_ClusterList // uint8 *pAppOutClusterList; }; // This is the Endpoint/Interface description. It is defined here, but // filled-in in SampleApp_Init(). Another way to go would be to fill // in the structure here and make it a "const" (in code space). The // way it's defined in this sample app it is define in RAM. endPointDesc_t SampleApp_epDesc;
  124. 124. 124 /********************************************************************* * LOCAL VARIABLES */ uint8 SampleApp_TaskID; // Task ID for internal task/event processing // This variable will be received when // SampleApp_Init() is called. devStates_t SampleApp_NwkState; uint8 SampleApp_TransID; // This is the unique message ID (counter) afAddrType_t SampleApp_Periodic_DstAddr; afAddrType_t SampleApp_Flash_DstAddr; aps_Group_t SampleApp_Group; uint8 SampleAppPeriodicCounter = 0; uint8 SampleAppFlashCounter = 0; /********************************************************************* * LOCAL FUNCTIONS */ void SampleApp_HandleKeys( uint8 shift, uint8 keys ); void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pckt ); void SampleApp_SendPeriodicMessage( void ); void SampleApp_SendFlashMessage( uint16 flashTime );  
  125. 125. 125 void SampleApp_Init( uint8 task_id ) { SampleApp_TaskID = task_id; SampleApp_NwkState = DEV_INIT; SampleApp_TransID = 0; #if defined ( BUILD_ALL_DEVICES ) // The "Demo" target: 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 #if defined ( HOLD_AUTO_START ) // HOLD_AUTO_START is a compile option that will suppress 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;    
  126. 126. 126 // Fill out the endpoint description. SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT; SampleApp_epDesc.task_id = &SampleApp_TaskID; SampleApp_epDesc.simpleDesc = (SimpleDescriptionFormat_t *)&SampleApp_SimpleDesc; SampleApp_epDesc.latencyReq = noLatencyReqs; // Register the endpoint description with the AF afRegister( &SampleApp_epDesc ); // 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 ); #if defined ( LCD_SUPPORTED ) HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 ); #endif }  
  127. 127. 127 uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events ) { afIncomingMSGPacket_t *MSGpkt; (void)task_id; // Intentionally unreferenced parameter if ( events & SYS_EVENT_MSG ) { MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID ); while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) { // Received when a key is pressed case KEY_CHANGE: SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys ); break; // Received when a messages is received (OTA) for this endpoint case AF_INCOMING_MSG_CMD: SampleApp_MessageMSGCB( MSGpkt ); break; // Received whenever the device changes state in the network 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;
  128. 128. 128 default: break; } // Release the memory osal_msg_deallocate( (uint8 *)MSGpkt ); // Next - if one is available 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(); // 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; } 
  129. 129. 129 void SampleApp_HandleKeys( uint8 shift, uint8 keys ) { (void)shift; // Intentionally unreferenced parameter if ( keys & HAL_KEY_SW_1 ) { /* This key sends the Flash Command is sent to Group 1. * This device will not receive the Flash Command from this * device (even if it belongs to group 1). */ SampleApp_SendFlashMessage( SAMPLEAPP_FLASH_DURATION ); } if ( keys & HAL_KEY_SW_2 ) { /* The Flash Command is sent to Group 1. * This key toggles this device in and out of group 1. * If this device doesn't belong to group 1, this application * will not receive the Flash command sent to group 1. */ 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 ); } } }  
  130. 130. 130 void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { uint16 flashTime; switch ( pkt->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; } } 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. } }
  131. 131. 131 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. } }
  132. 132. 解析SimpleApp
  133. 133. 133 TI Z-Stack Simple API (SAPI)  簡化版的API (適用private profile) – 更高層的應用框架  精簡過的 API 函式集與 callback events  簡化 stack startup procedure  可使用 Z-Tool 於run-time期間組態stack  SAPI提供以下服務  初始化  組態  網路與服務探索 (device, network及service discovery )  資料傳輸 zb_SystemReset zb_StartRequest zb_ReadConfiguration zb_WriteConfiguration zb_GetDeviceInfo zb_FindDeviceRequest zb_BindDevice zb_AllowBind zb_PermitJoiningRequest zb_SendDataRequest zb_ReceiveDataIndication
  134. 134. 134 裝置的啟動  每個device都有一組組態參數可設定,參數有預設值(寫在程 式中),可透過PC工具或外部MCU來修改參數。  所有device中的“network-specific”組態參數要設為相同 。  每個device中的“device-specific”組態參數可為不同。  使用ZCD_NV_LOGICAL_TYPE指定一個device為Coord,而電池 供電的device則要設為ED。  Coord依照ZCD_NV_CHANLIST參數以掃描通道。  Coord依照ZCD_NV_PANID參數以決定PANID。  Routers跟EDs依照 ZCD_NV_CHANLIST參數以掃描通道。  Routers跟EDs試圖尋找是否有ZCD_NV_PANID參數指定的PAN 存在以加入之。
  135. 135. 135 應用的綁定  一旦binding在source device建立後,app發送data時就不須指 定目標地址。呼叫zb_SendDataRequest()時使用0xFFFE當目標 地址,這會使stack在內部綁定表找出真正的目標地址。  一個binding entry可以指向多個目標地址,stack會自動複製 封包並傳送到每個被綁定的目標地址去。  如果NV_RESTORE編譯選項有啟用,stack會將binding entries 存到NV-ram。如此,裝置重開機後,設定仍然會存在。  組態裝置綁定的兩種機制:  若extended addr已知,可用zb_BindDevice()建binding entry  若extended addr未知,就要用類似按鈕的方式來建立。目標裝置按下 按鈕後調用zb_AllowBindResponse()進入允許綁定狀態,來源裝置按下 按鈕後調用zb_ BindDevice()以嘗試綁定。
  136. 136. 136 使用SAPI建立Private應用  列出app使用到的物理裝置(溫度感測器, 讀取器等),為 每個裝置指定一個16-bits的device_id。  為每個裝置規劃16-bits的command_id (cluster id)。  規劃不同裝置上command_id的方向性。  將以上資訊填入simple descriptor,並為app指定一個 profile id。  在app程式中實現每個物理裝置的操作邏輯、以及綁定。
  137. 137. 137 SimpleApp 功能簡介 (I)  SimpleCollector (Coord/Router) 與 SimpleSensor (ED)  SimpleSensor會將感測到的溫度與自身的電池電量資訊 傳送給SimpleCollector收集。  網路建立流程:  自動組網  Sensor裝置加入網路後,會自動綁定到Collector  Sensor定期發送資訊給Collector,並要求點對點ACK  若Sensor收不到ACK,會移除到該Collector的綁定,並重新搜索網路以 綁到新的Collector (可能是同一個)  Command: SENSOR_REPORT_CMD_ID (sensor端是輸出 /collector端是輸入),此命令訊息夾帶2-bytes的資料, 第1個byte指示感測類型(溫度或電量)、第2個byte是該 感測量的數值。
  138. 138. 138 SimpleApp 功能簡介 (II)  服務發現與綁定:  SimpleSensor 加 入 網 路 後 會 嘗 試 探 索 並 將 自 己 綁 到 SimpleCollector。 若它找到一個以上的collector裝置,會自動選 擇第一個回覆的collector當綁定對象。若找不到則持續找下去。  加 入 網 路 後 , SimpleCollector 要 進 Allow Bind mode 來 接 受 SimpleSensor的 binding requests。此範例以按下SW1開啟Allow Bind模式(LED1亮起);按下S2則關閉 Allow Bind mode與LED1。  封包傳送與接收  成功綁定後,sensor在讀取溫度與電量後會發送REPORT 命令封 包給collector (並加上點對點ACK)。若沒收到ACK,SAPI會以 zb_SendDataConfirm回調通知App;sensor將移除現有綁定並在 網路中重試綁定。  Collector收到sensor封包後,以serial port將資料傳給PC。
  139. 139. 139 sapi.c 重點摘錄 const pTaskEventHandlerFn tasksArr[] = { macEventLoop, nwk_event_loop, Hal_ProcessEvent, #if defined( MT_TASK ) MT_ProcessEvent, #endif APS_event_loop, ZDApp_event_loop, SAPI_ProcessEvent }; endPointDesc_t sapi_epDesc; uint8 sapi_TaskID; UINT16 SAPI_ProcessEvent( byte task_id, UINT16 events ) { ... 略 ... if ( events & SYS_EVENT_MSG ) { while ( pMsg ) { switch ( pMsg->event ) { case ZDO_CB_MSG: SAPI_ProcessZDOMsgs( (zdoIncomingMsg_t *)pMsg ); break; case AF_DATA_CONFIRM_CMD: pDataConfirm = (afDataConfirm_t *) pMsg; SAPI_SendDataConfirm( pDataConfirm->transID, pDataConfirm->hdr.status ); break; case AF_INCOMING_MSG_CMD: pMSGpkt = (afIncomingMSGPacket_t *) pMsg; SAPI_ReceiveDataIndication( pMSGpkt->srcAddr.addr.shortAddr, pMSGpkt->clusterId, pMSGpkt->cmd.DataLength, pMSGpkt->cmd.Data); break; 回調 zb_FindDeviceConfirm()或 zb_BindConfirm() 回調 zb_SendDataConfirm() 回調 zb_ReceiveDataIndication()
  140. 140. 140 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; case ZDO_MATCH_DESC_RSP_SENT: SAPI_AllowBindConfirm( ((ZDO_MatchDescRspSent_t *)pMsg)->nwkAddr ); break; case KEY_CHANGE: #if ( SAPI_CB_FUNC ) zb_HandleKeys( ((keyChange_t *)pMsg)->state, ((keyChange_t *)pMsg)->keys ); #endif break; case SAPICB_DATA_CNF: SAPI_SendDataConfirm( (uint8)((sapi_CbackEvent_t *)pMsg)->data, ((sapi_CbackEvent_t *)pMsg)->hdr.status ); break; case SAPICB_BIND_CNF: SAPI_BindConfirm( ((sapi_CbackEvent_t *)pMsg)->data, ((sapi_CbackEvent_t *)pMsg)->hdr.status ); break; 回調 zb_StartConfirm() 回調 zb_AllowBindConfirm() 回調 zb_HandleKeys() 回調 zb_SendDataConfirm() 回調 zb_BindConfirm()
  141. 141. 141 case SAPICB_START_CNF: SAPI_StartConfirm( ((sapi_CbackEvent_t *)pMsg)->hdr.status ); break; default: // User messages should be handled by user or passed to the application if ( pMsg->event >= ZB_USER_MSG ) { } break; } // Release the memory osal_msg_deallocate( (uint8 *) pMsg ); // Next pMsg = (osal_event_hdr_t *) osal_msg_receive( task_id ); } // Return unprocessed events return (events ^ SYS_EVENT_MSG); } if ( events & ZB_ALLOW_BIND_TIMER ) { afSetMatch(sapi_epDesc.simpleDesc->EndPoint, FALSE); return (events ^ ZB_ALLOW_BIND_TIMER); } if ( events & ZB_BIND_TIMER ) { // Send bind confirm callback to application SAPI_BindConfirm( sapi_bindInProgress, ZB_TIMEOUT ); sapi_bindInProgress = 0xffff; return (events ^ ZB_BIND_TIMER); } 回調 zb_StartConfirm() 回調 zb_BindConfirm()
  142. 142. 142 if ( events & ZB_ENTRY_EVENT ) { uint8 startOptions; // Give indication to application of device startup #if ( SAPI_CB_FUNC ) zb_HandleOsalEvent( ZB_ENTRY_EVENT ); #endif // LED off cancels HOLD_AUTO_START blink set in the stack 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 { // blink leds and wait for external input to config and restart HalLedBlink(HAL_LED_2, 0, 50, 500); } return (events ^ ZB_ENTRY_EVENT ); } // This must be the last event to be processed if ( events & ( ZB_USER_EVENTS ) ) { // User events are passed to the application #if ( SAPI_CB_FUNC ) zb_HandleOsalEvent( events ); #endif // Do not return here, return 0 later } // Discard unknown events return 0; } 回調 zb_HandleOsalEvent() 回調 zb_HandleOsalEvent()
  143. 143. 143 SimpleApp.h /************************************************************************************************** Filename: SimpleApp.h Revised: $Date: 2009-03-18 15:56:27 -0700 (Wed, 18 Mar 2009) $ Revision: $Revision: 19453 $ Description: Sample application utilizing the Simple API. **************************************************************************************************/ #ifndef SIMPLE_APP_H #define SIMPLE_APP_H #define MY_PROFILE_ID 0x0F10 #define MY_ENDPOINT_ID 0x02 // Define devices #define DEV_ID_SWITCH 1 #define DEV_ID_CONTROLLER 2 #define DEV_ID_SENSOR 3 #define DEV_ID_COLLECTOR 4 #define DEVICE_VERSION_SWITCH 1 #define DEVICE_VERSION_CONTROLLER 1 #define DEVICE_VERSION_SENSOR 1 #define DEVICE_VERSION_COLLECTOR 1 // Define the Command ID's used in this application #define TOGGLE_LIGHT_CMD_ID 1 #define SENSOR_REPORT_CMD_ID 2 #endif // SIMPLE_APP_H
  144. 144. 144 SimpleCollector.c /************************************************************************************************** Filename: SimpleCollector.c Description: Sample application utilizing the Simple API. /****** INCLUDES *****/ ... 略 ... #include "sapi.h" #include "SimpleApp.h" /****** CONSTANTS ******/ // Application States #define APP_INIT 0 #define APP_START 1 // Application osal event identifiers #define MY_START_EVT 0x0001 // Same definitions as in SimpleSensor.c #define TEMP_REPORT 0x01 #define BATTERY_REPORT 0x02 /***** LOCAL VARIABLES ******/ static uint8 myAppState = APP_INIT; static uint8 myStartRetryDelay = 10; /***** GLOBAL VARIABLES ******/ // Inputs and Outputs for Collector device #define NUM_OUT_CMD_COLLECTOR 0 #define NUM_IN_CMD_COLLECTOR 1 // List of output and input commands for Collector device const cId_t zb_InCmdList[NUM_IN_CMD_COLLECTOR] = { SENSOR_REPORT_CMD_ID }; // Define SimpleDescriptor for Collector device const SimpleDescriptionFormat_t zb_SimpleDesc = { MY_ENDPOINT_ID, // Endpoint MY_PROFILE_ID, // Profile ID DEV_ID_COLLECTOR, // Device ID DEVICE_VERSION_COLLECTOR, // Device Version 0, // Reserved NUM_IN_CMD_COLLECTOR, // Number of Input Commands (cId_t *) zb_InCmdList, // Input Command List NUM_OUT_CMD_COLLECTOR, // Number of Output Commands (cId_t *) NULL // Output Command List };
  145. 145. 145 /****************************************************************************** * @fn zb_HandleOsalEvent * @brief The zb_HandleOsalEvent function is called by the operating * system when a task event is set */ void zb_HandleOsalEvent( uint16 event ) { } /********************************************************************* * @fn zb_HandleKeys * @brief Handles all key events for this device. void zb_HandleKeys( uint8 shift, uint8 keys ) { uint8 startOptions; uint8 logicalType; ... 略 ... if ( keys & HAL_KEY_SW_1 ) { 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  見sapi.c  
  146. 146. 146 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 { // Turn ON Allow Bind mode indefinitely zb_AllowBind( 0xFF ); HalLedSet( HAL_LED_1, HAL_LED_MODE_ON ); } } if ( keys & HAL_KEY_SW_2 ) { if ( myAppState == APP_INIT ) { // In the init state, keys are used to indicate the logical mode. // Key 2 starts device as a router zb_ReadConfiguration( ZCD_NV_LOGICAL_TYPE, sizeof(uint8), &logicalType ); if ( logicalType != ZG_DEVICETYPE_ENDDEVICE ) { logicalType = ZG_DEVICETYPE_ROUTER; 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 { // Turn OFF Allow Bind mode indefinitely zb_AllowBind( 0x00 ); HalLedSet( HAL_LED_1, HAL_LED_MODE_OFF ); } } ... 略 ... } }  
  147. 147. 147 /****************************************************************************** * @fn zb_StartConfirm * @brief The zb_StartConfirm callback is called by the ZigBee stack * after a start request operation completes 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 ); } } /********************************************************** * @fn zb_SendDataConfirm * @brief The zb_SendDataConfirm callback function * is called by the ZigBee after a send data * operation completes void zb_SendDataConfirm( uint8 handle, uint8 status ) { } /********************************************************** * @fn zb_BindConfirm * @brief The zb_BindConfirm callback is called * by the ZigBee stack after a bind operation * completes. void zb_BindConfirm( uint16 commandId, uint8 status ) { } /**************************************** * @fn zb_AllowBindConfirm * @brief Indicates when another * device attempted to bind * to this device void zb_AllowBindConfirm( uint16 source ) { } /****************************************** * @fn zb_FindDeviceConfirm * @brief The zb_FindDeviceConfirm * callback function is called * by the stack when a find device * operation completes. void zb_FindDeviceConfirm( uint8 searchType, uint8 *searchKey, uint8 *result ) { }
  148. 148. 148 /****************************************************************************** * @fn zb_ReceiveDataIndication * @brief The zb_ReceiveDataIndication callback function is called * asynchronously by the ZigBee stack to notify the application * when data is received from a peer device. * * @param source - The short address of the peer device that sent the data * command - The commandId associated with the data * len - The number of bytes in the pData parameter * pData - The data sent by the peer device CONST uint8 strDevice[] = "Device:0x"; CONST uint8 strTemp[] = "Temp: "; CONST uint8 strBattery[] = "Battery: "; void zb_ReceiveDataIndication( uint16 source, uint16 command, uint16 len, uint8 *pData ) { uint8 buf[32]; uint8 *pBuf; uint8 tmpLen; uint8 sensorReading; if (command == SENSOR_REPORT_CMD_ID) { // Received report from a sensor sensorReading = pData[1]; // If tool available, write to serial port tmpLen = (uint8)osal_strlen( (char*)strDevice ); pBuf = osal_memcpy( buf, strDevice, tmpLen ); _ltoa( source, pBuf, 16 ); pBuf += 4; *pBuf++ = ' ';

×