Google is committed to advancing racial equity for Black communities. See how.
本頁面由 Cloud Translation API 翻譯而成。
Switch to English

使用OpenThread API開發

26b7f4f6b3ea0700.png

Nest發布的OpenThreadThread®網絡協議的開源實現。 Nest已發布OpenThread,以使開發人員可以廣泛使用Nest產品中使用的技術,以加快互聯家庭產品的開發速度。

線程規範定義了用於家庭應用的基於IPv6的可靠,安全和低功耗的無線設備到設備通信協議。 OpenThread實現了所有線程網絡層,包括IPv6、6LoWPAN,具有MAC安全性的IEEE 802.15.4,網狀鏈路建立和網狀路由。

在此Codelab中,您將使用OpenThread API啟動Thread網絡,監視設備角色並對其做出反應,發送UDP消息,以及將這些操作與真實硬件上的按鈕和LED綁定在一起。

2a6db2e258c32237.png

您將學到什麼

  • 如何對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板

a6693da3ce213856.png

安裝軟件

要構建和刷新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標頭用於otEXPECTotEXPECT_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);
    }
}

請注意otEXPECTotEXPECT_ACTION宏。這些可確保UDP消息有效並且已在緩衝區中正確分配;如果無效,則該函數通過跳轉到exit塊以釋放緩衝區來正常處理錯誤。

有關用於初始化UDP的功能的更多信息,請參見openthread.io上的《 IPv6UDP參考》。

行動:實施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()函數中。此功能配置線程網絡數據集。

第016章

行動:添加調用以啟用線程網絡接口和堆棧

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點亮。

20a3b4b480356447.png

和以前一樣,記下nRF52840板的序列號:

c00d519ebec7e5f0.jpeg

導航到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消息為止。

203dd094acca1f97.png

9bbd96d9b1c63504.png

您閃過的設備是一種稱為路由器合格終端設備(REED)的特定類型的全線程設備(FTD)。這意味著它們可以充當路由器或終端設備,並且可以將自己從終端設備升級為路由器。

線程最多可以支持32個路由器,但是嘗試將路由器的數量保持在16到23之間。如果REED作為終端設備連接,並且路由器的數量小於16,它將自動升級為路由器。在您將otThreadSetRouterSelectionJitter值設置為應用程序的秒數(20秒)內,此更改應在隨機時間發生。

每個線程網絡都有一個領導者,它是一個路由器,負責管理線程網絡中的一組路由器。開啟所有設備後,20秒鐘後,其中一個應成為領導者(LED1點亮),另外兩個應為路由器(LED2點亮)。

4e1e885861a66570.png

刪除領導者

如果將領導者從線程網絡中刪除,則其他路由器會將自己提升為領導者,以確保網絡仍具有領導者。

使用電源開關關閉排行榜(LED1點亮的排行榜)。等待大約20秒鐘。在其餘兩塊板之一中,LED2(路由器)將關閉,LED1(領導者)將打開。該設備現在是線程網絡的領導者。

4c57c87adb40e0e3.png

重新打開原始的排行榜。當終端設備(LED3點亮)時,它應自動重新加入線程網絡。在20秒內(路由器選擇抖動),它會升級為路由器(LED2點亮)。

5f40afca2dcc4b5b.png

重置板

關閉所有三塊板,然後再次將它們重新打開並觀察LED。通電的第一個板應以“領導者”角色啟動(LED1點亮)—線程網絡中的第一個路由器自動成為“領導者”。

最初,其他兩塊板作為終端設備(LED3點亮)連接到網絡,但應在20秒內升級到路由器(LED2點亮)。

網絡分區

如果您的主板未獲得足夠的電源,或者它們之間的無線電連接較弱,則Thread網絡可能會分成多個分區,並且您可能有多個設備顯示為Leader。

線程具有自我修復功能,因此分區最終應合併到一個領導者的單個分區中。

如果從上一個練習繼續進行,則LED4不應在任何設備上點亮。

挑選任意一塊木板,然後按Button1。運行應用程序的線程網絡中所有其他板上的LED4應該切換其狀態。如果從上一個練習繼續進行,則現在應該啟用它們。

f186a2618fdbe3fd.png

再次按Button1進入同一塊板。所有其他板上的LED4應該再次切換。

在另一塊板上按Button1,觀察其他板上LED4的切換方式。在當前LED4所在的一塊板上按Button1。該板上的LED4保持點亮,但其他板上的LED點亮。

f5865ccb8ab7aa34.png

網絡分區

如果您的電路板已經分區,並且其中有一個以上的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.ioGitHub ,以獲取各種OpenThread資源,包括:

參考: