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

1. ข้อมูลเบื้องต้น

26b7f4f6b3ea0700.png

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

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

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

2a6db2e258c32237.png

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

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

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

ฮาร์ดแวร์:

  • บอร์ดพัฒนานักพัฒนาซอฟต์แวร์แบบเซมิคอนดักเตอร์แบบนอร์ดิก 3 เครื่อง nRF52840
  • สายเชื่อม USB กับไมโคร USB 3 เส้นเพื่อเชื่อมต่อบอร์ด
  • เครื่อง Linux ที่มีพอร์ต USB อย่างน้อย 3 พอร์ต

ซอฟต์แวร์:

  • สายเครื่องมือ GNU
  • เครื่องมือบรรทัดคําสั่ง nRF5x ของนอร์ดิก
  • ซอฟต์แวร์ Segger J-Link
  • OpenThread
  • Git

เนื้อหาของ Codelab นี้ได้รับอนุญาตภายใต้ใบอนุญาต Creative Commons Attribution 3.0 และตัวอย่างโค้ดได้รับอนุญาตภายใต้ใบอนุญาต Apache 2.0 ยกเว้นที่ระบุไว้เป็นอย่างอื่น

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

ดําเนินการ Codelab ของฮาร์ดแวร์ให้เสร็จสมบูรณ์

ก่อนเริ่มใช้ Codelab นี้ คุณควรดําเนินการ Codelab สร้างเครือข่ายชุดข้อความด้วย nRF52840 Boards และ OpenThread ให้เสร็จสมบูรณ์ โดยดําเนินการดังนี้

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

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

เครื่อง Linux

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

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

Codelab นี้ใช้nnF52840 PDK Board 3 อัน

a6693da3ce213856.png

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

หากต้องการสร้างและเปิด FlashThread จะต้องติดตั้ง SEGGER J-Link, เครื่องมือบรรทัดคําสั่ง nRF5x, ARM GNU Toolchain และแพ็กเกจ Linux ต่างๆ หากสร้าง Codelab สําหรับเครือข่ายการสร้างชุดข้อความตามที่ต้องการเสร็จแล้ว คุณจะมีทุกสิ่งที่จําเป็นสําหรับการติดตั้งอยู่แล้ว หากไม่ถูกต้อง ให้ทําการ Codelab ดังกล่าวให้เรียบร้อยก่อนดําเนินการต่อ เพื่อให้แน่ใจว่าคุณจะสามารถสร้าง Flash ใน OpenThread ไปยังกระดาน dev nRF52840 ได้

3. โคลนที่เก็บ

OpenThread มีโค้ดแอปพลิเคชันตัวอย่างที่คุณสามารถใช้เป็นจุดเริ่มต้นสําหรับ Codelab นี้

โคลนที่เก็บ Openn 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

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

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

เช่น อินสแตนซ์ 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 และไฟ LED ต้องประกาศไว้ใน 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 ได้ หากต้องการเข้าถึงปุ่มและไฟ LED บนบอร์ด nRF52840 dev คุณจะต้องใช้ฟังก์ชันเหล่านั้นในแพลตฟอร์ม 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

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

การดําเนินการ: เพิ่มส่วนหัวที่มี

ต่อไป เพิ่มส่วนหัวที่คุณต้องใช้สําหรับฟังก์ชัน 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

ฟังก์ชันนี้จะใช้เพื่อสลับ 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 Platform สําหรับส่วนที่ 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);

การดําเนินการ: เพิ่มการลงทะเบียนการติดต่อกลับสําหรับฟังก์ชันเครื่องจัดการการเปลี่ยนแปลงสถานะ

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

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

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

ใน main.c หลังจากฟังก์ชัน main() ให้ใช้ฟังก์ชัน handleNetifStateChanged ฟังก์ชันนี้จะตรวจสอบแฟล็ก 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 หลังส่วน "รวม" และก่อนคําสั่ง #if ให้เพิ่มค่าคงที่เฉพาะ UDP และกําหนดค่าต่อไปนี้

#define UDP_PORT 1212

static const char UDP_DEST_ADDR[] = "ff03::1";
static const char UDP_PAYLOAD[]   = "Hello OpenThread World!";

ff03::1 คือที่อยู่มัลติแคสต์แบบ Mesh-Local ระบบจะส่งข้อความทั้งหมดที่ส่งมาที่อีเมลนี้ไปยังชุดข้อความทั้งหมดในเครือข่าย ดูข้อมูลเพิ่มเติมเกี่ยวกับการรองรับมัลติแคสต์ใน OpenThread ได้ที่ Multicast ใน 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 จะเปิดซ็อกเก็ตและลงทะเบียนฟังก์ชันเรียกกลับ (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 เพื่อให้แอปพลิเคชันของเรามีความปลอดภัยมากขึ้นและจํากัดโหนด Thread ในเครือข่ายของเราเฉพาะผู้ที่ใช้งานแอปพลิเคชัน

แล้วเปิดไฟล์ ./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 = 0x2222
  • รหัส PAN แบบขยาย = C0DE1AB5C0DE1AB5
  • คีย์เครือข่าย = 1234C0DE1AB51234C0DE1AB51234C0DE
  • ชื่อเครือข่าย = OTCodelab

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

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

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

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

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

คุณสามารถยืนยันได้โดยดูที่คําจํากัดความของฟังก์ชัน otThreadSetRouterSelectionJitter ซึ่งอยู่ภายในคําสั่ง preprocessor 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

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

./third_party/Nordicseconductor/CMakeLists.txt

เพิ่มแฟล็กบางส่วนไปยัง Nordicseconductor 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/Nordicseconductor/CMakeLists.txt

สุดท้าย เพิ่มไฟล์ไดรเวอร์ nrfx_gpiote.c ลงในไฟล์ Nordicเซมิคอนดักเตอร์ CMakeLists.txt เพื่อรวมไว้ในไลบรารีไลบรารีของไดรเวอร์นอร์ดิก

การดําเนินการ: เพิ่มไดรเวอร์ gpio ลงในไฟล์ Nordicกึ่งconductor CMakeLists.txt

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

...

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

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

เมื่ออัปเดตโค้ดเสร็จเรียบร้อย คุณก็พร้อมที่จะสร้างและใช้แอปพลิเคชันบนบอร์ดนักพัฒนาซอฟต์แวร์ Nordic nRF52840 ทั้งสาม อุปกรณ์แต่ละเครื่องจะทํางานเป็น "ชุดข้อความเต็ม" (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 และแปลงเป็นรูปแบบเลขฐานสิบหกด้วย 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 กับพอร์ตแก้ไขข้อบกพร่องของไมโคร USB ที่อยู่ข้าง PIN ของไฟภายนอกบนบอร์ด 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.

ทําขั้นตอน "Flash the boards" นี้ซ้ําสําหรับอีก 2 กระดาน กระดานแต่ละกระดานควรจะเชื่อมต่อกับเครื่อง Linux ด้วยวิธีเดียวกัน และคําสั่งแฟลชนั้นเหมือนกัน ยกเว้นหมายเลขซีเรียลของกระดาน ใช้หมายเลขซีเรียลที่ไม่ซ้ํากันของแต่ละกระดานใน

nrfjprog คําสั่งกะพริบ

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

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

กระดาน nRF52840 ทั้ง 3 แผงควรขับเคลื่อนแล้วและเรียกใช้แอปพลิเคชัน OpenThread ของเรา ดังที่อธิบายก่อนหน้านี้ แอปพลิเคชันนี้มีคุณลักษณะหลักสองประการ

ตัวชี้วัดบทบาทของอุปกรณ์

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

  • LED1 = ผู้นํา
  • LED2 = เราเตอร์
  • LED3 = อุปกรณ์สุดท้าย

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

มัลติแคสต์ UDP

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

203dd094acca1f97.png

9bbd96d9b1c63504.png

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

อุปกรณ์ที่คุณแฟลชเป็นอุปกรณ์ชุดข้อความเต็มรูปแบบ (FTD) เฉพาะที่เรียกว่า อุปกรณ์สิ้นสุดเราเตอร์ที่มีสิทธิ์ (REED) ซึ่งหมายความว่าสามารถทํางานได้เป็นเราเตอร์หรืออุปกรณ์ปลายทาง และโปรโมตตนเองจากอุปกรณ์ระดับบนไปยังเราเตอร์ได้

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

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

ไฟล์ 4e1e885861a66570.png

นําผู้นําออก

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

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

4c57c87adb40e0e3.png

เปิดกระดานผู้นําเดิมอีกครั้ง ควรมีการเข้าร่วมเครือข่ายชุดข้อความอีกครั้งโดยอัตโนมัติในฐานะอุปกรณ์ปิดท้าย (LED3) ภายใน 20 วินาที (Jitter การเลือกเราเตอร์) จะโปรโมตตัวเองไปยังเราเตอร์ (LED2)

5f40afca2dcc4b5b.png

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

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

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

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

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

ชุดข้อความแก้ไขปัญหาด้วยตนเองได้แล้ว ดังนั้นพาร์ติชันต่างๆ ควรผสานรวมกลับเข้าไปในพาร์ติชันเดียวโดยมีตัวแปรที่ดีที่สุด 1 รายการ

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

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

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

f186a2618fdbe3fd.png

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

กดปุ่ม 1 บนกระดานอื่นและสังเกตการสลับแบบ LED4 บนกระดานอื่นๆ กดปุ่ม 1 บนกระดานที่ LED4 เปิดอยู่ ไฟ LED4 จะยังคงเปิดอยู่สําหรับกระดานดังกล่าว แต่สลับเปิด/ปิดช่องอื่นอยู่

f5865ccb8ab7aa34.png

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

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

15. ยินดีด้วย

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

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

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

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

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

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

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

โปรดดู openthread.io และ GitHub สําหรับทรัพยากร OpenThread ที่หลากหลาย ซึ่งรวมถึง:

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

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