Nest發布的OpenThread是Thread®網絡協議的開源實現。 Nest已發布OpenThread,以使開發人員可以廣泛使用Nest產品中使用的技術,以加快互聯家庭產品的開發速度。
線程規範定義了用於家庭應用的基於IPv6的可靠,安全和低功耗的無線設備到設備通信協議。 OpenThread實現了所有線程網絡層,包括IPv6、6LoWPAN,具有MAC安全性的IEEE 802.15.4,網狀鏈路建立和網狀路由。
在此Codelab中,您將使用OpenThread API啟動Thread網絡,監視設備角色並對其做出反應,發送UDP消息,以及將這些操作與真實硬件上的按鈕和LED綁定在一起。
您將學到什麼
- 如何對Nordic nRF52840開發板上的按鈕和LED進行編程
- 如何使用常見的OpenThread API和
otInstance
類 - 如何監視和響應OpenThread狀態更改
- 如何將UDP消息發送到線程網絡中的所有設備
- 如何修改Makefile
你需要什麼
硬件:
- 3個Nordic Semiconductor nRF52840開發板
- 3條USB到Micro-USB電纜以連接板
- 具有至少3個USB端口的Linux機器
軟件:
- GNU工具鏈
- Nordic nRF5x命令行工具
- Segger J-Link軟件
- 開放線程
- 吉特
除非另有說明,否則本Codelab的內容均已根據知識共享署名3.0許可進行了許可,而代碼示例均已根據Apache 2.0許可進行了許可。
完成硬件代碼實驗室
在開始此Codelab之前,您應該使用nRF52840開發板和OpenThread Codelab完成構建線程網絡的步驟:
- 詳細介紹構建和刷新所需的所有軟件
- 教您如何構建OpenThread並將其刷新到Nordic nRF52840板上
- 展示線程網絡的基礎
本Codelab中沒有詳細介紹構建OpenThread和刷新板所需的環境-僅介紹了刷新板的基本說明。假定您已經完成了構建線程網絡代碼實驗室。
Linux機器
該Codelab旨在使用基於i386或x86的Linux計算機來刷新所有Thread開發板。所有步驟均在Ubuntu 14.04.5 LTS(Trusty Tahr)上進行了測試。
Nordic Semiconductor nRF52840板
該Codelab使用三個nRF52840 PDK板。
安裝軟件
要構建和刷新OpenThread,您需要安裝SEGGER J-Link,nRF5x命令行工具,ARM GNU工具鍊和各種Linux軟件包。如果您已按照要求完成了“構建線程網絡代碼實驗室”,則將已經安裝了所需的一切。如果沒有,請先完成該Codelab,然後再繼續以確保您可以構建OpenThread並將其刷新到nRF52840開發板。
OpenThread帶有示例應用程序代碼,您可以將其用作此Codelab的起點。
克隆並安裝OpenThread:
$ cd ~ $ git clone --recursive https://github.com/openthread/openthread.git $ cd openthread $ ./bootstrap
OpenThread的公共API位於OpenThread存儲庫中的include/openthread
中。這些API在線程和平台級別提供對各種OpenThread功能的訪問,以供您在應用程序中使用:
- OpenThread實例信息和控件
- 應用程序服務,例如IPv6,UDP和CoAP
- 網絡憑證管理,以及專員和加入者角色
- 邊界路由器管理
- 增強功能,例如兒童監督和卡紙檢測
有關所有OpenThread API的參考信息,請訪問openthread.io/reference 。
使用API
要使用API,請將其頭文件包含在您的一個應用程序文件中。然後調用所需的功能。
例如,OpenThread附帶的CLI示例應用程序使用以下API標頭:
範例/apps/cli/main.c
#include <openthread/config.h> #include <openthread/cli.h> #include <openthread/diag.h> #include <openthread/tasklet.h> #include <openthread/platform/logging.h>
OpenThread實例
otInstance
結構是使用OpenThread API時經常使用的結構。初始化後,此結構表示OpenThread庫的靜態實例,並允許用戶進行OpenThread API調用。
例如,在CLI示例應用程序的main()
函數中初始化OpenThread實例:
示例/apps/cli/main.c
int main(int argc, char *argv[]) { otInstance *instance ... #if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES // Call to query the buffer size (void)otInstanceInit(NULL, &otInstanceBufferLength); // Call to allocate the buffer otInstanceBuffer = (uint8_t *)malloc(otInstanceBufferLength); assert(otInstanceBuffer); // Initialize OpenThread with the buffer instance = otInstanceInit(otInstanceBuffer, &otInstanceBufferLength); #else instance = otInstanceInitSingle(); #endif ... return 0; }
平台特定功能
如果要將平台特定的功能添加到OpenThread隨附的示例應用程序之一,請首先在examples/platforms/openthread-system.h
標頭中使用所有功能的otSys
命名空間聲明它們。然後在特定於平台的源文件中實現它們。通過這種抽象方式,可以將相同的函數標頭用於其他示例平台。
例如,我們將用於掛接到nRF52840按鈕的GPIO函數,並且必須在openthread-system.h
聲明LED。
在您喜歡的文本編輯器中打開examples/platforms/openthread-system.h
文件。
示例/平台/openthread-system.h
行動:添加特定於平台的GPIO函數聲明
將這些函數聲明添加到文件中的任何位置:
/** * Init LED module. * */ void otSysLedInit(void); void otSysLedSet(uint8_t aLed, bool aOn); void otSysLedToggle(uint8_t aLed); /** * A callback will be called when GPIO interrupts occur. * */ typedef void (*otSysButtonCallback)(otInstance *aInstance); void otSysButtonInit(otSysButtonCallback aCallback); void otSysButtonProcess(otInstance *aInstance);
我們將在下一步中實現它們。
請注意, otSysButtonProcess
函數聲明使用otInstance
。這樣,如果需要,應用程序可以在按下按鈕時訪問有關OpenThread實例的信息。這完全取決於您的應用程序的需求。如果在函數的實現中不需要它,則可以使用OpenThread API中的OT_UNUSED_VARIABLE
宏來抑制某些工具鏈未使用變量周圍的構建錯誤。稍後我們將提供示例。
在上一步中,我們遍歷了可用於GPIO的examples/platforms/openthread-system.h
中特定於平台的函數聲明。為了訪問nRF52840開發板上的按鈕和LED,您需要為nRF52840平台實現這些功能。在此代碼中,您將添加以下功能:
- 初始化GPIO引腳和模式
- 控制引腳上的電壓
- 啟用GPIO中斷並註冊回調
在examples/platforms/nrf528xx/src
目錄中,創建一個名為gpio.c
的文件。在這個新文件中,添加以下內容。
示例/平台/nrf528xx/src/gpio.c(新文件)
動作:添加定義
這些定義用作nRF52840特定值和OpenThread應用程序級別使用的變量之間的抽象。
/** * @file * This file implements the system abstraction for GPIO and GPIOTE. * */ #define BUTTON_GPIO_PORT 0x50000300UL #define BUTTON_PIN 11 // button #1 #define GPIO_LOGIC_HI 0 #define GPIO_LOGIC_LOW 1 #define LED_GPIO_PORT 0x50000300UL #define LED_1_PIN 13 // turn on to indicate leader role #define LED_2_PIN 14 // turn on to indicate router role #define LED_3_PIN 15 // turn on to indicate child role #define LED_4_PIN 16 // turn on to indicate UDP receive
有關nRF52840按鈕和LED的更多信息,請參見Nordic Semiconductor信息中心。
動作:添加標題包括
接下來,添加包含GPIO功能所需的標頭。
/* Header for the functions defined here */ #include "openthread-system.h" #include <string.h> /* Header to access an OpenThread instance */ #include <openthread/instance.h> /* Headers for lower-level nRF52840 functions */ #include "platform-nrf5.h" #include "hal/nrf_gpio.h" #include "hal/nrf_gpiote.h" #include "nrfx/drivers/include/nrfx_gpiote.h"
行動:為按鈕1添加回調和中斷功能
接下來添加此代碼。 in_pin1_handler
函數是在初始化按鈕按下功能時註冊的回調(此文件中的更新)。
請注意,此回調如何使用OT_UNUSED_VARIABLE
宏,因為傳遞給in_pin1_handler
的變量實際上並未在函數中使用。
/* Declaring callback function for button 1. */ static otSysButtonCallback sButtonHandler; static bool sButtonPressed; /** * @brief Function to receive interrupt and call back function * set by the application for button 1. * */ static void in_pin1_handler(uint32_t pin, nrf_gpiote_polarity_t action) { OT_UNUSED_VARIABLE(pin); OT_UNUSED_VARIABLE(action); sButtonPressed = true; }
操作:添加一個功能來配置LED
添加此代碼以配置初始化期間所有LED的模式和狀態。
/** * @brief Function for configuring: PIN_IN pin for input, PIN_OUT pin for output, * and configures GPIOTE to give an interrupt on pin change. */ void otSysLedInit(void) { /* Configure GPIO mode: output */ nrf_gpio_cfg_output(LED_1_PIN); nrf_gpio_cfg_output(LED_2_PIN); nrf_gpio_cfg_output(LED_3_PIN); nrf_gpio_cfg_output(LED_4_PIN); /* Clear all output first */ nrf_gpio_pin_write(LED_1_PIN, GPIO_LOGIC_LOW); nrf_gpio_pin_write(LED_2_PIN, GPIO_LOGIC_LOW); nrf_gpio_pin_write(LED_3_PIN, GPIO_LOGIC_LOW); nrf_gpio_pin_write(LED_4_PIN, GPIO_LOGIC_LOW); /* Initialize gpiote for button(s) input. Button event handlers are set in the application (main.c) */ ret_code_t err_code; err_code = nrfx_gpiote_init(); APP_ERROR_CHECK(err_code); }
行動:添加一個功能來設置LED的模式。
設備角色更改時將使用此功能。
/** * @brief Function to set the mode of an LED. */ void otSysLedSet(uint8_t aLed, bool aOn) { switch (aLed) { case 1: nrf_gpio_pin_write(LED_1_PIN, (aOn == GPIO_LOGIC_HI)); break; case 2: nrf_gpio_pin_write(LED_2_PIN, (aOn == GPIO_LOGIC_HI)); break; case 3: nrf_gpio_pin_write(LED_3_PIN, (aOn == GPIO_LOGIC_HI)); break; case 4: nrf_gpio_pin_write(LED_4_PIN, (aOn == GPIO_LOGIC_HI)); break; } }
行動:添加一個功能來切換LED的模式。
當設備收到組播UDP消息時,該功能將用於切換LED4。
/** * @brief Function to toggle the mode of an LED. */ void otSysLedToggle(uint8_t aLed) { switch (aLed) { case 1: nrf_gpio_pin_toggle(LED_1_PIN); break; case 2: nrf_gpio_pin_toggle(LED_2_PIN); break; case 3: nrf_gpio_pin_toggle(LED_3_PIN); break; case 4: nrf_gpio_pin_toggle(LED_4_PIN); break; } }
行動:添加功能來初始化和處理按鈕按下。
第一個功能為按按鈕初始化板,第二個功能在按按鈕1時發送多播UDP消息。
/** * @brief Function to toggle the mode of an LED. */ void otSysButtonInit(otSysButtonCallback aCallback) { nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true); in_config.pull = NRF_GPIO_PIN_PULLUP; ret_code_t err_code; err_code = nrfx_gpiote_in_init(BUTTON_PIN, &in_config, in_pin1_handler); APP_ERROR_CHECK(err_code); sButtonHandler = aCallback; sButtonPressed = false; nrfx_gpiote_in_event_enable(BUTTON_PIN, true); } void otSysButtonProcess(otInstance *aInstance) { if (sButtonPressed) { sButtonPressed = false; sButtonHandler(aInstance); } }
操作:保存並關閉gpio.c
文件。
在我們的應用程序中,我們希望根據設備角色來點亮不同的LED。讓我們跟踪以下角色:領導者,路由器,終端設備。我們可以將它們分配給LED,如下所示:
- LED1 =領導者
- LED2 =路由器
- LED3 =終端設備
要啟用此功能,應用程序需要知道何時更改了設備角色以及如何打開正確的LED作為響應。我們將在第一部分中使用OpenThread實例,在第二部分中使用GPIO平台抽象。
在首選的文本編輯器中打開examples/apps/cli/main.c
文件。
示例/apps/cli/main.c
動作:添加標題包括
在main.c
文件的includes部分中,添加角色更改功能所需的API頭文件。
#include <openthread/instance.h> #include <openthread/thread.h> #include <openthread/thread_ftd.h>
操作:為OpenThread實例狀態更改添加處理程序函數聲明
將此聲明添加到main.c
,在頭文件頭之後和任何#IF
語句之前。此功能將在主應用程序之後定義。
void handleNetifStateChanged(uint32_t aFlags, void *aContext);
操作:為狀態更改處理函數添加回調註冊
在main.c
,將此函數添加到otCliUartInit
調用之後的main()
函數中。此回調註冊告訴OpenThread每當OpenThread實例狀態更改時就調用handleNetifStateChange
函數。
/* Register Thread state change handler */ otSetStateChangedCallback(instance, handleNetifStateChanged, instance);
行動:添加狀態更改實施
在main.c
,在main()
函數之後,實現handleNetifStateChanged
函數。此函數檢查OpenThread實例的OT_CHANGED_THREAD_ROLE
標誌,如果已更改,則根據需要打開/關閉LED。
void handleNetifStateChanged(uint32_t aFlags, void *aContext) { if ((aFlags & OT_CHANGED_THREAD_ROLE) != 0) { otDeviceRole changedRole = otThreadGetDeviceRole(aContext); switch (changedRole) { case OT_DEVICE_ROLE_LEADER: otSysLedSet(1, true); otSysLedSet(2, false); otSysLedSet(3, false); break; case OT_DEVICE_ROLE_ROUTER: otSysLedSet(1, false); otSysLedSet(2, true); otSysLedSet(3, false); break; case OT_DEVICE_ROLE_CHILD: otSysLedSet(1, false); otSysLedSet(2, false); otSysLedSet(3, true); break; case OT_DEVICE_ROLE_DETACHED: case OT_DEVICE_ROLE_DISABLED: /* Clear LED4 if Thread is not enabled. */ otSysLedSet(4, false); break; } } }
在我們的應用程序中,當在一塊板上按下Button1時,我們還希望將UDP消息發送到網絡中的所有其他設備。為了確認收到消息,我們將在其他板上切換LED4作為響應。
要啟用此功能,應用程序需要:
- 啟動時初始化UDP連接
- 能夠將UDP消息發送到網狀本地多播地址
- 處理傳入的UDP消息
- 切換LED4以響應傳入的UDP消息
在首選的文本編輯器中打開examples/apps/cli/main.c
文件。
示例/apps/cli/main.c
動作:添加標題包括
在main.c
文件頂部的包含部分中,添加多播UDP功能所需的API頭文件。
#include <string.h> #include <openthread/message.h> #include <openthread/udp.h> #include "utils/code_utils.h"
code_utils.h
標頭用於otEXPECT
和otEXPECT_ACTION
宏, otEXPECT_ACTION
宏可驗證運行時條件並otEXPECT_ACTION
處理錯誤。
動作:添加定義和常量:
在main.c
文件中的includes部分之後和任何#IF
語句之前,添加特定於UDP的常量並定義:
#define UDP_PORT 1212 static const char UDP_DEST_ADDR[] = "ff03::1"; static const char UDP_PAYLOAD[] = "Hello OpenThread World!";
ff03::1
是網狀本地多播地址。發送到該地址的所有消息都將發送到網絡中的所有全線程設備。有關OpenThread中多播支持的更多信息,請參見openthread.io上的多播。
行動:添加函數聲明
在main.c
文件中,在otTaskletsSignalPending
定義之後和main()
函數之前,添加特定於UDP的函數以及一個表示UDP套接字的靜態變量:
static void initUdp(otInstance *aInstance); static void sendUdp(otInstance *aInstance); static void handleButtonInterrupt(otInstance *aInstance); void handleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo); static otUdpSocket sUdpSocket;
行動:添加調用以初始化GPIO LED和按鈕
在main.c
,將這些函數調用添加到otSetStateChangedCallback
調用之後的main()
函數中。這些函數初始化GPIO和GPIOTE引腳,並設置按鈕處理程序以處理按鈕按下事件。
/* init GPIO LEDs and button */ otSysLedInit(); otSysButtonInit(handleButtonInterrupt);
行動:添加UDP初始化調用
在main.c
,在剛添加的otSysButtonInit
調用之後,將此函數添加到main()
函數中:
initUdp(instance);
此調用可確保在應用程序啟動時初始化UDP套接字。否則,設備將無法發送或接收UDP消息。
動作:添加呼叫以處理GPIO按鈕事件
在main.c
,在while
循環中,在otSysProcessDrivers
調用之後,將此函數調用添加到main()
函數中。此函數在gpio.c
聲明, gpio.c
檢查是否按下了按鈕,如果按下,則調用在上一步中設置的處理程序( handleButtonInterrupt
)。
otSysButtonProcess(instance);
操作:實施按鈕中斷處理程序
在main.c
,添加實施handleButtonInterrupt
後功能handleNetifStateChanged
您在上一步中添加的功能:
/** * Function to handle button push event */ void handleButtonInterrupt(otInstance *aInstance) { sendUdp(aInstance); }
行動:實施UDP初始化
在main.c
,添加實施initUdp
後功能handleButtonInterrupt
剛剛添加的功能:
/** * Initialize UDP socket */ void initUdp(otInstance *aInstance) { otSockAddr listenSockAddr; memset(&sUdpSocket, 0, sizeof(sUdpSocket)); memset(&listenSockAddr, 0, sizeof(listenSockAddr)); listenSockAddr.mPort = UDP_PORT; otUdpOpen(aInstance, &sUdpSocket, handleUdpReceive, aInstance); otUdpBind(&sUdpSocket, &listenSockAddr); }
UDP_PORT
是您先前定義的端口(1212),而OT_NETIF_INTERFACE_ID_THREAD
是線程網絡接口ID。 otUdpOpen
函數打開套接字,並為接收到UDP消息註冊一個回調函數( handleUdpReceive
)。
行動:實施UDP消息傳遞
在main.c
,添加實施sendUdp
後功能initUdp
剛剛添加的功能:
/** * Send a UDP datagram */ void sendUdp(otInstance *aInstance) { otError error = OT_ERROR_NONE; otMessage * message; otMessageInfo messageInfo; otIp6Address destinationAddr; memset(&messageInfo, 0, sizeof(messageInfo)); otIp6AddressFromString(UDP_DEST_ADDR, &destinationAddr); messageInfo.mPeerAddr = destinationAddr; messageInfo.mPeerPort = UDP_PORT; message = otUdpNewMessage(aInstance, NULL); otEXPECT_ACTION(message != NULL, error = OT_ERROR_NO_BUFS); error = otMessageAppend(message, UDP_PAYLOAD, sizeof(UDP_PAYLOAD)); otEXPECT(error == OT_ERROR_NONE); error = otUdpSend(&sUdpSocket, message, &messageInfo); exit: if (error != OT_ERROR_NONE && message != NULL) { otMessageFree(message); } }
請注意otEXPECT
和otEXPECT_ACTION
宏。這些可確保UDP消息有效並且已在緩衝區中正確分配;如果無效,則該函數通過跳轉到exit
塊以釋放緩衝區來正常處理錯誤。
有關用於初始化UDP的功能的更多信息,請參見openthread.io上的《 IPv6和UDP參考》。
行動:實施UDP消息處理
在main.c
,添加實施handleUdpReceive
後功能sendUdp
剛才添加的功能。該功能只需切換LED4。
/** * Function to handle UDP datagrams received on the listening socket */ void handleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo) { OT_UNUSED_VARIABLE(aContext); OT_UNUSED_VARIABLE(aMessage); OT_UNUSED_VARIABLE(aMessageInfo); otSysLedToggle(4); }
為了便於演示,我們希望我們的設備在啟動時立即啟動Thread並加入網絡。為此,我們將使用otOperationalDataset
結構。此結構包含將Thread網絡憑據傳輸到設備所需的所有參數。
使用此結構將覆蓋OpenThread內置的網絡默認設置,以使我們的應用程序更安全,並將網絡中的Thread節點限制為僅運行該應用程序的節點。
再次,在您首選的文本編輯器中打開examples/apps/cli/main.c
文件。
範例/apps/cli/main.c
行動:添加標頭包括
在main.c
文件頂部的include部分中,添加配置線程網絡所需的API頭文件:
#include <openthread/dataset_ftd.h>
操作:添加用於設置網絡配置的功能聲明
將此聲明添加到main.c
,在頭文件頭之後和任何#IF
語句之前。該功能將在主應用程序功能之後定義。
static void setNetworkConfiguration(otInstance *aInstance);
操作:添加網絡配置調用
在main.c
,將此函數調用添加到otSetStateChangedCallback
調用之後的main()
函數中。此功能配置線程網絡數據集。
行動:添加調用以啟用線程網絡接口和堆棧
在main.c
,將這些函數調用添加到otSysButtonInit
調用之後的main()
函數中。此功能配置線程網絡數據集。
/* Start the Thread network interface (CLI cmd > ifconfig up) */ otIp6SetEnabled(instance, true); /* Start the Thread stack (CLI cmd > thread start) */ otThreadSetEnabled(instance, true);
行動:實施線程網絡配置
在main.c
,在main()
函數之後添加setNetworkConfiguration
函數的實現:
/** * Override default network settings, such as panid, so the devices can join a network */ void setNetworkConfiguration(otInstance *aInstance) { static char aNetworkName[] = "OTCodelab"; otOperationalDataset aDataset; memset(&aDataset, 0, sizeof(otOperationalDataset)); /* * Fields that can be configured in otOperationDataset to override defaults: * Network Name, Mesh Local Prefix, Extended PAN ID, PAN ID, Delay Timer, * Channel, Channel Mask Page 0, Network Master Key, PSKc, Security Policy */ aDataset.mActiveTimestamp = 1; aDataset.mComponents.mIsActiveTimestampPresent = true; /* Set Channel to 15 */ aDataset.mChannel = 15; aDataset.mComponents.mIsChannelPresent = true; /* Set Pan ID to 2222 */ aDataset.mPanId = (otPanId)0x2222; aDataset.mComponents.mIsPanIdPresent = true; /* Set Extended Pan ID to C0DE1AB5C0DE1AB5 */ uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xC0, 0xDE, 0x1A, 0xB5, 0xC0, 0xDE, 0x1A, 0xB5}; memcpy(aDataset.mExtendedPanId.m8, extPanId, sizeof(aDataset.mExtendedPanId)); aDataset.mComponents.mIsExtendedPanIdPresent = true; /* Set master key to 1234C0DE1AB51234C0DE1AB51234C0DE */ uint8_t key[OT_MASTER_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5}; memcpy(aDataset.mMasterKey.m8, key, sizeof(aDataset.mMasterKey)); aDataset.mComponents.mIsMasterKeyPresent = true; /* Set Network Name to OTCodelab */ size_t length = strlen(aNetworkName); assert(length <= OT_NETWORK_NAME_MAX_SIZE); memcpy(aDataset.mNetworkName.m8, aNetworkName, length); aDataset.mComponents.mIsNetworkNamePresent = true; #if OPENTHREAD_FTD otDatasetSetActive(aInstance, &aDataset); /* Set the router selection jitter to override the 2 minute default. CLI cmd > routerselectionjitter 20 Warning: For demo purposes only - not to be used in a real product */ uint8_t jitterValue = 20; otThreadSetRouterSelectionJitter(aInstance, jitterValue); #else OT_UNUSED_VARIABLE(aInstance); #endif }
如功能中所詳述,我們為此應用程序使用的Thread網絡參數為:
- 頻道= 15
- PAN ID = 0x2222
- 擴展PAN ID = C0DE1AB5C0DE1AB5
- 網絡主密鑰= 1234C0DE1AB51234C0DE1AB51234C0DE
- 網絡名稱= OTCodelab
此外,這是我們減少路由器選擇抖動的地方,因此我們的設備可以更快地更改角色以進行演示。請注意,僅當節點是FTD(全線程設備)時才執行此操作。下一步將對此進行更多介紹。
一些OpenThread的API會修改只能用於演示或測試目的的設置。在使用OpenThread的應用程序的生產部署中,不應使用這些API。
例如, otThreadSetRouterSelectionJitter
函數調整終端設備將其自身提升到路由器所花費的時間(以秒為單位)。根據線程規範,此值的默認值為120。為了在此Codelab中易於使用,我們將其更改為20,因此您不必等待很長時間即可等待Thread節點更改角色。
但是,您可能已經註意到在預處理指令中調用了此函數:
#if OPENTHREAD_FTD otDatasetSetActive(aInstance, &aDataset); /* Set the router selection jitter to override the 2 minute default. CLI cmd >routerselectionjitter 20 Warning: For demo purposes only - not to be used in a real product */ uint8_t jitterValue = 20; otThreadSetRouterSelectionJitter(aInstance, jitterValue); #else OT_UNUSED_VARIABLE(aInstance); #endif
這是為了防止將其添加到MTD(最小線程設備)版本中。 MTD設備不會成為路由器,並且MTD構建中不包括對諸如otThreadSetRouterSelectionJitter
類的功能的支持。
您可以通過查看otThreadSetRouterSelectionJitter
函數定義來確認這一點,該函數定義也包含在OPENTHREAD_FTD
的預處理程序指令中:
src /核心/api/thread_ftd_api.cpp
#if OPENTHREAD_FTD #include <openthread/thread_ftd.h> ... void otThreadSetRouterSelectionJitter(otInstance *aInstance, uint8_t aRouterJitter) { Instance &instance = *static_cast<Instance *>(aInstance); instance.GetThreadNetif().GetMle().SetRouterSelectionJitter(aRouterJitter); } ... #endif // OPENTHREAD_FTD
在構建應用程序之前,三個Makefile需要一些小的更新。這些由構建系統用來編譯和鏈接您的應用程序。
示例/apps/cli/Makefile.am
為確保所有OPENTHREAD_FTD
塊都包含在FTD中,請將其添加到CLI Makefile中的CPPFLAGS
中。
行動:添加FTD標誌
在首選的文本編輯器中打開examples/apps/cli/Makefile.am
,並將ot_cli_ftd_CPPFLAGS部分更新為以下內容:
ot_cli_ftd_CPPFLAGS = \ $(CPPFLAGS_COMMON) \ -DOPENTHREAD_FTD=1 \ $(NULL)
示例/ Makefile-nrf52840
現在,向nrf52840 Makefile中添加一些標誌,以確保在應用程序中定義了GPIO功能。
操作:將標誌添加到nrf52840 Makefile
在首選的文本編輯器中打開examples/Makefile-nrf52840
,並在include ... common-switches.mk
行之後添加以下COMMONCFLAGS
:
... include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk # Defined in third_party/NordicSemiconductor/nrfx/templates/nRF52840/nrfx_config.h COMMONCFLAGS += -DGPIOTE_ENABLED=1 COMMONCFLAGS += -DGPIOTE_CONFIG_IRQ_PRIORITY=7 COMMONCFLAGS += -DGPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS=1 ...
示例/平台/nrf528xx/nrf52840/Makefile.am
現在,將新的gpio.c
文件添加到平台nrf52840 Makefile.am
文件中。
操作:將gpio源添加到nrf52840平台Makefile
在首選的文本編輯器中打開examples/platforms/nrf528xx/nrf52840/Makefile.am
,然後在$(NULL)
語句之前的PLATFORM_COMMON_SOURCES
節的末尾添加文件。不要忘了在行尾加反斜杠!
... PLATFORM_COMMON_SOURCES ... src/gpio.c \ $(NULL) ...
third_party / NordicSemiconductor / Makefile.am
最後,將nrfx_gpiote.c
驅動程序文件添加到nrfx_gpiote.c
第三方Makefile中,以便它隨Nordic驅動程序的庫一起提供。
操作:將gpio驅動程序添加到nrf52840平台Makefile
在首選的文本編輯器中打開third_party/NordicSemiconductor/Makefile.am
,然後在$(NULL)
語句之前的NORDICSEMI_COMMON_SOURCES
部分的末尾添加文件。不要忘了在行尾加反斜杠!
... NORDICSEMI_COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c \ $(NULL) ...
完成所有代碼更新後,您就可以構建應用程序並將其刷新到所有三個Nordic nRF52840開發板。每個設備都將充當全線程設備(FTD)。
構建OpenThread
構建nRF52840示例平台。由於在上一步中更改了Makefile,因此您需要在構建之前運行bootstrap
腳本:
$ cd ~/openthread $ ./bootstrap $ make -f examples/Makefile-nrf52840
使用OpenThread FTD CLI二進製文件導航到該目錄,然後使用ARM Embedded Toolchain將其轉換為十六進制格式:
$ cd ~/openthread/output/nrf52840/bin $ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex
刷板
將ot-cli-ftd.hex
文件ot-cli-ftd.hex
到每個nRF52840板上。
將USB電纜連接到nRF52840板上外部電源引腳旁邊的Micro-USB調試端口,然後將其插入Linux機器。正確設置, LED5點亮。
和以前一樣,記下nRF52840板的序列號:
導航到nRFx命令行工具的位置,然後使用板的序列號將OpenThread CLI FTD十六進製文件刷新到nRF52840板上:
$ cd ~/nrfjprog $ ./nrfjprog -f nrf52 -s 683704924 --chiperase --program \ ~/openthread/output/nrf52840/bin/ot-cli-ftd.hex --reset
閃爍期間,LED5將短暫關閉。成功後將生成以下輸出:
Parsing hex file. Erasing user available code and UICR flash areas. Applying system reset. Checking that the area to write is not protected. Programing device. Applying system reset. Run.
對其他兩個板重複此“刷新板”步驟。每個板應該以相同的方式連接到Linux機器,並且刷新的命令是相同的,除了板的序列號。確保在nrfjprog
閃爍命令中使用每個板的唯一序列號。
如果成功,則每塊板上的LED1,LED2或LED3均將點亮。您甚至可能會在閃爍(設備角色更改功能)後不久看到點亮的LED從3切換到2(或2到1)。
現在,所有三個nRF52840板都應該通電並運行我們的OpenThread應用程序。如前所述,此應用程序具有兩個主要功能。
設備角色指示器
每塊板上點亮的LED反映了Thread節點的當前角色:
- LED1 =領導者
- LED2 =路由器
- LED3 =終端設備
隨著角色的變化,點亮的LED也會變化。您應該已經在每台設備加電20秒內在一兩個板上看到了這些變化。
UDP組播
在板上按Button1時,UDP消息將發送到網狀本地多播地址,該地址包括線程網絡中的所有其他節點。響應接收到此消息,所有其他板上的LED4點亮或熄滅。每個板的LED4保持打開或關閉狀態,直到接收到另一個UDP消息為止。
您閃過的設備是一種稱為路由器合格終端設備(REED)的特定類型的全線程設備(FTD)。這意味著它們可以充當路由器或終端設備,並且可以將自己從終端設備升級為路由器。
線程最多可以支持32個路由器,但是嘗試將路由器的數量保持在16到23之間。如果REED作為終端設備連接,並且路由器的數量小於16,它將自動升級為路由器。在您將otThreadSetRouterSelectionJitter
值設置為應用程序的秒數(20秒)內,此更改應在隨機時間發生。
每個線程網絡都有一個領導者,它是一個路由器,負責管理線程網絡中的一組路由器。開啟所有設備後,20秒鐘後,其中一個應成為領導者(LED1點亮),另外兩個應為路由器(LED2點亮)。
刪除領導者
如果將領導者從線程網絡中刪除,則其他路由器會將自己提升為領導者,以確保網絡仍具有領導者。
使用電源開關關閉排行榜(LED1點亮的排行榜)。等待大約20秒鐘。在其餘兩塊板之一中,LED2(路由器)將關閉,LED1(領導者)將打開。該設備現在是線程網絡的領導者。
重新打開原始的排行榜。當終端設備(LED3點亮)時,它應自動重新加入線程網絡。在20秒內(路由器選擇抖動),它會升級為路由器(LED2點亮)。
重置板
關閉所有三塊板,然後再次將它們重新打開並觀察LED。通電的第一個板應以“領導者”角色啟動(LED1點亮)—線程網絡中的第一個路由器自動成為“領導者”。
最初,其他兩塊板作為終端設備(LED3點亮)連接到網絡,但應在20秒內升級到路由器(LED2點亮)。
網絡分區
如果您的主板未獲得足夠的電源,或者它們之間的無線電連接較弱,則Thread網絡可能會分成多個分區,並且您可能有多個設備顯示為Leader。
線程具有自我修復功能,因此分區最終應合併到一個領導者的單個分區中。
如果從上一個練習繼續進行,則LED4不應在任何設備上點亮。
挑選任意一塊木板,然後按Button1。運行應用程序的線程網絡中所有其他板上的LED4應該切換其狀態。如果從上一個練習繼續進行,則現在應該啟用它們。
再次按Button1進入同一塊板。所有其他板上的LED4應該再次切換。
在另一塊板上按Button1,觀察其他板上LED4的切換方式。在當前LED4所在的一塊板上按Button1。該板上的LED4保持點亮,但其他板上的LED點亮。
網絡分區
如果您的電路板已經分區,並且其中有一個以上的Leader,則多播消息的結果在各個電路板之間會有所不同。如果您在已分區的板上按Button1(因此是分區的Thread網絡的唯一成員),則其他板上的LED4將不會點亮。如果發生這種情況,請重置板-理想情況下,它們將改革單個線程網絡,並且UDP消息傳遞應正常工作。
您已經創建了使用OpenThread API的應用程序!
您現在知道:
- 如何對Nordic nRF52840開發板上的按鈕和LED進行編程
- 如何使用常見的OpenThread API和
otInstance
類 - 如何監視和響應OpenThread狀態更改
- 如何將UDP消息發送到線程網絡中的所有設備
- 如何修改Makefile
下一步
在此Codelab的基礎上,嘗試以下練習:
- 修改GPIO模塊以使用GPIO引腳代替板載LED,並連接根據路由器角色更改顏色的外部RGB LED
- 添加對其他示例平台的GPIO支持
- 與其使用多播來通過按鈕ping所有設備,不如使用Router / Leader API查找並ping單個設備
- 使用OpenThread邊界路由器將網狀網絡連接到Internet,並從Thread網絡外部多播它們以點亮LED
進一步閱讀
請訪問openthread.io和GitHub ,以獲取各種OpenThread資源,包括:
- 支持的平台-發現所有支持OpenThread的平台
- 構建OpenThread —有關構建和配置OpenThread的更多詳細信息
- 線程入門—線程概念的重要參考
參考: