Google は、黒人コミュニティのための人種的公平の促進に取り組んでいます。詳細をご覧ください。

OpenThread API を使用した開発

1. はじめに

26b7f4f6b3ea0700.png

Google Nest がリリースした OpenThread は、Thread® ネットワーク プロトコルのオープンソース実装です。Google Nest は、Google Nest 製品で使用されているテクノロジーをデベロッパーが幅広く利用できるようにするために、OpenThread をリリースしました。これにより、スマートホーム向けの製品開発が加速します。

Thread 仕様は、家庭用アプリケーション向けの IPv6 ベースの信頼性が高く、安全で低電力のワイヤレス デバイス間通信プロトコルを定義しています。OpenThread は、MAC セキュリティ、メッシュリンク確立、メッシュルーティングを備えた IPv6、6LoWPAN、IEEE 802.15.4 を含むすべてのスレッドネットワーキングレイヤを実装します。

この Codelab では、OpenThread API を使用して、Thread ネットワークの起動、デバイスロールの変更のモニタリングと対応、UDP メッセージの送信を行います。これらのアクションは、実際のハードウェアのボタンや LED に関連付けます。

2a6db2e258c32237.png

学習内容

  • Nordic nRF52840 開発ボードでボタンと LED をプログラムする方法
  • 一般的な OpenThread API と otInstance クラスの使い方
  • OpenThread 状態の変化を監視して対応する方法
  • Thread ネットワークのすべてのデバイスに UDP メッセージを送信する方法
  • Makefile の変更方法

必要なもの

ハードウェア:

  • 北欧半導体 nRF52840 開発ボード x 3
  • ボードを接続するための USB - マイクロ USB ケーブル x 3
  • USB ポートが 3 つ以上ある Linux マシン

ソフトウェア:

  • GNU ツールチェーン
  • 北欧の nRF5x コマンドライン ツール
  • Segger J-Link ソフトウェア
  • OpenThread
  • Git

特に断りのない限り、この Codelab のコンテンツはクリエイティブ・コモンズの表示 3.0 ライセンスにより使用許諾され、コードサンプルは Apache 2.0 ライセンスにより使用許諾されます。

2. 始める

ハードウェア Codelab を完了する

この Codelab を開始する前に、nRF52840 ボードと OpenThread を使用してスレッド ネットワークを構築する Codelab を完了する必要があります。

  • ビルドとフラッシュに必要なすべてのソフトウェアの詳細
  • OpenThread を構築し、Nordic nRF52840 ボードにフラッシュする方法を学習
  • Thread ネットワークの基本を示します。

OpenThread をビルドし、ボードをフラッシュするために必要な環境設定については、この Codelab では詳しく説明しません。ボードをフラッシュするための基本的な手順だけです。Codelab は、Thread ネットワークの構築 Codelab をすでに完了していることを前提としています。

Linux マシン

この Codelab は、i386 ベースまたは x86 ベースの Linux マシンですべての Thread 開発ボードをフラッシュできるように設計されています。すべての手順は Ubuntu 14.04.5 LTS(Trusty Tahr)でテストされました。

Nordic Semiconductor nRF52840 ボード

この Codelab では、3 つの nRF52840 PDK ボードを使用します。

a6693da3ce213856.png

ソフトウェアをインストールする

OpenThread をビルドしてフラッシュするには、SEGGER J-Link、nRF5x コマンドライン ツール、ARM GNU ツールチェーン、およびさまざまな Linux パッケージをインストールする必要があります。必要に応じて「Build a Thread Network Codelab」を完了している場合、必要なものはすべて揃っています。そうでない場合は、その Codelab を完了してから、OpenThread をビルドして nRF52840 開発ボードにフラッシュできることを確認してください。

3. リポジトリのクローンを作成する

OpenThread には、この Codelab の出発点として使用できるサンプル アプリケーション コードが含まれています。

OpenThread Nordic nRF528xx サンプル リポジトリのクローンを作成し、OpenThread をビルドします。

$ git clone --recursive https://github.com/openthread/ot-nrf528xx
$ cd ot-nrf528xx
$ ./script/bootstrap

4. OpenThread API の基本

OpenThread の公開 API は、OpenThread リポジトリの ./openthread/include/openthread にあります。これらの API では、アプリケーションで使用するために、スレッドレベルとプラットフォーム レベルの両方でさまざまな OpenThread 機能にアクセスできます。

  • OpenThread インスタンス情報とコントロール
  • IPv6、UDP、CoAP などのアプリケーション サービス
  • ネットワーク認証情報の管理、コミッショナー、Joiner のロール
  • ボーダー ルーターの管理
  • お子様の管理機能や Jam 検出などの拡張機能

すべての OpenThread API のリファレンス情報は、openthread.io/reference で入手できます。

API の使用

API を使用するには、アプリケーション ファイルのいずれかにそのヘッダー ファイルを含めます。次に、目的の関数を呼び出します。

たとえば、OpenThread に含まれる CLI サンプルアプリでは、次の API ヘッダーを使用しています。

./openthread/examples/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 を呼び出せるようになります。

たとえば、OpenThread インスタンスは、CLI サンプルアプリの main() 関数で初期化されます。

./openthread/examples/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 に含まれるサンプル アプリケーションのいずれかにプラットフォーム固有の関数を追加する場合は、最初に ./openthread/examples/platforms/openthread-system.h ヘッダーで、すべての関数に対して otSys 名前空間を使用して宣言します。次に、それらをプラットフォーム固有のソースファイルに実装します。このように抽象化すると、他のサンプル プラットフォームにも同じ関数ヘッダーを使用できます。

たとえば、nRF52840 ボタンと LED の接続に使用する GPIO 関数は、openthread-system.h で宣言する必要があります。

任意のテキスト エディタで ./openthread/examples/platforms/openthread-system.h ファイルを開きます。

./openthread/examples/platforms/openthread-system.h

アクション: プラットフォーム固有の GPIO 関数宣言を追加する

以下の関数宣言を、openthread/instance.h ヘッダーの #include の後に追加します。

/**
 * 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 マクロを使用して、一部のツールチェーンで未使用の変数に関するビルドエラーを抑制できます。この例については、後で説明します。

5. GPIO プラットフォームの抽象化を実装する

前のステップでは、GPIO に使用できる ./openthread/examples/platforms/openthread-system.h のプラットフォーム固有の関数宣言について説明しました。nRF52840 開発ボードでボタンと LED にアクセスするには、nRF52840 プラットフォームにこれらの機能を実装する必要があります。このコードでは、次のような関数を追加します。

  • GPIO のピンとモードを初期化する
  • ピンの電圧を制御する
  • GPIO 割り込みを有効にし、コールバックを登録する

./src/src ディレクトリに、gpio.c という名前の新しいファイルを作成します。この新しいファイルに、以下の内容を追加します。

./src/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 Infocenter を参照してください。

アクション: ヘッダーの内容を追加します。

次に、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 関数は、ボタンが押されたとき(このファイルの後半)で初期化されるときに呼び出されるコールバックです。

in_pin1_handler に渡される変数は実際には関数で使用されていないため、このコールバックでは OT_UNUSED_VARIABLE マクロを使用していることに注意してください。

/* 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 つ目の関数はボタンを押すとボードを初期化し、2 つ目の関数はボタン 1 が押されるとマルチキャスト UDP メッセージを送信します。

/**
 * @brief Function to initialize the button.
 */
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 ファイルを保存して閉じます。

6. API: デバイスロールの変更への対応

このアプリでは、デバイスの役割に応じて異なる LED を点灯させます。ここでは、リーダー、ルーター、エンドデバイスの役割について説明します。次のように LED に割り当てることができます。

  • LED1 = リーダー
  • LED2 = ルーター
  • LED3 = エンドデバイス

この機能を有効にするには、デバイスの役割が変更された日時と、それに応じて正しい LED を点灯する方法をアプリが把握する必要があります。最初の部分では OpenThread インスタンスを使用し、2 番目の部分では GPIO プラットフォーム抽象化を使用します。

任意のテキスト エディタで ./openthread/examples/apps/cli/main.c ファイルを開きます。

./openthread/examples/apps/cli/main.c にアクセスします。

アクション: ヘッダーの内容を追加します。

main.c ファイルの include セクションに、ロール変更機能に必要な 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 で、otAppCliInit 呼び出しの後に main() 関数にこの関数を追加します。このコールバックの登録は、OpenThread インスタンスの状態が変わるたびに handleNetifStateChange 関数を呼び出すように OpenThread に指示します。

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

7. API: マルチキャストを使用して LED をオンにする

今回のアプリケーションでは、1 つのボードで Button1 が押されたときに、ネットワーク内の他のすべてのデバイスに UDP メッセージを送信します。メッセージの受信を確認するため、それに応じて他のボードの LED4 を切り替えます。

この機能を有効にするには、アプリケーションが次の要件を満たしている必要があります。

  • 起動時に UDP 接続を初期化する
  • メッシュのローカル マルチキャスト アドレスに UDP メッセージを送信できるようにする
  • 受信 UDP メッセージを処理する
  • 受信した UDP メッセージに応じて LED4 を切り替えます

任意のテキスト エディタで ./openthread/examples/apps/cli/main.c ファイルを開きます。

./openthread/examples/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 マクロに使用されます。

アクション: 定義と定数を追加する:

main.c ファイルの include セクションの後、#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 で、この関数呼び出しを、otSysProcessDrivers 呼び出しの後、while ループ内の main() 関数に追加します。gpio.c で宣言されたこの関数は、ボタンが押されたかどうかを確認し、押された場合は、前の手順で設定されたハンドラ(handleButtonInterrupt)を呼び出します。

otSysButtonProcess(instance);

アクション: ボタン割り込みハンドラを実装する。

main.c で、前の手順で追加した handleNetifStateChanged 関数の後に handleButtonInterrupt 関数の実装を追加します。

/**
 * Function to handle button push event
 */
void handleButtonInterrupt(otInstance *aInstance)
{
    sendUdp(aInstance);
}

アクション: UDP 初期化を実装します。

main.c で、先ほど追加した handleButtonInterrupt 関数の後に initUdp 関数の実装を追加します。

/**
 * 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(aInstance, &sUdpSocket, &listenSockAddr, OT_NETIF_THREAD);
}

UDP_PORT は、前に定義したポート(1212)です。otUdpOpen 関数はソケットを開き、UDP メッセージを受信するとコールバック関数(handleUdpReceive)を登録します。otUdpBind は、OT_NETIF_THREAD を渡してスレッドを Thread ネットワーク インターフェースにバインドします。その他のネットワーク インターフェース オプションについては、UDP API リファレンスotNetifIdentifier 列挙型をご覧ください。

アクション: UDP メッセージを実装します。

main.c で、先ほど追加した initUdp 関数の後に sendUdp 関数の実装を追加します。

/**
 * 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(aInstance, &sUdpSocket, message, &messageInfo);

 exit:
    if (error != OT_ERROR_NONE && message != NULL)
    {
        otMessageFree(message);
    }
}

otEXPECT マクロと otEXPECT_ACTION マクロに注意してください。これらは、UDP メッセージが有効であり、バッファに正しく割り当てられていることを確認します。有効でない場合、関数は exit ブロックに移動することでエラーを適切に処理し、バッファを解放します。

UDP の初期化に使用される関数の詳細については、openthread.io の IPv6UDP のリファレンスをご覧ください。

アクション: UDP メッセージ処理を実装します

main.c で、先ほど追加した sendUdp 関数の後に handleUdpReceive 関数の実装を追加します。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);
}

8. API: Thread ネットワークの構成

わかりやすくするために、電源投入時にデバイスをすぐに Thread を起動してネットワークに参加させることをおすすめします。そのために、otOperationalDataset 構造を使用します。この構造体は、Thread ネットワーク認証情報をデバイスに送信するために必要なすべてのパラメータを保持します。

この構造を使用すると、OpenThread に組み込まれたネットワークのデフォルトがオーバーライドされ、アプリケーションのセキュリティが強化され、ネットワーク内の Thread ノードがアプリケーションを実行しているノードのみに制限されます。

もう一度、任意のテキスト エディタで ./openthread/examples/apps/cli/main.c ファイルを開きます。

./openthread/examples/apps/cli/main.c にアクセスします。

アクション: ヘッダーインクルードを追加します。

main.c ファイルの先頭のインクルード セクション内に、Thread ネットワークを構成するために必要な API ヘッダー ファイルを追加します。

#include <openthread/dataset_ftd.h>

アクション: ネットワーク構成を設定するための関数宣言を追加する

この宣言を main.c で、ヘッダーに含まれる #if ステートメントの前に追加します。この関数は、メインのアプリ関数の後に定義されます。

static void setNetworkConfiguration(otInstance *aInstance);

アクション: ネットワーク構成の呼び出しを追加します。

main.c で、この関数呼び出しを otSetStateChangedCallback 呼び出しの後の main() 関数に追加します。この関数は、Thread ネットワーク データセットを構成します。

/* Override default network credentials */
setNetworkConfiguration(instance);

アクション: Thread ネットワーク インターフェースとスタックを有効にする呼び出しを追加します。

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);

アクション: Thread ネットワーク構成を実装する

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 Key, PSKc, Security Policy
     */
    aDataset.mActiveTimestamp.mSeconds             = 1;
    aDataset.mActiveTimestamp.mTicks               = 0;
    aDataset.mActiveTimestamp.mAuthoritative       = false;
    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 network key to 1234C0DE1AB51234C0DE1AB51234C0DE */
    uint8_t key[OT_NETWORK_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE};
    memcpy(aDataset.mNetworkKey.m8, key, sizeof(aDataset.mNetworkKey));
    aDataset.mComponents.mIsNetworkKeyPresent = 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;

    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);
}

この関数で詳しく説明しているように、このアプリケーションで使用する Thread ネットワーク パラメータは次のとおりです。

  • チャンネル = 15
  • PAN ID = 0x2222
  • 拡張 PAN ID = C0DE1AB5C0DE1AB5
  • ネットワーク キー = 1234C0DE1AB51234C0DE1AB51234C0DE
  • ネットワーク名 = OTCodelab

さらに、ここではルーターの選択ジッターを下げて、デモ用にデバイスで迅速にロールを変更しています。これは、ノードが FTD(フルスレッド デバイス)の場合にのみ行われます。詳しくは、次のステップで説明します。

9. API: 制限付き関数

OpenThread の API の一部は、デモやテスト目的でのみ変更する設定を変更します。これらの API は、OpenThread を使用したアプリケーションの本番環境デプロイでは使用しないでください。

たとえば、otThreadSetRouterSelectionJitter 関数は、エンドデバイスが Router に昇格するのにかかる時間(秒単位)を調整します。この値のデフォルトは、Thread 仕様に従って 120 です。この Codelab では使いやすいように、このスレッドを 20 に変更しているため、Thread ノードでロールが変更されるのをそれほど長く待つ必要はありません。

注: MTD デバイスはルーターにならず、otThreadSetRouterSelectionJitter のような関数のサポートは MTD ビルドには含まれていません。後で CMake オプション -DOT_MTD=OFF を指定する必要があります。指定しない場合、ビルドは失敗します。

これを確認するには、OPENTHREAD_FTD のプリプロセッサ ディレクティブに含まれている otThreadSetRouterSelectionJitter 関数定義を調べます。

./openthread/src/core/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

10. CMake の更新

アプリをビルドする前に、3 つの CMake ファイルに必要なマイナー アップデートがいくつかあります。これらは、アプリのコンパイルとリンクのためにビルドシステムで使用されます。

./third_party/NordicSemiconductor/CMakeLists.txt

NordicSemiconductor CMakeLists.txt にフラグを追加して、アプリで GPIO 関数が定義されるようにします。

アクション: CMakeLists.txt ファイルにフラグを追加する。

任意のテキスト エディタで ./third_party/NordicSemiconductor/CMakeLists.txt を開き、COMMON_FLAG セクションに次の行を追加します。

...
set(COMMON_FLAG
    -DSPIS_ENABLED=1
    -DSPIS0_ENABLED=1
    -DNRFX_SPIS_ENABLED=1
    -DNRFX_SPIS0_ENABLED=1
    ...

    # Defined in ./third_party/NordicSemiconductor/nrfx/templates/nRF52840/nrfx_config.h
    -DGPIOTE_ENABLED=1
    -DGPIOTE_CONFIG_IRQ_PRIORITY=7
    -DGPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS=1
)

...

./src/CMakeLists.txt

./src/CMakeLists.txt ファイルを編集して新しい gpio.c ソースファイルを追加します。

アクション: gpio ソースを ./src/CMakeLists.txt ファイルに追加します。

任意のテキスト エディタで ./src/CMakeLists.txt を開き、ファイルを NRF_COMM_SOURCES セクションに追加します。

...

set(NRF_COMM_SOURCES
  ...
  src/gpio.c
  ...
)

...

./third_party/NordicSemiconductor/CMakeLists.txt

最後に、nrfx_gpiote.c ドライバ ファイルを NordicSemiconductor の CMakeLists.txt ファイルに追加します。これにより、Nordic ドライバのライブラリ ビルドに含まれるようになります。

アクション: gpio ドライバを NordicSemiconductor CMakeLists.txt ファイルに追加します。

任意のテキスト エディタで ./third_party/NordicSemiconductor/CMakeLists.txt を開き、ファイルを COMMON_SOURCES セクションに追加します。

...

set(COMMON_SOURCES
  ...
  nrfx/drivers/src/nrfx_gpiote.c
  ...
)
...

11. デバイスをセットアップする

コードの更新がすべて完了したら、アプリケーションをビルドして、3 つの Nordic nRF52840 開発ボードにフラッシュする準備が整いました。各デバイスはフルスレッド デバイス(FTD)として機能します。

OpenThread を構築する

nRF52840 プラットフォーム用の OpenThread FTD バイナリをビルドします。

$ cd ~/ot-nrf528xx
$ ./script/build nrf52840 UART_trans -DOT_MTD=OFF -DOT_APP_RCP=OFF -DOT_RCP=OFF

OpenThread FTD CLI バイナリがあるディレクトリに移動し、ARM 組み込みツールチェーンを使用して 16 進数形式に変換します。

$ cd build/bin
$ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex

ボードをフラッシュする

ot-cli-ftd.hex ファイルを各 nRF52840 ボードにフラッシュします。

nRF52840 ボードの外部電源ピンの横にあるマイクロ USB デバッグポートに USB ケーブルを接続し、Linux マシンに接続します。正しく設定すると、LED5 がオンになります。

20a3b4b480356447.png

前と同様に、nRF52840 ボードのシリアル番号を確認します。

c00d519ebec7e5f0.jpeg

nRFx コマンドライン ツールの場所に移動し、OpenThread CLI FTD 16 進数ファイルを ボードのシリアル番号で nRF52840 ボードにフラッシュします。

$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --verify --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.

これを「ボードをフラッシュ」して、他の 2 つのボードについて同じ手順を繰り返します。各ボードは Linux マシンに同じように接続し、フラッシュのコマンドは同一です(ただしボードのシリアル番号は除きます)。各ボードの一意のシリアル番号を必ず使用してください。

nrfjprog 点滅コマンド。

成功すると、各ボードで LED1、LED2、または LED3 のいずれかが点灯します。点滅するとすぐに LED ライトが 3 から 2(または 2 から 1)に変わることがあります(デバイスロールの変更機能)。

12. アプリケーションの機能

これで、3 つの nRF52840 ボードすべてに電源が供給され、OpenThread アプリケーションが実行されるはずです。前述のように、このアプリケーションには 2 つの主要な機能があります。

デバイスの役割インジケーター

各ボードの LED ライトは、Thread ノードの現在の役割を反映しています。

  • LED1 = リーダー
  • LED2 = ルーター
  • LED3 = エンドデバイス

役割が変わると LED も点灯します。各デバイスの電源を入れてから 20 秒以内に、これらの変更をボードに 1 つか 2 つ反映されているはずです。

UDP マルチキャスト

ボードで Button1 を押すと、UDP メッセージがメッシュスレッドのマルチキャスト アドレスに送信されます。これには、Thread ネットワーク内の他のすべてのノードが含まれます。このメッセージの受信に応じて、他のすべてのボードの LED4 がオンまたはオフに切り替わります。LED4 は、別の UDP メッセージを受信するまで、各ボードでオンまたはオフのままになります。

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. デモ: デバイスロールの変更を監視する

フラッシュ対象のデバイスは、ルーター適格エンドデバイス(REED)と呼ばれる特定の種類のフルスレッド デバイス(FTD)です。つまり、ルーターまたはエンドデバイスとして機能し、エンドデバイスからルーターに昇格できます。

スレッドは最大 32 個の Router をサポートできますが、Router の数を 16 ~ 23 に維持しようとします。REED がエンドデバイスとしてアタッチされ、ルーターの数が 16 未満の場合は、自動的に Router に昇格します。この変更は、アプリで otThreadSetRouterSelectionJitter 値を設定した秒数(20 秒)内でランダムに行われます。

すべての Thread ネットワークにはリーダーもあります。リーダーは、スレッド ネットワーク内のルーターセットを管理するルーターです。すべてのデバイスをオンにして、20 秒後、そのうちの 1 つはリーダー(LED1 がオン)、他の 2 つはルーター(LED2 オン)になります。

4e1e885861a66570.png

リーダーを削除

リーダーを Thread ネットワークから削除すると、別の Router が自身をリーダーに昇格し、リーダーが維持されるようにします。

電源スイッチを使用して、リーダーボード(LED1 が点灯しているボード)をオフにします。20 秒ほど待ちます。残りの 2 つのボードのいずれかで、LED2(ルーター)がオフになり、LED1(リーダー)がオンになります。このデバイスが Thread ネットワークのリーダーになりました。

4c57c87adb40e0e3.png

元のリーダーボードを再びオンにします。終了デバイス(LED3 が点灯)として Thread ネットワークに自動的に再参加します。20 秒以内にルーター選択ジッターがルーターに昇格(LED2 が点灯)

5f40afca2dcc4b5b.png

ボードをリセットする

3 つのボードをすべてオフにしてから、もう一度入れて LED を確認します。最初にパワーアップしたボードは、リーダーの役割で始まります(LED1 が点灯しています)。スレッド ネットワークの最初のルーターは自動的にリーダーになります。

他の 2 つのボードは、最初は終了デバイス(LED3 が点灯)としてネットワークに接続されていますが、20 秒以内にルーター(LED2 が点灯)に昇格します。

ネットワーク パーティション

ボードに十分な電力が供給されていない、またはボード間の無線接続が弱い場合、Thread ネットワークがパーティションに分割され、複数のデバイスがリーダーとして表示されます。

スレッドは自己修復します。最終的に、パーティションは 1 つのリーダーを持つ 1 つのパーティションにマージされます。

14. デモ: UDP マルチキャストの送信

前の演習を続ける場合、どのデバイスでも LED4 が点灯しないでください。

任意のボードを選択して Button1 キーを押します。アプリケーションを実行している Thread ネットワークの他のすべてのボード上の LED4 で、それらの状態が切り替わります。前回の演習から続行すると、オンになります。

f186a2618fdbe3fd.png

同じボードの Button1 をもう一度押します。他のすべてのボードの LED4 が再び切り替わります。

別のボードの Button1 を押し、他のボードで LED4 が切り替わる様子を確認します。LED4 が現在搭載されているボードのいずれかで Button1 を押します。そのボードでは LED4 がオンのままですが、他ではオンになります。

f5865ccb8ab7aa34.png

ネットワーク パーティション

ボードが分割されており、その中に複数のリーダーがある場合、マルチキャスト メッセージの結果はボード間で異なります。パーティション分割したボード(パーティション スレッド ネットワークの唯一のメンバー)に Button1 を押した場合、それに応じて他のボードの LED4 は点灯しません。この場合、ボードをリセットします。ボードは単一の Thread ネットワークを形成し、UDP メッセージが正しく動作するのが理想的です。

15. 完了

これで、OpenThread API を使用するアプリケーションが作成されました。

次のことを学習しました。

  • Nordic nRF52840 開発ボードでボタンと LED をプログラムする方法
  • 一般的な OpenThread API と otInstance クラスの使い方
  • OpenThread 状態の変化を監視して対応する方法
  • Thread ネットワークのすべてのデバイスに UDP メッセージを送信する方法
  • Makefile の変更方法

次のステップ

この Codelab では、次の演習を進めていきます。

  • オンボード LED の代わりに GPIO ピンを使用するように GPIO モジュールを変更し、ルーターの役割に応じて色が変わる外部 RGB LED を接続する
  • 別のサンプル プラットフォームで GPIO サポートを追加する
  • マルチキャストを使用して、ボタンからすべてのデバイスに ping する代わりに、Router / Leader API を使用して、個々のデバイスを特定して ping します。
  • OpenThread ボーダー ルーターを使用してメッシュ ネットワークをインターネットに接続し、Thread ネットワークの外部からマルチキャストして LED を点灯させます。

関連情報

openthread.ioGitHub で、次に示すさまざまな OpenThread リソースについてご確認ください。

関連資料: