Google cam kết thúc đẩy công bằng chủng tộc cho Cộng đồng người da đen. Xem cách thực hiện.

Phát triển bằng API OpenThread

1. Giới thiệu

26b7f4f6b3ea0700.png

OpenThread do Nest phát hành là một hình thức triển khai nguồn mở của giao thức mạng Thread®. Nest đã phát hành OpenThread để cung cấp công nghệ này trong các sản phẩm Nest cho nhiều nhà phát triển, nhằm đẩy nhanh quá trình phát triển các sản phẩm cho nhà thông minh.

Thông số kỹ thuật của luồng xác định giao thức giao tiếp giữa thiết bị và thiết bị không dây với độ tin cậy, dựa trên IPv6 dành cho các ứng dụng gia đình. OpenThread triển khai tất cả các lớp mạng Thread bao gồm IPv6, 6LoWPAN, IEEE 802.15.4 với bảo mật MAC, Mesh Link SETUP và Mesh Route.

Trong Lớp học lập trình này, bạn sẽ sử dụng các API OpenThread để bắt đầu mạng Thread, giám sát và phản ứng với các thay đổi trong vai trò trên thiết bị, cũng như gửi thông báo UDP, cũng như liên kết những hành động này với các nút và đèn LED trên phần cứng thực.

2a6db2e258c32237.png

Kiến thức bạn sẽ học được

  • Cách lập trình các nút và đèn LED trên bảng phát triển Bắc Âu nRF52840
  • Cách sử dụng các API OpenThread phổ biến và lớp otInstance
  • Cách theo dõi và phản ứng với các thay đổi về trạng thái của OpenThread
  • Cách gửi tin nhắn UDP đến tất cả các thiết bị trong mạng Thread
  • Cách sửa đổi Makefiles

Bạn cần có

Phần cứng:

  • 3 bảng điều khiển nhà phát triển Bắc Âu bán dẫn nRF52840
  • 3 cáp USB sang Micro-USB để kết nối các bo mạch
  • Một máy Linux có ít nhất 3 cổng USB

Phần mềm:

  • Chuỗi công cụ GNU
  • Công cụ dòng lệnh nRF5x của Nordic
  • Phần mềm Segger J-Link
  • OpenThread
  • Git

Trừ khi có thông báo khác, nội dung của Lớp học lập trình này được cấp phép theo Giấy phép ghi công theo Creative Commons 3.0 và các mã mẫu được cấp phép theo Giấy phép Apache 2.0.

2. Bắt đầu

Hoàn thành lớp học lập trình phần cứng

Trước khi bắt đầu Lớp học lập trình này, bạn nên hoàn thành Lớp học lập trình Xây dựng mạng Thread với nRF52840 Board và OpenThread:

  • Chi tiết tất cả phần mềm bạn cần để xây dựng và nhấp nháy
  • Hướng dẫn bạn cách tạo OpenThread và flash nó trên các bảng Nordic nRF52840
  • Chứng minh các khái niệm cơ bản về Mạng chuỗi

Bạn không cần phải thiết lập môi trường nào để tạo OpenThread và flash các bảng chứa chi tiết trong Lớp học lập trình này – chỉ cần các hướng dẫn cơ bản về cách nhấp nháy các bảng. Giả định rằng bạn đã hoàn tất Lớp học lập trình mạng chuỗi cuộc trò chuyện.

Máy Linux

Lớp học lập trình này được thiết kế để sử dụng máy Linux dựa trên i386 hoặc x86 để flash tất cả các bảng phát triển Chuỗi. Tất cả các bước đã được thử nghiệm trên Ubuntu 14.04.5 LTS (Trusty Tahr).

Bo mạch nRF52840 bán dẫn Bắc Âu

Lớp học lập trình này sử dụng ba bảng PDRF của nRF52840.

a6693da3ce213856.png

Cài đặt phần mềm

Để xây dựng và flash OpenThread, bạn cần cài đặt SEGGER J-Link, công cụ Lệnh nRF5x, công cụ ARM GNU và các gói Linux khác. Nếu đã hoàn thành Lớp học lập trình mạng theo chuỗi theo yêu cầu, thì bạn sẽ có sẵn mọi thứ mình cần. Nếu chưa hoàn tất, hãy hoàn tất Lớp học lập trình đó trước khi tiếp tục để đảm bảo bạn có thể tạo và flash OpenThread đến các bảng phát triển nRF52840.

3. Sao chép kho lưu trữ

OpenThread có kèm theo mã ứng dụng mẫu mà bạn có thể dùng làm điểm xuất phát cho Lớp học lập trình này.

Sao chép các ví dụ về OpenThread Nordic nRF528xx và tạo dựng OpenThread:

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

4. Kiến thức cơ bản về API OpenThread

Các API công khai của OpenThread\39; nằm ở ./openthread/include/openthread trong kho lưu trữ OpenThread. Các API này cung cấp quyền truy cập vào nhiều tính năng và chức năng trong OpenThread ở cả cấp Thread và cấp nền tảng để sử dụng trong các ứng dụng của bạn:

  • Thông tin và tùy chọn kiểm soát phiên bản OpenThread
  • Các dịch vụ ứng dụng như IPv6, UDP và CoAP
  • Quản lý thông tin xác thực mạng, cùng với vai trò Ủy viên và Người tham gia
  • Quản lý bộ định tuyến biên
  • Các tính năng nâng cao như tính năng giám sát trẻ em và phát hiện Jam

Bạn có thể xem thông tin tham khảo về tất cả các API OpenThread tại openthread.io/reference.

Sử dụng API

Để sử dụng API, hãy đưa tệp tiêu đề của API vào một trong các tệp ứng dụng của bạn. Sau đó, gọi hàm mong muốn.

Ví dụ: ứng dụng mẫu CLI có trong OpenThread sử dụng các tiêu đề API sau đây:

./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>

Thực thể OpenThread

Cấu trúc otInstance là thứ bạn sẽ thường xuyên sử dụng khi làm việc với các API OpenThread. Sau khi được khởi tạo, cấu trúc này sẽ đại diện cho một thực thể tĩnh của thư viện OpenThread và cho phép người dùng thực hiện lệnh gọi API OpenThread.

Ví dụ: thực thể OpenThread được khởi tạo trong hàm main() của ứng dụng mẫu 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;
}

Hàm cụ thể theo nền tảng

Nếu bạn muốn thêm các hàm dành riêng cho nền tảng vào một trong các ứng dụng mẫu có trong OpenThread, trước tiên, hãy khai báo các hàm này trong tiêu đề ./openthread/examples/platforms/openthread-system.h bằng cách sử dụng vùng chứa tên otSys cho tất cả các hàm. Sau đó, hãy triển khai các tệp này trong một tệp nguồn dành riêng cho nền tảng. Nhờ đó, bạn có thể dùng cùng một tiêu đề hàm cho các nền tảng mẫu khác.

Ví dụ: các hàm GPIO mà chúng ta sẽ dùng để kết nối với các nút nRF52840 và đèn LED phải được khai báo trong openthread-system.h.

Mở tệp ./openthread/examples/platforms/openthread-system.h trong trình chỉnh sửa văn bản ưu tiên của bạn.

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

VIỆC: Thêm khai báo hàm GPIO dành riêng cho nền tảng.

Thêm các khai báo hàm này sau #include cho tiêu đề 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);

Chúng tôi sẽ triển khai các chính sách này trong bước tiếp theo.

Xin lưu ý rằng phần khai báo hàm otSysButtonProcess sử dụng otInstance. Bằng cách đó, ứng dụng có thể truy cập vào thông tin về thực thể OpenThread khi một nút được nhấn, nếu cần. Tất cả phụ thuộc vào nhu cầu của ứng dụng của bạn. Nếu không cần triển khai hàm này, bạn có thể sử dụng macro OT_UNUSED_VARIABLE từ API OpenThread để ngăn lỗi xây dựng xung quanh các biến không sử dụng cho một số chuỗi công cụ. Chúng ta sẽ xem các ví dụ về việc này ở phần sau.

5. Triển khai trừu tượng nền tảng GPIO

Trong bước trước đó, chúng ta đã xem xét các khai báo hàm dành riêng cho nền tảng trong ./openthread/examples/platforms/openthread-system.h mà bạn có thể sử dụng cho GPIO. Để truy cập vào các nút và đèn LED trên bảng điều khiển nhà phát triển nRF52840, bạn cần triển khai các chức năng đó cho nền tảng nRF52840. Trong mã này, bạn sẽ thêm các hàm:

  • Khởi chạy các ghim và chế độ GPIO
  • Điều khiển điện áp trên pin
  • Bật gián đoạn GPIO và đăng ký một lệnh gọi lại

Trong thư mục ./src/src, hãy tạo một tệp mới có tên gpio.c. Trong tệp mới này, hãy thêm nội dung sau đây.

./src/src/gpio.c (tệp mới)

ACTION: Thêm định nghĩa.

Các định nghĩa này đóng vai trò là khái niệm trừu tượng giữa các giá trị dành riêng cho nRF52840 và các biến được dùng ở cấp ứng dụng 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

Để biết thêm thông tin về các nút và đèn LED nRF52840, hãy xem Trung tâm thông tin bán dẫn Bắc Âu.

ACTION: Thêm tiêu đề bao gồm.

Tiếp theo, hãy thêm tiêu đề bao gồm bạn cần có chức năng 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"

ACTION: Thêm chức năng gọi lại và gián đoạn cho Nút 1.

Thêm mã này tiếp theo. Hàm in_pin1_handler là lệnh gọi lại được đăng ký khi hàm nhấn nút được khởi tạo (sau đó trong tệp này).

Hãy lưu ý cách lệnh gọi lại này sử dụng macro OT_UNUSED_VARIABLE, vì các biến được chuyển đến in_pin1_handler không thực sự được dùng trong hàm.

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

ACTION: Thêm một chức năng để định cấu hình đèn LED.

Thêm mã này để định cấu hình chế độ và trạng thái của tất cả các đèn LED trong quá trình khởi tạo.

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

ACTION: Thêm một chức năng để đặt chế độ của đèn LED.

Chức năng này sẽ được dùng khi vai trò của thiết bị thay đổi.

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

ACTION: Thêm một chức năng để bật/tắt chế độ của đèn LED.

Chức năng này sẽ dùng để bật/tắt đèn LED4 khi thiết bị nhận được thông báo UDP đa hướng.

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

ACTION: Thêm chức năng để khởi tạo và xử lý việc nhấn nút.

Hàm đầu tiên khởi tạo bảng cho một lần nhấn nút và thứ hai gửi thông báo UDP đa hướng khi nút 1 được nhấn.

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

HÀNH ĐỘNG: Lưu và đóng gpio.c tệp.

6. API: Phản hồi các thay đổi về vai trò trên thiết bị

Trong ứng dụng, chúng tôi muốn các đèn LED khác nhau sáng lên tùy thuộc vào vai trò của thiết bị. Hãy theo dõi các vai trò sau: Trưởng nhóm, Bộ định tuyến, Thiết bị cuối. Chúng ta có thể gán các đèn LED đó cho các đèn LED như sau:

  • LED1 = Dẫn đầu
  • LED2 = Bộ định tuyến
  • LED3 = Thiết bị cuối

Để bật chức năng này, ứng dụng cần biết thời điểm vai trò thiết bị thay đổi và cách bật đèn LED chính xác để phản hồi. Chúng tôi sẽ sử dụng phiên bản OpenThread cho phần đầu tiên, và bản tóm tắt nền tảng GPIO cho phần thứ hai.

Mở tệp ./openthread/examples/apps/cli/main.c trong trình chỉnh sửa văn bản ưu tiên của bạn.

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

ACTION: Thêm tiêu đề bao gồm.

Trong phần bao gồm của tệp main.c, hãy thêm tệp tiêu đề API mà bạn cần để sử dụng tính năng thay đổi vai trò.

#include <openthread/instance.h>
#include <openthread/thread.h>
#include <openthread/thread_ftd.h>

ACTION: Thêm khai báo hàm trình xử lý cho thay đổi trạng thái của thực thể OpenThread.

Thêm phần khai báo này vào main.c, sau tiêu đề bao gồm và trước mọi câu lệnh #if. Hàm này sẽ được xác định sau ứng dụng chính.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

ACTION: Thêm gói đăng ký gọi lại cho hàm trình xử lý thay đổi trạng thái.

Trong main.c, hãy thêm hàm này vào hàm main() sau lệnh gọi otAppCliInit. Lượt đăng ký lại này sẽ yêu cầu OpenThread gọi hàm handleNetifStateChange mỗi khi trạng thái của thực thể OpenThread thay đổi.

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

HÀNH ĐỘNG: Thêm cách triển khai thay đổi trạng thái.

Trong main.c, sau hàm main(), hãy triển khai hàm handleNetifStateChanged. Chức năng này kiểm tra cờ OT_CHANGED_THREAD_ROLE của phiên bản OpenThread và nếu đèn đã thay đổi, hãy bật/tắt các đèn LED nếu cần.

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: Sử dụng tính năng phát đa hướng để bật đèn LED

Trong ứng dụng của mình, chúng ta cũng muốn gửi thông báo UDP đến tất cả các thiết bị khác trong mạng khi Nút 1 được nhấn trên một bảng. Để xác nhận việc nhận thông báo, chúng tôi sẽ chuyển đổi đèn LED4 trên các bảng khác để phản hồi.

Để bật chức năng này, ứng dụng cần:

  • Khởi tạo kết nối UDP khi khởi động
  • Có thể gửi tin nhắn UDP đến địa chỉ đa hướng địa phương
  • Xử lý tin nhắn UDP đến
  • Bật/tắt đèn LED4 để phản hồi các thông báo UDP đến

Mở tệp ./openthread/examples/apps/cli/main.c trong trình chỉnh sửa văn bản ưu tiên của bạn.

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

ACTION: Thêm tiêu đề bao gồm.

Trong phần bao gồm ở đầu tệp main.c, hãy thêm tệp tiêu đề API mà bạn cần cho tính năng UDP đa hướng.

#include <string.h>

#include <openthread/message.h>
#include <openthread/udp.h>

#include "utils/code_utils.h"

Tiêu đề code_utils.h được dùng cho các macro otEXPECTotEXPECT_ACTION xác thực các điều kiện thời gian chạy và xử lý lỗi một cách khéo léo.

ACTION: Thêm định nghĩa và hằng số:

Trong tệp main.c, sau phần "include" và trước mọi câu lệnh #if, hãy thêm hằng số UDP cụ thể và xác định:

#define UDP_PORT 1212

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

ff03::1 là địa chỉ đa hướng cục bộ dạng lưới. Mọi tin nhắn được gửi đến địa chỉ này sẽ được gửi đến tất cả các Thiết bị chuỗi đầy đủ trong mạng. Hãy xem mục Đa hướng trên onthread.io để biết thêm thông tin về tính năng hỗ trợ phát đa hướng trong OpenThread.

VIỆC: Thêm khai báo hàm.

Trong tệp main.c, sau định nghĩa otTaskletsSignalPending và trước hàm main(), hãy thêm các hàm UDP riêng, cũng như một biến tĩnh để biểu thị cổng 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;

VIỆC: Thêm cuộc gọi để khởi tạo đèn LED và nút của GPIO.

Trong main.c, hãy thêm các lệnh gọi hàm này vào hàm main() sau lệnh gọi otSetStateChangedCallback. Các hàm này khởi tạo các ghim GPIO và GPIOTE, đồng thời đặt một trình xử lý nút để xử lý các sự kiện đẩy nút.

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

ACTION: Thêm lệnh gọi khởi chạy UDP.

Trong main.c, hãy thêm hàm này vào hàm main() sau lệnh gọi otSysButtonInit mà bạn vừa thêm:

initUdp(instance);

Lệnh gọi này đảm bảo cổng cổng UDP được khởi tạo khi ứng dụng khởi động. Nếu không có chế độ này, thiết bị không thể gửi hoặc nhận tin nhắn UDP.

VIỆC: Thêm cuộc gọi để xử lý sự kiện nút GPIO.

Trong main.c, hãy thêm lệnh gọi hàm này vào hàm main() sau lệnh gọi otSysProcessDrivers, trong vòng lặp while. Hàm này, được khai báo trong gpio.c, kiểm tra xem nút có được nhấn không và nếu có, hãy gọi trình xử lý (handleButtonInterrupt) đã được đặt ở bước trên.

otSysButtonProcess(instance);

ACTION: Triển khai trình xử lý gián đoạn nút.

Trong main.c, hãy thêm phương thức triển khai hàm handleButtonInterrupt sau hàm handleNetifStateChanged mà bạn đã thêm ở bước trước đó:

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

ACTION: Triển khai tính năng khởi chạy UDP.

Trong main.c, hãy thêm cách triển khai hàm initUdp sau hàm handleButtonInterrupt mà bạn vừa thêm:

/**
 * 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 là cổng bạn đã xác định trước đó (1212). Hàm otUdpOpen mở cổng và đăng ký một hàm callback (handleUdpReceive) khi nhận được thông báo UDP. otUdpBind liên kết cổng vào giao diện mạng Thread bằng cách chuyển OT_NETIF_THREAD. Để biết các tùy chọn giao diện mạng khác, hãy tham khảo bảng liệt kê otNetifIdentifier trong Tài liệu tham khảo API UDP.

ACTION: Triển khai tính năng nhắn tin UDP.

Trong main.c, hãy thêm cách triển khai hàm sendUdp sau hàm initUdp mà bạn vừa thêm:

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

Lưu ý macro otEXPECTotEXPECT_ACTION. Các thông số này đảm bảo rằng thông báo UDP hợp lệ và được phân bổ chính xác trong bộ đệm, và nếu không, hàm sẽ xử lý lỗi một cách khéo léo bằng cách chuyển đến khối exit để giải phóng bộ đệm.

Hãy tham khảo phần Tham chiếu IPv6UDP trên Openthread.io để biết thêm thông tin về các hàm dùng để khởi tạo UDP.

ACTION: Triển khai tính năng xử lý tin nhắn UDP.

Trong main.c, hãy thêm cách triển khai hàm handleUdpReceive sau hàm sendUdp mà bạn vừa thêm. Chức năng này chỉ bật/tắt đèn 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: Định cấu hình mạng Thread

Để dễ dàng minh họa, chúng tôi muốn các thiết bị của mình bắt đầu chuỗi cuộc trò chuyện ngay lập tức và kết nối với nhau vào một mạng khi thiết bị đó được bật nguồn. Để làm điều này, chúng ta sẽ dùng cấu trúc otOperationalDataset. Cấu trúc này lưu giữ tất cả thông số cần thiết để truyền thông tin xác thực mạng Thread tới một thiết bị.

Việc sử dụng cấu trúc này sẽ ghi đè các giá trị mặc định của mạng được tích hợp sẵn trong OpenThread, để làm cho ứng dụng của chúng tôi an toàn hơn và giới hạn các nút Thread trong mạng của chúng tôi ở chỉ những người dùng đang chạy ứng dụng.

Một lần nữa, mở tệp ./openthread/examples/apps/cli/main.c trong trình chỉnh sửa văn bản ưa thích của bạn.

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

ACTION: Thêm tiêu đề bao gồm.

Trong phần "include" ở đầu tệp main.c, hãy thêm tệp tiêu đề API mà bạn cần định cấu hình Mạng Thread:

#include <openthread/dataset_ftd.h>

ACTION: Thêm tính năng khai báo hàm để đặt cấu hình mạng.

Thêm phần khai báo này vào main.c, sau tiêu đề bao gồm và trước mọi câu lệnh #if. Hàm này sẽ được xác định sau hàm ứng dụng chính.

static void setNetworkConfiguration(otInstance *aInstance);

ACTION: Thêm lệnh gọi cấu hình mạng.

Trong main.c, hãy thêm lệnh gọi hàm này vào hàm main() sau lệnh gọi otSetStateChangedCallback. Hàm này định cấu hình tập dữ liệu mạng Thread.

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

ACTION: Thêm cuộc gọi để bật giao diện và chuỗi ngăn xếp Thread.

Trong main.c, hãy thêm các lệnh gọi hàm này vào hàm main() sau lệnh gọi otSysButtonInit.

/* Start the Thread network interface (CLI cmd > ifconfig up) */
otIp6SetEnabled(instance, true);

/* Start the Thread stack (CLI cmd > thread start) */
otThreadSetEnabled(instance, true);

ACTION: Triển khai cấu hình mạng Thread.

Trong main.c, hãy thêm cách triển khai hàm setNetworkConfiguration sau hàm 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);
}

Như được nêu chi tiết trong hàm, các thông số mạng Thread mà chúng tôi sử dụng cho ứng dụng này là:

  • Kênh = 15
  • Mã PAN = 0x2222
  • Mã PAN mở rộng = C0DE1AB5C0DE1AB5
  • Khóa mạng = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Tên mạng = OTCodelab

Ngoài ra, đây là nơi chúng tôi giảm Bộ chọn bộ định tuyến để thiết bị của chúng tôi thay đổi vai trò nhanh hơn cho mục đích minh họa. Xin lưu ý rằng việc này chỉ được thực hiện nếu nút này là một FTD (Thiết bị luồng đầy đủ). Hãy tìm hiểu thêm về bước này trong bước tiếp theo.

9. API: Hàm bị hạn chế

Một số API OpenThread\39; sửa đổi các tùy chọn cài đặt chỉ nên được sửa đổi cho mục đích thử nghiệm hoặc minh họa. Không nên sử dụng các API này khi triển khai phiên bản chính thức của một ứng dụng bằng OpenThread.

Ví dụ: hàm otThreadSetRouterSelectionJitter điều chỉnh thời gian (tính bằng giây) mà thiết bị cuối cần để quảng bá chính lên Bộ định tuyến. Giá trị mặc định cho giá trị này là 120, theo Đặc tả chuỗi. Để dễ sử dụng trong Lớp học mã này, chúng ta sẽ thay đổi giá trị đó thành 20, vì vậy, bạn không cần phải đợi rất lâu để nút Nút thay đổi vai trò.

Lưu ý: Thiết bị MTD không trở thành bộ định tuyến và hỗ trợ cho chức năng như otThreadSetRouterSelectionJitter không được bao gồm trong bản dựng MTD. Sau đó, chúng ta cần chỉ định tùy chọn CMake -DOT_MTD=OFF, nếu không thì chúng ta sẽ gặp lỗi xây dựng.

Bạn có thể xác nhận điều này bằng cách xem định nghĩa hàm otThreadSetRouterSelectionJitter. Tiêu đề này nằm trong lệnh xử lý trước của 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. Cập nhật CC

Trước khi tạo ứng dụng, bạn cần thực hiện một vài cập nhật nhỏ đối với 3 tệp CMake. Các bản dựng này được hệ thống bản dựng sử dụng để biên dịch và liên kết ứng dụng của bạn.

./third_party/NordicSemiconductor/CMakeLists.txt

Bây giờ, hãy thêm một số cờ vào NordicSemiconductor CMakeLists.txt để đảm bảo các hàm GPIO được xác định trong ứng dụng.

HÀNH ĐỘNG: Thêm cờ vào tệp CMakeLists.txt .

Mở ./third_party/NordicSemiconductor/CMakeLists.txt trong trình chỉnh sửa văn bản ưa thích của bạn và thêm các dòng sau trong phần 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

Chỉnh sửa tệp ./src/CMakeLists.txt để thêm tệp nguồn gpio.c mới:

HÀNH ĐỘNG: Thêm nguồn gpio vào tệp ./src/CMakeLists.txt .

Mở ./src/CMakeLists.txt trong trình chỉnh sửa văn bản mà bạn muốn và thêm tệp vào phần NRF_COMM_SOURCES.

...

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

...

./third_party/NordicSemiconductor/CMakeLists.txt

Cuối cùng, hãy thêm tệp trình điều khiển nrfx_gpiote.c vào tệp Bắc ÂuSemiconductor CMakeLists.txt để tệp này được đưa vào bản dựng thư viện của trình điều khiển Bắc Âu.

ACTION: Thêm trình điều khiển gpio vào tệp NordicSemiconductor CMakeLists.txt .

Mở ./third_party/NordicSemiconductor/CMakeLists.txt trong trình chỉnh sửa văn bản mà bạn muốn và thêm tệp vào phần COMMON_SOURCES.

...

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

11. Thiết lập thiết bị

Với tất cả các lần cập nhật mã được thực hiện, bạn đã sẵn sàng xây dựng và cài đặt ứng dụng cho cả ba bảng phát triển Bắc Âu nRF52840. Mỗi thiết bị sẽ hoạt động như một Thiết bị tạo chuỗi đầy đủ (FTD).

Tạo chuỗi mở

Xây dựng tệp nhị phân OpenThread FTD cho nền tảng nRF52840.

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

Chuyển đến thư mục có tệp nhị phân OpenThread FTD CLI và chuyển đổi tệp đó sang định dạng hex bằng ARM Nhúng Toolchain:

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

Chơi bảng

Đưa tệp ot-cli-ftd.hex lên mỗi bảng nRF52840.

Cắm cáp USB vào cổng gỡ lỗi Micro-USB bên cạnh chân cắm nguồn bên ngoài trên bảng mạch nRF52840, sau đó cắm vào máy Linux. Đặt đúng LED5.

20a3b4b480356447.png

Như trước đây, hãy lưu ý số sê-ri của bảng nRF52840:

c00d519ebec7e5f0.jpeg

Di chuyển đến vị trí của Công cụ dòng lệnh nRFx và flash tệp hex Openthread CLI FTD lên bảng nRF52840, sử dụng số sê-ri của bảng:

$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --verify --chiperase --program \
       ~/openthread/output/nrf52840/bin/ot-cli-ftd.hex --reset

Đèn LED5 sẽ tắt nhanh trong thời gian nhấp nháy. Kết quả sau đây được tạo ra khi thành công:

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.

Lặp lại "Flash này Board" bước cho hai bảng còn lại. Mỗi bảng phải được kết nối với máy Linux theo cách giống nhau và lệnh để đèn flash giống nhau, ngoại trừ số sê-ri của bảng mạch. Đảm bảo sử dụng số sê-ri duy nhất của mỗi bảng trong

nrfjprog lệnh nhấp nháy.

Nếu thành công, đèn LED1, LED2 hoặc LED3 sẽ sáng trên mỗi bảng. Bạn thậm chí có thể thấy công tắc đèn LED sáng từ 3 đến 2 (hoặc 2 đến 1) ngay sau khi nhấp nháy (tính năng thay đổi vai trò trên thiết bị).

12. Chức năng của ứng dụng

Giờ đây, cả ba bảng nRF52840 sẽ đều được bật nguồn và chạy ứng dụng OpenThread của chúng tôi. Như đã nêu chi tiết trước đó, ứng dụng này có hai tính năng chính.

Chỉ báo vai trò trên thiết bị

Đèn LED sáng trên mỗi bảng phản ánh vai trò hiện tại của nút Chủ đề:

  • LED1 = Dẫn đầu
  • LED2 = Bộ định tuyến
  • LED3 = Thiết bị cuối

Vai trò sẽ thay đổi và đèn LED sáng. Bạn sẽ thấy những thay đổi này trên bảng hoặc trong vòng 20 giây từ mỗi thiết bị đang bật nguồn.

Phát đa hướng UDP

Khi bạn nhấn nút 1 trên bảng, thông báo UDP sẽ được gửi đến địa chỉ đa hướng cục bộ lưới, bao gồm tất cả các nút khác trong mạng Thread. Để nhận được thông báo này, LED4 trên tất cả các bảng khác sẽ bật hoặc tắt. Đèn LED 4 sẽ bật hoặc tắt cho mỗi bảng cho đến khi nhận được thông báo UDP khác.

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. Bản minh họa: Quan sát những thay đổi về vai trò trên thiết bị

Thiết bị bạn đã nhấp nháy là một loại cụ thể của Thiết bị đủ luồng (FTD) được gọi là Thiết bị cuối đủ điều kiện dùng cho bộ định tuyến (REED). Điều này có nghĩa là thiết bị này có thể hoạt động như một Bộ định tuyến hoặc Thiết bị cuối, và có thể tự quảng bá từ một Thiết bị kết thúc đến một Bộ định tuyến.

Thread có thể hỗ trợ tối đa 32 Router, nhưng hãy cố gắng giữ số lượng Router từ 16 đến 23. Nếu REED đính kèm dưới dạng Thiết bị cuối và số Bộ định tuyến dưới 16, thì bộ định tuyến sẽ tự động quảng bá chính bộ định tuyến đó. Thay đổi này sẽ diễn ra vào một thời điểm ngẫu nhiên trong phạm vi số giây mà bạn đã đặt giá trị otThreadSetRouterSelectionJitter trong ứng dụng (20 giây).

Mỗi mạng Thread cũng có một Router, là Router chịu trách nhiệm quản lý tập hợp các Router trong mạng Thread. Khi tất cả các thiết bị đang bật, sau 20 giây, một trong số các thiết bị đó sẽ là Trưởng nhóm (LED1 đang bật) và hai thiết bị còn lại phải là Bộ định tuyến (bật đèn LED2).

4e1e885861a66570.png

Xóa biến thể dẫn đầu

Nếu Trưởng nhóm bị loại bỏ khỏi mạng Thread, thì một Bộ định tuyến khác sẽ tự quảng bá cho Trưởng nhóm, để đảm bảo mạng vẫn có Trưởng nhóm.

Tắt bảng Lãnh đạo (bảng có đèn LED1 sáng) bằng công tắc Nguồn. Đợi khoảng 20 giây. Trên một trong hai bảng còn lại, LED2 (Bộ định tuyến) sẽ tắt và LED1 (Chì) sẽ bật. Thiết bị này hiện là biến thể dẫn đầu của mạng Thread.

4c57c87adb40e0e3.png

Bật lại Board ban đầu. Thiết bị sẽ tự động tham gia lại mạng Thread dưới dạng Thiết bị cuối (LED3 được chiếu sáng). Trong vòng 20 giây (Bộ chọn bộ định tuyến), thiết bị sẽ tự quảng bá tới Bộ định tuyến (LED2 được chiếu sáng).

5f40afca2dcc4b5b.png

Đặt lại bảng

Tắt cả 3 bảng, sau đó bật lại và quan sát các đèn LED. Bảng đầu tiên được bật nguồn sẽ bắt đầu với vai trò Trưởng nhóm (LED1 được chiếu sáng) — Bộ định tuyến đầu tiên trong mạng Thread sẽ tự động trở thành Trưởng nhóm.

Hai bảng còn lại ban đầu kết nối với mạng ở dạng Thiết bị cuối (LED3 được chiếu sáng) nhưng phải tự quảng bá với Bộ định tuyến (LED2 sẽ sáng) trong vòng 20 giây.

Các phân vùng mạng

Nếu bảng của bạn không có đủ nguồn điện hoặc kết nối radio giữa các mạng đó yếu, thì mạng Thread có thể chia thành các phân vùng và bạn có thể có nhiều thiết bị hiển thị là Trưởng nhóm.

Chuỗi là chuỗi tự phục hồi, do đó, các phân vùng cuối cùng sẽ hợp nhất lại vào một phân vùng duy nhất với một Lãnh đạo.

14. Minh họa: Gửi nội dung đa phương tiện UDP

Nếu tiếp tục ở bài tập trước, bạn sẽ không thấy đèn LED4 trên bất kỳ thiết bị nào.

Chọn bảng bất kỳ rồi nhấn Nút 1. LED4 trên tất cả các bảng khác trong mạng Thread chạy ứng dụng sẽ chuyển đổi trạng thái của chúng. Nếu tiếp tục từ bài tập trước, tính năng này sẽ bật.

f186a2618fdbe3fd.png

Nhấn lại Nút 1 cho cùng một bảng. Đèn LED4 trên tất cả các bảng khác sẽ bật lại.

Hãy nhấn Nút1 trên một bảng khác và quan sát cách đèn LED4 bật/tắt trên các bảng khác. Nhấn Nút 1 trên một trong các bảng mà đèn LED4 hiện đang bật. Đèn LED4 vẫn bật trên bảng đó nhưng bật/tắt các thiết bị khác.

f5865ccb8ab7aa34.png

Các phân vùng mạng

Nếu các bảng của bạn đã được phân vùng và có nhiều hơn một Người dẫn đầu, thì kết quả của thông báo đa hướng sẽ khác nhau giữa các bảng. Nếu bạn nhấn Nút1 trên một bảng có phân vùng (và do đó là thành viên duy nhất của mạng Chuỗi được phân vùng), LED4 trên các bảng khác sẽ không sáng khi phản hồi. Nếu điều này xảy ra, hãy đặt lại các bảng đó. Tốt nhất là bạn sẽ định dạng lại một mạng Thread và thông báo UDP sẽ hoạt động chính xác.

15. Xin chúc mừng!

Bạn đã tạo một ứng dụng sử dụng API OpenThread!

Hiện tại, bạn đã biết:

  • Cách lập trình các nút và đèn LED trên bảng phát triển Bắc Âu nRF52840
  • Cách sử dụng các API OpenThread phổ biến và lớp otInstance
  • Cách theo dõi và phản ứng với các thay đổi về trạng thái của OpenThread
  • Cách gửi tin nhắn UDP đến tất cả các thiết bị trong mạng Thread
  • Cách sửa đổi Makefiles

Các bước tiếp theo

Xây dựng dựa trên Lớp học lập trình này, hãy thử các bài tập sau:

  • Sửa đổi mô-đun GPIO để dùng chân cắm GPIO thay vì đèn LED tích hợp, đồng thời kết nối đèn LED RGB bên ngoài, làm thay đổi màu sắc dựa trên vai trò của Bộ định tuyến
  • Thêm tùy chọn hỗ trợ GPIO cho một nền tảng mẫu khác
  • Thay vì sử dụng tính năng phát đa hướng để ping tất cả các thiết bị từ một lần nhấn nút, hãy sử dụng API Bộ định tuyến/Chìa khóa để tìm và ping một thiết bị riêng lẻ
  • Kết nối mạng lưới của bạn với Internet bằng cách sử dụng Bộ định tuyến đường viền OpenThread và truyền chúng nhiều lần từ bên ngoài mạng Thread để chiếu sáng các đèn LED

Tài liệu đọc thêm

Hãy tham khảo openthread.ioGitHub để xem nhiều tài nguyên OpenThread, bao gồm:

Tham chiếu: