การพัฒนาด้วย API ของ OpenThread

1. บทนำ

26b7f4f6b3ea0700.png

OpenThread ของ Nest เป็นการใช้งานแบบโอเพนซอร์สของโปรโตคอลเครือข่าย Thread® Nest ได้เปิดตัว OpenThread เพื่อทำให้เทคโนโลยีที่ใช้ในผลิตภัณฑ์ Nest พร้อมใช้งานในวงกว้างสำหรับนักพัฒนาซอฟต์แวร์เพื่อเร่งการพัฒนาผลิตภัณฑ์สำหรับบ้านที่เชื่อมต่อ

ข้อกำหนดของเทรดกำหนดโปรโตคอลการสื่อสารระหว่างอุปกรณ์ไร้สายที่เสถียร ปลอดภัย และใช้พลังงานต่ำ โดยใช้ IPv6 สำหรับแอปพลิเคชันในบ้าน OpenThread ใช้เลเยอร์เครือข่ายเทรดทั้งหมดซึ่งรวมถึง IPv6, 6LoWPAN, IEEE 802.15.4 ที่มีการรักษาความปลอดภัย MAC, Mesh Link Generatement และ Mesh Routing

ใน Codelab นี้ คุณจะใช้ API ของ OpenThread เพื่อเริ่มเครือข่ายเทรด ตรวจสอบและตอบสนองต่อการเปลี่ยนแปลงของบทบาทในอุปกรณ์ ตลอดจนส่งข้อความ UDP รวมถึงเชื่อมโยงการดำเนินการเหล่านี้กับปุ่มและ LED ในฮาร์ดแวร์จริง

2a6db2e258c32237.png

สิ่งที่คุณจะได้เรียนรู้

  • วิธีตั้งโปรแกรมปุ่มและ LED ในกระดานการพัฒนา Nordic nRF52840
  • วิธีใช้ API ทั่วไปของ OpenThread และคลาส otInstance
  • วิธีตรวจสอบและตอบสนองต่อการเปลี่ยนแปลงสถานะของ OpenThread
  • วิธีส่งข้อความ UDP ไปยังอุปกรณ์ทั้งหมดในเครือข่ายเทรด
  • วิธีแก้ไขไฟล์ Makefile

สิ่งที่ต้องมี

ฮาร์ดแวร์

  • Nordic เซมิคอนดักเตอร์ nRF52840 Dev 3 บอร์ด
  • สายเชื่อม USB กับ Micro-USB 3 สาย
  • เครื่อง Linux ที่มีพอร์ต USB อย่างน้อย 3 พอร์ต

ซอฟต์แวร์:

  • กนูเครื่องมือเชน
  • เครื่องมือบรรทัดคำสั่ง nRF5x ของ Nordic
  • ซอฟต์แวร์ Segger J-Link
  • OpenThread
  • Git

เนื้อหาของ Codelab นี้ได้รับอนุญาตภายใต้สัญญาอนุญาตที่ต้องระบุที่มาของครีเอทีฟคอมมอนส์ 3.0 และตัวอย่างโค้ดได้รับอนุญาตภายใต้สัญญาอนุญาต Apache 2.0 เว้นแต่จะระบุไว้เป็นอย่างอื่น

2. เริ่มต้นใช้งาน

ทำขั้นตอน Codelab ของฮาร์ดแวร์ให้เสร็จ

ก่อนเริ่ม Codelab นี้ คุณควรทำขั้นตอน Build a Thread Network with nRF52840 Boards and OpenThread Codelab ให้เสร็จสมบูรณ์ ซึ่งมีดังนี้

  • ระบุรายละเอียดของซอฟต์แวร์ทั้งหมดที่คุณต้องการสำหรับการสร้างและการกะพริบ
  • สอนวิธีสร้าง OpenThread และ Flash ในกระดาน Nordic nRF52840
  • แสดงให้เห็นถึงพื้นฐานของเครือข่ายเทรด

Codelab นี้มีรายละเอียดเกี่ยวกับการตั้งค่าสภาพแวดล้อมใดๆ ที่จำเป็นต่อการสร้าง OpenThread และ Flash บอร์ด จึงมีเพียงวิธีการพื้นฐานสำหรับการแฟลชบอร์ดเท่านั้น เราถือว่าคุณได้เรียนรู้ Codelab การสร้างเครือข่ายเทรดเสร็จสมบูรณ์แล้ว

เครื่อง Linux

Codelab นี้ได้รับการออกแบบมาเพื่อใช้เครื่อง Linux ที่ใช้ i386 หรือ x86 เพื่อแฟลชบอร์ดการพัฒนาเทรดทั้งหมด ขั้นตอนทั้งหมดผ่านการทดสอบใน Ubuntu 14.04.5 LTS (Trusty Tahr)

บอร์ด Nordic เซมิคอนดักเตอร์ nRF52840

Codelab นี้ใช้บอร์ด nRF52840 PDK จำนวน 3 ชุด

a6693da3ce213856.png

ติดตั้งซอฟต์แวร์

หากต้องการสร้างและแฟลช OpenThread คุณต้องติดตั้ง SEGGER J-Link, เครื่องมือบรรทัดคำสั่ง nRF5x, เครื่องมือ ARM GNU Toolchain และแพ็กเกจต่างๆ ของ Linux หากคุณทำ 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

API สาธารณะของ OpenThread จะอยู่ที่ ./openthread/include/openthread ในที่เก็บ OpenThread API เหล่านี้ช่วยให้สามารถเข้าถึงฟีเจอร์และฟังก์ชันการทำงานที่หลากหลายของ OpenThread ทั้งในระดับเทรดและระดับแพลตฟอร์มเพื่อใช้ในแอปพลิเคชันของคุณ

  • ข้อมูลและการควบคุมอินสแตนซ์ OpenThread
  • บริการแอปพลิเคชัน เช่น IPv6, UDP และ CoAP
  • การจัดการข้อมูลเข้าสู่ระบบเครือข่าย พร้อมด้วยบทบาทคณะกรรมาธิการและบทบาทสมาชิก
  • การจัดการ Border Router
  • ฟีเจอร์ขั้นสูง เช่น การควบคุมดูแลเด็กและการตรวจจับ Jam

ดูข้อมูลอ้างอิงเกี่ยวกับ API ทั้งหมดของ OpenThread ได้ที่ openthread.io/reference

การใช้ API

หากต้องการใช้ API ให้รวมไฟล์ส่วนหัวของ API ไว้ในไฟล์แอปพลิเคชันไฟล์ใดไฟล์หนึ่งของคุณ จากนั้นเรียกใช้ฟังก์ชันที่ต้องการ

ตัวอย่างเช่น แอปตัวอย่าง CLI ที่รวมอยู่ใน OpenThread จะใช้ส่วนหัว 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 คือสิ่งที่คุณจะต้องใช้บ่อยเมื่อทำงานกับ API ของ OpenThread เมื่อเริ่มต้นแล้ว โครงสร้างนี้จะแสดงอินสแตนซ์แบบคงที่ของไลบรารี OpenThread และอนุญาตให้ผู้ใช้เรียกใช้ API ของ OpenThread ได้

ตัวอย่างเช่น อินสแตนซ์ OpenThread จะเริ่มต้นในฟังก์ชัน main() ของแอปตัวอย่าง CLI

./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 สำหรับทุกฟังก์ชัน จากนั้นติดตั้งใช้งานในไฟล์แหล่งที่มาเฉพาะแพลตฟอร์ม เมื่อใช้วิธีนี้ คุณจะใช้ส่วนหัวฟังก์ชันแบบเดียวกันสำหรับแพลตฟอร์มตัวอย่างอื่นๆ ได้

ตัวอย่างเช่น ฟังก์ชัน GPIO ที่เราจะใช้เพื่อเชื่อมต่อกับปุ่ม nRF52840 และต้องมีการประกาศใน openthread-system.h

เปิดไฟล์ ./openthread/examples/platforms/openthread-system.h ในโปรแกรมแก้ไขข้อความที่ต้องการ

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

การดำเนินการ: เพิ่มการประกาศฟังก์ชัน GPIO เฉพาะแพลตฟอร์ม

เพิ่มการประกาศฟังก์ชันเหล่านี้หลัง #include สำหรับส่วนหัว openthread/instance.h:

/**
 * 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 เมื่อกดปุ่มได้ หากจำเป็น ทั้งหมดขึ้นอยู่กับความต้องการของแอปพลิเคชันของคุณ หากไม่ต้องการใช้ฟังก์ชันดังกล่าว ให้ใช้มาโคร OT_UNUSED_VARIABLE จาก OpenThread API เพื่อระงับการสร้างข้อผิดพลาดเกี่ยวกับตัวแปรที่ไม่ได้ใช้สำหรับ Toolchain บางรายการ เราจะดูตัวอย่างกันภายหลัง

5. ใช้กระบวนการนามธรรมแพลตฟอร์ม GPIO

ในขั้นตอนก่อนหน้า เราได้พูดถึงการประกาศฟังก์ชันเฉพาะแพลตฟอร์มใน ./openthread/examples/platforms/openthread-system.h ที่สามารถใช้สำหรับ GPIO ได้ คุณต้องใช้ฟังก์ชันเหล่านั้นสําหรับแพลตฟอร์ม nRF52840 เพื่อเข้าถึงปุ่มและ LED ในกระดานนักพัฒนา nRF52840 ในโค้ดนี้ คุณจะเพิ่มฟังก์ชันที่

  • เริ่มต้น PIN และโหมด GPIO
  • ควบคุมแรงดันไฟฟ้าบนพิน
  • เปิดใช้การรบกวน GPIO และลงทะเบียน Callback

ในไดเรกทอรี ./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

ดูข้อมูลเพิ่มเติมเกี่ยวกับปุ่มและไฟ LED ของ nRF52840 ได้ที่ศูนย์ข้อมูล Nordic เซมิคอนดักเตอร์

การดำเนินการ: เพิ่มส่วนหัว

ถัดไป ให้เพิ่มส่วนหัวที่จำเป็นสำหรับฟังก์ชัน 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"

การดำเนินการ: เพิ่มฟังก์ชัน Callback และการขัดจังหวะสำหรับปุ่ม 1

เพิ่มโค้ดนี้ถัดไป ฟังก์ชัน in_pin1_handler เป็น Callback ที่ลงทะเบียนเมื่อเริ่มต้นฟังก์ชันการกดปุ่ม (ต่อไปคือไฟล์นี้)

โปรดสังเกตวิธีที่ Callback นี้ใช้มาโคร 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

ฟังก์ชันนี้จะใช้เพื่อเปิด/ปิด LED4 เมื่ออุปกรณ์ได้รับข้อความ UDP แบบมัลติแคสต์

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

การดำเนินการ: เพิ่มฟังก์ชันเพื่อเริ่มต้นและประมวลผลการกดปุ่ม

ฟังก์ชันแรกจะเริ่มต้นกระดานสำหรับการกดปุ่ม และฟังก์ชันที่สองจะส่งข้อความ UDP แบบหลายแคสต์เมื่อกดปุ่ม 1

/**
 * @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 สำหรับส่วนแรก และนามธรรมแพลตฟอร์ม GPIO สำหรับส่วนที่ 2

เปิดไฟล์ ./openthread/examples/apps/cli/main.c ในโปรแกรมแก้ไขข้อความที่ต้องการ

./openthread/examples/apps/cli/main.c

การดำเนินการ: เพิ่มส่วนหัว

ในส่วน "รวม" ของไฟล์ main.c ให้เพิ่มไฟล์ส่วนหัว 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);

การดำเนินการ: เพิ่มการลงทะเบียน Callback สำหรับฟังก์ชันตัวแฮนเดิลการเปลี่ยนสถานะ

เพิ่มฟังก์ชันนี้ในฟังก์ชัน main() ใน main.c หลังจากเรียก otAppCliInit การลงทะเบียน Callback นี้จะบอกให้ OpenThread เรียกฟังก์ชัน handleNetifStateChange ทุกครั้งที่สถานะอินสแตนซ์ OpenThread มีการเปลี่ยนแปลง

/* Register Thread state change handler */
otSetStateChangedCallback(instance, handleNetifStateChanged, instance);

การดำเนินการ: เพิ่มการดำเนินการเปลี่ยนแปลงสถานะ

ใน main.c หลังฟังก์ชัน main() ให้ใช้ฟังก์ชัน handleNetifStateChanged ฟังก์ชันนี้จะตรวจสอบ Flag OT_CHANGED_THREAD_ROLE ของอินสแตนซ์ OpenThread และหากมีการเปลี่ยนแปลง ให้เปิด/ปิดไฟ 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

ในแอปพลิเคชันของเรา เรายังต้องการส่งข้อความ UDP ไปยังอุปกรณ์อื่นๆ ทั้งหมดในเครือข่ายเมื่อกดปุ่มที่ 1 บนกระดานแผ่นหนึ่งด้วย เราจะสลับ LED4 บนบอร์ดอื่นๆ ในการตอบกลับเพื่อยืนยันการรับข้อความ

หากต้องการเปิดใช้ฟังก์ชันนี้ แอปพลิเคชันต้องทำสิ่งต่อไปนี้

  • เริ่มต้นการเชื่อมต่อ UDP เมื่อเริ่มต้นระบบ
  • สามารถส่งข้อความ UDP ไปยังที่อยู่มัลติแคสต์ในเครื่อง Mesh
  • จัดการข้อความ UDP ขาเข้า
  • สลับ LED4 เมื่อมีข้อความ UDP ขาเข้า

เปิดไฟล์ ./openthread/examples/apps/cli/main.c ในโปรแกรมแก้ไขข้อความที่ต้องการ

./openthread/examples/apps/cli/main.c

การดำเนินการ: เพิ่มส่วนหัว

ในส่วน "รวม" ที่ด้านบนของไฟล์ main.c ให้เพิ่มไฟล์ส่วนหัวของ API ที่ต้องใช้สำหรับฟีเจอร์ UDP มัลติแคสต์

#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 คือที่อยู่มัลติแคสต์ในเครื่องที่ทำงานร่วมกัน ข้อความที่ส่งถึงที่อยู่นี้จะส่งไปยังอุปกรณ์ Full Thread ทั้งหมดในเครือข่าย ดูข้อมูลเพิ่มเติมเกี่ยวกับการรองรับมัลติแคสต์ใน OpenThread ได้ที่ Multicast on 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;

การดำเนินการ: เพิ่มการโทรเพื่อเริ่มต้นไฟ LED และปุ่ม GPIO

ใน main.c ให้เพิ่มการเรียกฟังก์ชันเหล่านี้ลงในฟังก์ชัน main() หลังจากการเรียก otSetStateChangedCallback ฟังก์ชันเหล่านี้จะเริ่มต้นหมุด GPIO และ GPIOTE และตั้งค่าตัวแฮนเดิลของปุ่มเพื่อจัดการเหตุการณ์การกดปุ่ม

/* init GPIO LEDs and button */
otSysLedInit();
otSysButtonInit(handleButtonInterrupt);

การดำเนินการ: เพิ่มการเรียกใช้การเริ่มต้น UDP

ใน main.c ให้เพิ่มฟังก์ชันนี้ในฟังก์ชัน main() หลังจากการเรียก otSysButtonInit ที่คุณเพิ่งเพิ่ม:

initUdp(instance);

การเรียกนี้จะช่วยให้มั่นใจว่าซ็อกเก็ต UDP เริ่มต้นเมื่อแอปพลิเคชันเริ่มทำงาน หากไม่มีคีย์นี้ อุปกรณ์จะส่งหรือรับข้อความ UDP ไม่ได้

การดำเนินการ: เพิ่มการโทรเพื่อประมวลผลเหตุการณ์ปุ่ม GPIO

ใน main.c ให้เพิ่มการเรียกฟังก์ชันนี้ลงในฟังก์ชัน main() หลังการเรียก otSysProcessDrivers ในลูป while ฟังก์ชันนี้ประกาศใน 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(aInstance, &sUdpSocket, &listenSockAddr, OT_NETIF_THREAD);
}

UDP_PORT คือพอร์ตที่คุณกำหนดไว้ก่อนหน้านี้ (1212) ฟังก์ชัน otUdpOpen จะเปิดซ็อกเก็ตและลงทะเบียนฟังก์ชัน Callback (handleUdpReceive) เมื่อได้รับข้อความ UDP otUdpBind จะเชื่อมโยงซ็อกเก็ตกับอินเทอร์เฟซเครือข่ายเทรดโดยการส่ง OT_NETIF_THREAD สำหรับตัวเลือกอินเทอร์เฟซเครือข่ายอื่นๆ โปรดดูการแจกแจง otNetifIdentifier ในเอกสารอ้างอิง UDP API

การดำเนินการ: ใช้การรับส่งข้อความ 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(aInstance, &sUdpSocket, message, &messageInfo);

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

จดมาโคร otEXPECT และ otEXPECT_ACTION เพื่อให้มั่นใจว่าข้อความ UDP ถูกต้องและมีการจัดสรรอย่างถูกต้องในบัฟเฟอร์ หากไม่ใช่ ฟังก์ชันจะจัดการข้อผิดพลาดได้อย่างลงตัวโดยการข้ามไปยังบล็อก exit ซึ่งจะทำให้บัฟเฟอร์มีพื้นที่มากขึ้น

ดูข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันที่ใช้ในการเริ่มต้น UDP ได้ในข้อมูลอ้างอิง IPv6 และ UDP ใน openthread.io

การดำเนินการ: ใช้การจัดการข้อความ 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);
}

8. API: กำหนดค่าเครือข่ายเทรด

เพื่อให้การสาธิตได้โดยง่าย เราต้องการให้อุปกรณ์เริ่มต้นเทรดทันทีและรวมเข้าด้วยกันเป็นเครือข่ายเมื่อเครื่องเปิดอยู่ โดยเราจะใช้โครงสร้าง otOperationalDataset โครงสร้างนี้เก็บพารามิเตอร์ทั้งหมดที่จำเป็นต่อการส่งข้อมูลเข้าสู่ระบบของเครือข่ายเทรดไปยังอุปกรณ์

การใช้โครงสร้างนี้จะลบล้างค่าเริ่มต้นของเครือข่ายที่สร้างไว้ใน OpenThread เพื่อทำให้แอปพลิเคชันของเรามีความปลอดภัยยิ่งขึ้นและจำกัดโหนดเทรดในเครือข่ายของเราให้มีเฉพาะโหนดที่ใช้งานแอปพลิเคชันเท่านั้น

เปิดไฟล์ ./openthread/examples/apps/cli/main.c ในโปรแกรมแก้ไขข้อความที่ต้องการอีกครั้ง

./openthread/examples/apps/cli/main.c

การดำเนินการ: เพิ่มส่วนหัว

ในส่วน "รวม" ที่ด้านบนของไฟล์ main.c ให้เพิ่มไฟล์ส่วนหัว API ที่คุณต้องกำหนดค่าเครือข่ายเทรด ดังนี้

#include <openthread/dataset_ftd.h>

การดำเนินการ: เพิ่มการประกาศฟังก์ชันสำหรับการตั้งค่าการกำหนดค่าเครือข่าย

เพิ่มการประกาศนี้ลงใน main.c โดยหลังจากส่วนหัวรวมและก่อนคำสั่ง #if ใดๆ ระบบจะกำหนดฟังก์ชันนี้ต่อจากฟังก์ชันหลักของแอปพลิเคชัน

static void setNetworkConfiguration(otInstance *aInstance);

การดำเนินการ: เพิ่มการเรียกใช้การกำหนดค่าเครือข่าย

ใน main.c ให้เพิ่มการเรียกฟังก์ชันนี้ลงในฟังก์ชัน main() หลังจากการเรียกใช้ otSetStateChangedCallback ฟังก์ชันนี้กำหนดค่าชุดข้อมูลเครือข่ายเทรด

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

การดำเนินการ: เพิ่มการโทรเพื่อเปิดใช้อินเทอร์เฟซเครือข่ายเทรดและสแต็ก

ใน main.c ให้เพิ่มการเรียกฟังก์ชันเหล่านี้ลงในฟังก์ชัน main() หลังจากการเรียก otSysButtonInit

/* 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 ให้เพิ่มการใช้งานฟังก์ชัน setNetworkConfiguration หลังฟังก์ชัน main() ดังนี้

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

ตามรายละเอียดในฟังก์ชัน พารามิเตอร์เครือข่ายเทรดที่เราใช้สำหรับแอปพลิเคชันนี้มีดังนี้

  • ช่อง = 15
  • PAN ID = 0x2222
  • รหัส PAN แบบขยาย = C0DE1AB5C0DE1AB5
  • คีย์เครือข่าย = 1234C0DE1AB51234C0DE1AB51234C0DE
  • ชื่อเครือข่าย = OTCodelab

นอกจากนี้ เราจะลดความแปรปรวนของ Jitter การเลือกเราเตอร์ลงเพื่อให้อุปกรณ์เปลี่ยนบทบาทได้เร็วขึ้นเพื่อจุดประสงค์ในการสาธิต โปรดทราบว่าการดำเนินการนี้จะเสร็จสิ้นก็ต่อเมื่อโหนดเป็น FTD (Full Thread Device) โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ในขั้นตอนถัดไป

9. API: ฟังก์ชันที่ถูกจำกัด

API บางรายการของ OpenThread จะแก้ไขการตั้งค่าที่ควรแก้ไขเพื่อวัตถุประสงค์ในการสาธิตหรือการทดสอบเท่านั้น ไม่ควรใช้ API เหล่านี้ในการทำให้แอปพลิเคชันใช้งานได้จริงโดยใช้ OpenThread

ตัวอย่างเช่น ฟังก์ชัน otThreadSetRouterSelectionJitter จะปรับเวลา (เป็นวินาที) ที่อุปกรณ์ปลายทางใช้ในการโปรโมตตัวเองไปยังเราเตอร์ ค่าเริ่มต้นคือ 120 ตามข้อกำหนดของชุดข้อความ เพื่อความสะดวกในการใช้งานใน Codelab นี้ เราจะเปลี่ยนเป็น 20 คุณจึงไม่ต้องรอนานนักเพื่อให้โหนดเทรดเปลี่ยนบทบาท

หมายเหตุ: อุปกรณ์ MTD จะไม่กลายเป็นเราเตอร์และการรองรับฟังก์ชันอย่างเช่น otThreadSetRouterSelectionJitter จะไม่รวมอยู่ในบิลด์ของ MTD ในภายหลังเราต้องระบุตัวเลือก CMake -DOT_MTD=OFF มิฉะนั้นเราจะพบว่าบิลด์ล้มเหลว

คุณยืนยันได้โดยดูคำจำกัดความของฟังก์ชัน otThreadSetRouterSelectionJitter ที่อยู่ในคำสั่งล่วงหน้าของ OPENTHREAD_FTD ดังนี้

./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 ไฟล์ ซึ่งจะใช้โดยระบบบิลด์เพื่อคอมไพล์และลิงก์แอปพลิเคชัน

./third_party/NordicSemiconductor/CMakeLists.txt

ตอนนี้ให้เพิ่ม Flag ต่างๆ ให้กับ 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 ใหม่

การดำเนินการ: เพิ่มซอร์สโค้ด GPS ลงในไฟล์ ./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 เพื่อรวมไว้ในไลบรารีบิลด์ของไดรเวอร์นอร์ดิก

การดำเนินการ: เพิ่มไดรเวอร์ gpio ลงในไฟล์ NordicSemiconductor CMakeLists.txt

เปิด ./third_party/NordicSemiconductor/CMakeLists.txt ในโปรแกรมแก้ไขข้อความที่ต้องการ และเพิ่มไฟล์ลงในส่วน COMMON_SOURCES

...

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

11. ตั้งค่าอุปกรณ์

เมื่ออัปเดตโค้ดทั้งหมดเสร็จแล้ว คุณก็พร้อมที่จะสร้างและแฟลชแอปพลิเคชันไปยังกระดานพัฒนา Nordic nRF52840 ทั้ง 3 กระดานแล้ว อุปกรณ์แต่ละเครื่องจะทำงานเป็นอุปกรณ์ Full Thread (FTD)

สร้าง OpenThread

สร้างไบนารี OpenThread FTD สำหรับแพลตฟอร์ม nRF52840

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

ไปที่ไดเรกทอรีด้วยไบนารี OpenThread FTD CLI และแปลงเป็นรูปแบบเลขฐาน 16 ด้วยเครื่องมือ ARM Embedded Toolchain

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

แฟลชบอร์ด

แฟลชไฟล์ ot-cli-ftd.hex ไปยังบอร์ด nRF52840 แต่ละกระดาน

เสียบสาย USB กับพอร์ตแก้ไขข้อบกพร่องของ Micro-USB ถัดจากขาเสียบไฟภายนอกบนบอร์ด nRF52840 แล้วเสียบเข้ากับเครื่อง Linux ตั้งค่าอย่างถูกต้อง LED5 เปิดอยู่

20a3b4b480356447.png

จดบันทึกหมายเลขซีเรียลของบอร์ด nRF52840 ไว้เหมือนเดิม

c00d519ebec7e5f0.jpeg

ไปที่ตำแหน่งของเครื่องมือบรรทัดคำสั่ง nRFx และแฟลชไฟล์เลขฐานสิบหก OpenThread CLI FTD บนบอร์ด 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 ด้วยวิธีเดียวกัน และคำสั่ง Flash จะเหมือนกัน ยกเว้นหมายเลขซีเรียลของบอร์ด โปรดใช้หมายเลขซีเรียลที่ไม่ซ้ำกันของกระดานแต่ละกระดานใน

nrfjprog คำสั่ง Flash

หากติดตั้งสำเร็จ ไฟ LED1, LED2 หรือ LED3 จะติดสว่างขึ้นในแต่ละกระดาน คุณยังอาจเห็นสวิตช์ไฟ LED ที่สว่างจาก 3 เป็น 2 (หรือ 2 เป็น 1) หลังจากที่กะพริบเร็วๆ นี้ (ฟีเจอร์เปลี่ยนบทบาทของอุปกรณ์)

12. ฟังก์ชันการทำงานของแอปพลิเคชัน

ในตอนนี้กระดาน nRF52840 ทั้ง 3 กระดานควรเปิดใช้งานและเรียกใช้งานแอปพลิเคชัน OpenThread ของเรา ตามที่ได้อธิบายไปก่อนหน้านี้ แอปพลิเคชันนี้มีฟีเจอร์หลัก 2 อย่าง

สัญญาณบอกสถานะบทบาทของอุปกรณ์

ไฟ LED ที่สว่างขึ้นบนกระดานแต่ละกระดานแสดงถึงบทบาทปัจจุบันของโหนดเทรด

  • LED1 = ผู้นำ
  • LED2 = เราเตอร์
  • LED3 = อุปกรณ์ปลายทาง

เมื่อบทบาทเปลี่ยน ไฟ LED ที่สว่างขึ้นก็จะเปลี่ยนไปด้วย คุณน่าจะได้เห็นการเปลี่ยนแปลงเหล่านี้บนกระดานแล้วภายใน 20 วินาทีหลังจากที่อุปกรณ์แต่ละเครื่องเริ่มทำงาน

มัลติแคสต์ UDP

เมื่อกด Button1 บนกระดาน ระบบจะส่งข้อความ UDP ไปยังที่อยู่มัลติแคสต์ในเครื่องที่ทำงานร่วมกัน ซึ่งรวมถึงโหนดอื่นๆ ทั้งหมดในเครือข่ายเทรด เมื่อได้รับข้อความนี้ ไฟ LED4 บนกระดานอื่นๆ ทั้งหมดจะเปิดหรือปิด โดยจะเปิดหรือปิดไฟ LED4 สำหรับแต่ละกระดานจนกว่าจะได้รับข้อความ UDP อีกครั้ง

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. สาธิต: สังเกตการเปลี่ยนแปลงบทบาทในอุปกรณ์

อุปกรณ์ที่คุณแฟลชคืออุปกรณ์แบบ Full Thread (FTD) ประเภทใดประเภทหนึ่งที่เรียกว่าอุปกรณ์ปลายทางที่มีสิทธิ์ของเราเตอร์ (Router Eligible End Device หรือ REED) ซึ่งหมายความว่า Chromebook สามารถทำงานเป็นเราเตอร์หรืออุปกรณ์ปลายทาง และสามารถโปรโมตตัวเองจากอุปกรณ์ปลายทางไปเป็นเราเตอร์ได้

เทรดสามารถรองรับเราเตอร์ได้สูงสุด 32 ตัว แต่พยายามรักษาจำนวนเราเตอร์ให้อยู่ระหว่าง 16 ถึง 23 เราเตอร์ หาก REED ติดเป็นอุปกรณ์ปลายทางและจำนวนเราเตอร์ต่ำกว่า 16 เราเตอร์จะโปรโมตตัวเองไปยังเราเตอร์โดยอัตโนมัติ การเปลี่ยนแปลงนี้ควรเกิดขึ้นในเวลาแบบสุ่มภายในจำนวนวินาทีที่คุณกำหนดค่า otThreadSetRouterSelectionJitter ในแอปพลิเคชัน (20 วินาที)

เครือข่ายเทรดทุกเครือข่ายจะมี Leader ซึ่งเป็นเราเตอร์ที่รับผิดชอบในการจัดการชุดเราเตอร์ในเครือข่ายเทรด เมื่อเปิดอุปกรณ์ทั้งหมด หลังจากผ่านไป 20 วินาที เครื่องหนึ่งควรเป็นผู้นำ (LED1 เปิดอยู่) และอีก 2 เครื่องควรเป็นเราเตอร์ (ไฟ LED2 เปิดอยู่)

4e1e885861a66570.png

นำผู้นำออก

หากนำผู้นำออกจากเครือข่ายเทรด เราเตอร์อื่นจะโปรโมตตัวเองเป็นผู้นำเพื่อให้เครือข่ายนั้นยังคงมีผู้นำอยู่

ปิดลีดเดอร์บอร์ด (รุ่นที่มีไฟ LED1 สว่าง) โดยใช้สวิตช์เปิด/ปิด รอประมาณ 20 วินาที บน 1 ใน 2 กระดานที่เหลือจะมี LED2 (เราเตอร์) ปิดไปและ LED1 (ผู้นำ) จะติดขึ้นมา ตอนนี้อุปกรณ์นี้เป็นผู้นำของเครือข่ายเทรด

4c57c87adb40e0e3.png

เปิดกระดานผู้นำเดิมอีกครั้ง อุปกรณ์ควรกลับเข้าสู่เครือข่ายเทรดอีกครั้งเป็นอุปกรณ์ปลายทาง (LED3 ติดสว่าง) ภายใน 20 วินาที (เสียงรบกวนการเลือกเราเตอร์) การเลื่อนย้ายตัวเองไปยังเราเตอร์ (ไฟ LED2 ติดสว่าง)

5f40afca2dcc4b5b.png

รีเซ็ตกระดาน

ปิดกระดานทั้ง 3 กระดาน จากนั้นเปิดใหม่อีกครั้งและสังเกตไฟ LED กระดานแรกที่เปิดใช้งานควรเริ่มต้นในบทบาทผู้นำ (LED1 ติดสว่าง) เราเตอร์แรกในเครือข่ายเทรดจะกลายเป็นผู้นำโดยอัตโนมัติ

บอร์ดอีก 2 กระดานจะเชื่อมต่อกับเครือข่ายตอนแรกเป็นอุปกรณ์ปลายทาง (ไฟ LED3 สว่าง) แต่ควรเลื่อนหน้าจอตัวเองเป็นเราเตอร์ (ไฟ LED2 ติดสว่าง) ภายใน 20 วินาที

พาร์ติชันเครือข่าย

หากกระดานได้รับพลังงานไม่เพียงพอหรือการเชื่อมต่อวิทยุระหว่างกระดานมีสัญญาณอ่อน เครือข่ายเทรดอาจแบ่งออกเป็นพาร์ติชันและคุณมีอุปกรณ์มากกว่า 1 เครื่องแสดงเป็นหัวหน้า

เทรดจะเยียวยาตัวเองได้ ดังนั้นในที่สุดแล้วพาร์ติชันควรผสานกลับมาเป็นพาร์ติชันเดียวที่มีผู้นำ 1 คน

14. การสาธิต: ส่งมัลติแคสต์ UDP

หากดำเนินการต่อจากแบบฝึกหัดก่อนหน้า คุณไม่ควรติดไฟ LED4 บนอุปกรณ์ใดๆ

เลือกกระดานใดก็ได้แล้วกดปุ่ม 1 LED4 บนกระดานอื่นๆ ทั้งหมดในเครือข่ายเทรดที่เรียกใช้แอปพลิเคชันควรสลับสถานะของแผงกัน หากดำเนินการต่อจากแบบฝึกหัดก่อนหน้า จะเป็นการเปิดใช้

f186a2618fdbe3fd.png

กดปุ่มที่ 1 ที่กระดานเดิมอีกครั้ง ไฟ LED4 บนกระดานอื่นๆ ทั้งหมดควรสลับอีกครั้ง

กด Button1 บนกระดานอื่นและสังเกตการณ์การสลับไฟ LED4 บนกระดานอื่นๆ กดปุ่มที่ 1 บนแผงหนึ่งที่ไฟ LED4 เปิดอยู่ ไฟ LED4 จะยังเปิดอยู่สำหรับกระดานนั้น แต่จะสลับไปที่อื่น

f5865ccb8ab7aa34.png

พาร์ติชันเครือข่าย

หากกระดานของคุณมีการแบ่งพาร์ติชันและมีผู้นํามากกว่า 1 คน ผลลัพธ์ของข้อความมัลติแคสต์จะแตกต่างกันไปในแต่ละกระดาน หากคุณกด Button1 บนบอร์ดที่แบ่งพาร์ติชันแล้ว (ซึ่งคือสมาชิกเพียงรายเดียวในเครือข่ายเทรดที่แบ่งพาร์ติชันแล้ว) ไฟ LED4 บนกระดานอื่นๆ จะไม่สว่างขึ้นเป็นการตอบสนอง ในกรณีนี้ ให้รีเซ็ตบอร์ด ซึ่งควรจะเปลี่ยนเครือข่ายเทรดเดียวและการรับส่งข้อความ UDP ควรทำงานได้อย่างถูกต้อง

15. ยินดีด้วย

คุณได้สร้างแอปพลิเคชันที่ใช้ API ของ OpenThread แล้ว

ตอนนี้คุณทราบประเด็นต่อไปนี้แล้ว

  • วิธีตั้งโปรแกรมปุ่มและ LED ในกระดานการพัฒนา Nordic nRF52840
  • วิธีใช้ API ทั่วไปของ OpenThread และคลาส otInstance
  • วิธีตรวจสอบและตอบสนองต่อการเปลี่ยนแปลงสถานะของ OpenThread
  • วิธีส่งข้อความ UDP ไปยังอุปกรณ์ทั้งหมดในเครือข่ายเทรด
  • วิธีแก้ไขไฟล์ Makefile

ขั้นตอนถัดไป

จาก Codelab นี้ ลองทำตามแบบฝึกหัดต่อไปนี้

  • แก้ไขโมดูล GPIO ให้ใช้พิน GPIO แทนไฟ LED บนเครื่อง และเชื่อมต่อไฟ RGB LED ภายนอกที่เปลี่ยนสีตามบทบาทเราเตอร์
  • เพิ่มการรองรับ GPIO สำหรับแพลตฟอร์มตัวอย่างอื่น
  • ใช้ Router/Leader API เพื่อค้นหาและใช้คำสั่ง ping กับอุปกรณ์แต่ละเครื่อง แทนที่จะใช้มัลติแคสต์เพื่อใช้คำสั่ง ping กับอุปกรณ์ทั้งหมดจากการกดปุ่ม
  • เชื่อมต่อเครือข่ายที่ทำงานร่วมกันกับอินเทอร์เน็ตโดยใช้ OpenThread Border Router และมัลติแคสต์จากภายนอกเครือข่าย Thread เพื่อติดไฟ LED

อ่านเพิ่มเติม

ดู openthread.io และ GitHub สำหรับทรัพยากรต่างๆ ของ OpenThread เช่น

  • แพลตฟอร์มที่รองรับ — ค้นหาแพลตฟอร์มทั้งหมดที่รองรับ OpenThread
  • สร้าง OpenThread — รายละเอียดเพิ่มเติมเกี่ยวกับการสร้างและกำหนดค่า OpenThread
  • Thread Primer - ข้อมูลอ้างอิงที่ยอดเยี่ยมเกี่ยวกับแนวคิดของเทรด

ข้อมูลอ้างอิง: