Разработка с помощью API OpenThread

26b7f4f6b3ea0700.png

OpenThread выпущен Nest является реализацией открытого источника Thread® сетевого протокола. Nest выпустила OpenThread, чтобы сделать технологию, используемую в продуктах Nest, широко доступной для разработчиков, чтобы ускорить разработку продуктов для подключенного дома.

Спецификация Thread определяет IPv6 на основе надежной, безопасной и протокол беспроводной связи с низким энергопотреблением устройство-устройство для домашнего применения. OpenThread реализует все сетевые уровни потоков, включая IPv6, 6LoWPAN, IEEE 802.15.4 с безопасностью MAC, установлением Mesh Link и Mesh Routing.

В этой Codelab вы будете использовать API OpenThread для запуска сети Thread, отслеживания и реагирования на изменения в ролях устройств и отправки сообщений UDP, а также для привязки этих действий к кнопкам и индикаторам на реальном оборудовании.

2a6db2e258c32237.png

Что ты узнаешь

  • Как запрограммировать кнопки и светодиоды на платах разработки Nordic nRF52840
  • Как использовать общие интерфейсы OpenThread и otInstance класс
  • Как отслеживать и реагировать на изменения состояния OpenThread
  • Как отправлять сообщения UDP на все устройства в сети Thread
  • Как изменить Makefile

Что тебе понадобится

Аппаратное обеспечение:

  • 3 платы для разработки Nordic Semiconductor nRF52840
  • 3 кабеля USB - Micro-USB для подключения плат
  • Машина Linux с как минимум 3 USB-портами

Программное обеспечение:

  • Набор инструментов GNU
  • Инструменты командной строки Nordic nRF5x
  • Программное обеспечение Segger J-Link
  • OpenThread
  • Git

Если не указано иное, содержание этой Codelab распространяется под лицензией Creative Commons Attribution 3.0 , а примеры кода под лицензией Apache 2.0 License .

Заполните аппаратную лабораторию кодов

Перед началом этого Codelab, вы должны завершить Постройте Thread сети с nRF52840 советами и OpenThread Codelab, которые:

  • Подробно обо всем программном обеспечении, которое вам нужно для сборки и прошивки
  • Обучает созданию OpenThread и его прошивке на платах Nordic nRF52840.
  • Демонстрирует основы сети потоков.

Никакая настройка среды, необходимая для сборки OpenThread и прошивки плат, не подробно описана в этой Codelab - только базовые инструкции по прошивке плат. Предполагается, что вы уже выполнили кодовую лабораторию Build a Thread Network.

Завершите кодовую лабораторию Build a Thread Network

Машина Linux

Эта Codelab была разработана для использования Linux-машины на базе i386 или x86 для прошивки всех плат разработки Thread. Все шаги были протестированы на Ubuntu 14.04.5 LTS (Trusty Tahr).

Платы Nordic Semiconductor nRF52840

Это Codelab использует три nRF52840 PDK доски .

a6693da3ce213856.png

Установить программное обеспечение

Для сборки и прошивки OpenThread вам необходимо установить SEGGER J-Link, инструменты командной строки nRF5x, ARM GNU Toolchain и различные пакеты Linux. Если вы выполнили команду Build a Thread Network Codelab, как требуется, у вас уже будет все необходимое, установленное. Если нет, завершите эту Codelab, прежде чем продолжить, чтобы убедиться, что вы можете собрать и прошить OpenThread на платах разработки nRF52840.

Завершите кодовую лабораторию Build a Thread Network

OpenThread поставляется с примером кода приложения, который вы можете использовать в качестве отправной точки для этой Codelab.

Клонируем и устанавливаем OpenThread:

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

Общественные APIs OpenThread расположены в ./openthread/include/openthread в хранилище OpenThread. Эти API-интерфейсы обеспечивают доступ к различным функциям и функциям OpenThread как на уровне потоков, так и на уровне платформы для использования в ваших приложениях:

  • Информация об экземпляре OpenThread и управление
  • Сервисы приложений, такие как IPv6, UDP и CoAP
  • Управление сетевыми учетными данными, а также роли уполномоченного и присоединяющегося
  • Управление пограничным маршрутизатором
  • Расширенные функции, такие как наблюдение за детьми и обнаружение замятий.

Справочная информация по всем API , OpenThread доступны на 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 и позволяет пользователю выполнять вызовы 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 макрос из API OpenThread для подавления ошибок сборки вокруг неиспользуемых переменных для некоторых компилированных инструментов. Мы увидим примеры этого позже.

В предыдущем шаге, мы перешли на платформу конкретных объявлений функций в ./openthread/examples/platforms/openthread-system.h , которые могут быть использованы для GPIO. Чтобы получить доступ к кнопкам и светодиодным индикаторам на платах разработки nRF52840, вам необходимо реализовать эти функции для платформы nRF52840. В этом коде вы добавите функции, которые:

  • Инициализировать контакты и режимы GPIO
  • Контроль напряжения на штыре
  • Включите прерывания GPIO и зарегистрируйте обратный вызов

В ./src/src каталоге создайте файл с именем gpio.c . В этот новый файл добавьте следующее содержимое.

./src/src/gpio.c (НОВЫЙ ФАЙЛ)

ДЕЙСТВИЕ: Добавить определяет

Эти определения служат абстракциями между значениями, специфичными для nRF52840, и переменными, используемыми на уровне приложения OpenThread.

/**
 * @file
 *   This file implements the system abstraction for GPIO and GPIOTE.
 *
 */

#define BUTTON_GPIO_PORT 0x50000300UL
#define BUTTON_PIN 11 // button #1

#define GPIO_LOGIC_HI 0
#define GPIO_LOGIC_LOW 1

#define LED_GPIO_PORT 0x50000300UL
#define LED_1_PIN 13 // turn on to indicate leader role
#define LED_2_PIN 14 // turn on to indicate router role
#define LED_3_PIN 15 // turn on to indicate child role
#define LED_4_PIN 16 // turn on to indicate UDP receive

Для получения дополнительной информации о кнопках и светодиодах nRF52840 см Nordic Semiconductor Инфоцентр .

ДЕЙСТВИЕ: Добавить заголовок включает

Затем добавьте заголовок, который вам понадобится для функциональности GPIO.

/* Header for the functions defined here */
#include "openthread-system.h"

#include <string.h>

/* Header to access an OpenThread instance */
#include <openthread/instance.h>

/* Headers for lower-level nRF52840 functions */
#include "platform-nrf5.h"
#include "hal/nrf_gpio.h"
#include "hal/nrf_gpiote.h"
#include "nrfx/drivers/include/nrfx_gpiote.h"

ДЕЙСТВИЕ: Добавьте функции обратного вызова и прерывания для кнопки 1.

Затем добавьте этот код. in_pin1_handler функция обратного вызова , который зарегистрирован , когда функция нажмите кнопку инициализации (позже в этом файле).

Обратите внимание , как этот обратный вызов использует OT_UNUSED_VARIABLE макрос, как переменные , передаваемые in_pin1_handler не на самом деле используются в функции.

/* Declaring callback function for button 1. */
static otSysButtonCallback sButtonHandler;
static bool                sButtonPressed;

/**
 * @brief Function to receive interrupt and call back function
 * set by the application for button 1.
 *
 */
static void in_pin1_handler(uint32_t pin, nrf_gpiote_polarity_t action)
{
    OT_UNUSED_VARIABLE(pin);
    OT_UNUSED_VARIABLE(action);
    sButtonPressed = true;
}

ДЕЙСТВИЕ: Добавьте функцию для настройки светодиодов.

Добавьте этот код, чтобы настроить режим и состояние всех светодиодов во время инициализации.

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

ДЕЙСТВИЕ: Добавьте функцию для установки режима светодиода.

Эта функция будет использоваться при изменении роли устройства.

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

ДЕЙСТВИЕ: Добавьте функцию для переключения режима светодиода.

Эта функция будет использоваться для переключения светодиода 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 файл.

В нашем приложении мы хотим, чтобы горели разные светодиоды в зависимости от роли устройства. Отследим следующие роли: лидер, маршрутизатор, конечное устройство. Мы можем назначить их светодиодам так:

  • LED1 = Лидер
  • LED2 = Маршрутизатор
  • LED3 = Конечное устройство

Чтобы включить эту функцию, приложению необходимо знать, когда изменилась роль устройства и как в ответ включить соответствующий светодиод. Мы будем использовать экземпляр OpenThread для первой части и абстракцию платформы GPIO для второй.

Откройте ./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 и если он изменился, оказывается светодиоды включения / выключения в случае необходимости.

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

В нашем приложении мы также хотим отправлять UDP-сообщения всем другим устройствам в сети, когда Button1 нажата на одной плате. Чтобы подтвердить получение сообщения, мы переключим LED4 на других платах в ответ.

Чтобы включить эту функцию, приложению необходимо:

  • Инициализировать UDP-соединение при запуске
  • Возможность отправлять UDP-сообщение на многоадресный адрес локальной сети.
  • Обработка входящих сообщений 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 макросов , которые VALIDATE условий времени выполнения и корректно обрабатывать ошибки.

ДЕЙСТВИЕ: Добавьте определения и константы:

В 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 является сетка локальной групповой адрес. Любые сообщения, отправленные на этот адрес, будут отправлены на все полнопоточные устройства в сети. См Multicast на openthread.io для получения дополнительной информации о групповых поддержки в OpenThread.

ДЕЙСТВИЕ: Добавить объявления функций

В main.c файл, после otTaskletsSignalPending определения и перед main() функцией, добавьте UDP-специфические функции, а также статическую переменный для представления сокета UDP:

static void initUdp(otInstance *aInstance);
static void sendUdp(otInstance *aInstance);

static void handleButtonInterrupt(otInstance *aInstance);

void handleUdpReceive(void *aContext, otMessage *aMessage, 
                      const otMessageInfo *aMessageInfo);

static otUdpSocket sUdpSocket;

ДЕЙСТВИЕ: Добавьте вызовы для инициализации светодиодов GPIO и кнопки

В 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 Reference .

ДЕЙСТВИЕ: Реализуйте обмен сообщениями 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 блока, где он освобождает буфер.

См IPv6 и UDP Ссылки на openthread.io для получения дополнительной информации о функциях , используемых для инициализации UDP.

ДЕЙСТВИЕ: Реализовать обработку сообщений 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);
}

Для простоты демонстрации мы хотим, чтобы наши устройства немедленно запускали поток и объединялись в сеть при включении. Для этого мы будем использовать otOperationalDataset структуру. Эта структура содержит все параметры, необходимые для передачи учетных данных сети Thread на устройство.

Использование этой структуры переопределит сетевые настройки по умолчанию, встроенные в OpenThread, чтобы сделать наше приложение более безопасным и ограничить узлы Thread в нашей сети только теми, на которых запущено приложение.

Снова откройте ./openthread/examples/apps/cli/main.c файл в текстовом редакторе.

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

ДЕЙСТВИЕ: Добавить заголовок, включить

Внутри включает раздел в верхней части main.c файл, добавьте файл заголовка API вам нужно настроить сеть Thread:

#include <openthread/dataset_ftd.h>

ДЕЙСТВИЕ: Добавить объявление функции для настройки конфигурации сети.

Добавить это объявление в main.c , после того, как заголовок включает и перед любым #if заявления. Эта функция будет определена после основной функции приложения.

static void setNetworkConfiguration(otInstance *aInstance);

ДЕЙСТВИЕ: Добавьте вызов конфигурации сети

В main.c добавить вызов этой функции в main() функции после otSetStateChangedCallback вызова. Эта функция настраивает набор сетевых данных Thread.

/* 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                      = 1;
    aDataset.mComponents.mIsActiveTimestampPresent = true;

    /* Set Channel to 15 */
    aDataset.mChannel                      = 15;
    aDataset.mComponents.mIsChannelPresent = true;

    /* Set Pan ID to 2222 */
    aDataset.mPanId                      = (otPanId)0x2222;
    aDataset.mComponents.mIsPanIdPresent = true;

    /* Set Extended Pan ID to C0DE1AB5C0DE1AB5 */
    uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xC0, 0xDE, 0x1A, 0xB5, 0xC0, 0xDE, 0x1A, 0xB5};
    memcpy(aDataset.mExtendedPanId.m8, extPanId, sizeof(aDataset.mExtendedPanId));
    aDataset.mComponents.mIsExtendedPanIdPresent = true;

    /* Set 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 ID = C0DE1AB5C0DE1AB5
  • Сетевой ключ = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Имя сети = OTCodelab

Кроме того, здесь мы уменьшаем джиттер выбора маршрутизатора, чтобы наши устройства быстрее меняли роли в демонстрационных целях. Обратите внимание, что это делается только в том случае, если узел является FTD (Full Thread Device). Подробнее об этом в следующем шаге.

Некоторые из API-интерфейсов OpenThread изменяют настройки, которые следует изменять только в демонстрационных или тестовых целях. Эти API-интерфейсы не следует использовать в производственном развертывании приложения с использованием OpenThread.

Например, otThreadSetRouterSelectionJitter функция регулирует время (в секундах), необходимые для конечного устройства для продвижения себя к маршрутизатору. По умолчанию это значение равно 120 в соответствии со спецификацией потока. Для простоты использования в этой Codelab мы собираемся изменить его на 20, чтобы вам не пришлось долго ждать, пока узел Thread сменит роли.

Примечание: MTD устройства не станут маршрутизаторы, а также поддержка функции , как otThreadSetRouterSelectionJitter не входит в МПД сборки. Позже нам нужно указать 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

Перед сборкой приложения необходимо выполнить несколько незначительных обновлений для трех файлов CMake. Они используются системой сборки для компиляции и компоновки вашего приложения.

./third_party/NordicSemiconductor/CMakeLists.txt

Теперь добавьте некоторые флаги в NordicSemiconductor CMakeLists.txt , чтобы обеспечить функции GPIO определены в приложении.

ДЕЙСТВИЕ: Добавьте флаги в CMakeLists.txt

Открыть ./third_party/NordicSemiconductor/CMakeLists.txt в предпочитаемом текстовом редакторе и добавьте следующие строки в COMMON_FLAG разделе.

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

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

...

./src/CMakeLists.txt

Теперь добавьте новый gpio.c файл в ./src/CMakeLists.txt файл.

ДЕЙСТВИЕ: Добавьте источник gpio в

./src/CMakeLists.txt

файл

Открыть ./src/CMakeLists.txt в предпочитаемом текстовом редакторе и добавьте файл в NRF_COMM_SOURCES раздел.

...

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

...

./third_party/NordicSemiconductor/CMakeLists.txt

Наконец, добавьте nrfx_gpiote.c файл драйвера в NordicSemiconductor CMakeLists.txt файл, так что он входит в состав сборки библиотеки Северных драйверов.

ДЕЙСТВИЕ: Добавьте драйвер gpio в файл NordicSemiconductor CMakeLists.txt.

Открыть ./third_party/NordicSemiconductor/CMakeLists.txt в предпочитаемом текстовом редакторе и добавьте файл в COMMON_SOURCES раздел.

...

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

После завершения всех обновлений кода вы готовы собрать и прошить приложение на всех трех платах разработки 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 - кабель к порту отладки Micro-USB , рядом с внешним блоком питания штифта на плате nRF52840, а затем подключить его к машине Linux. Установите правильно, LED5 включен.

20a3b4b480356447.png

Как и раньше, обратите внимание на серийный номер платы nRF52840:

c00d519ebec7e5f0.jpeg

Перейдите к расположению инструментов командной строки nRFx и запрограммируйте шестнадцатеричный файл OpenThread CLI FTD на плату nRF52840, используя серийный номер платы:

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

LED5 погаснет на короткое время во время мигания. В случае успеха создается следующий результат:

Parsing hex file.
Erasing user available code and UICR flash areas.
Applying system reset.
Checking that the area to write is not protected.
Programing device.
Applying system reset.
Run.

Повторите этот шаг «прошить доски» для двух других плат. Каждая плата должна быть подключена к машине Linux одинаково, и команда для прошивки такая же, за исключением серийного номера платы. Обязательно используйте уникальный серийный номер каждой платы в

nrfjprog мигание команды.

В случае успеха на каждой плате будут гореть LED1, LED2 или LED3. Вы даже можете увидеть горящий светодиодный переключатель с 3 на 2 (или с 2 на 1) вскоре после мигания (функция изменения роли устройства).

Все три платы nRF52840 теперь должны быть включены и работать с нашим приложением OpenThread. Как указывалось ранее, это приложение имеет две основные функции.

Индикаторы роли устройства

Горящий светодиод на каждой плате отражает текущую роль узла потока:

  • LED1 = Лидер
  • LED2 = Маршрутизатор
  • LED3 = Конечное устройство

По мере изменения роли меняется и светящийся светодиод. Вы уже должны были увидеть эти изменения на одной или двух платах в течение 20 секунд после включения каждого устройства.

UDP Multicast

Когда кнопка Button1 нажата на плате, UDP-сообщение отправляется на локальный многоадресный адрес ячейки, который включает все другие узлы в сети Thread. В ответ на прием этого сообщения, СД4 на все остальные доски включает или выключает. LED4 остается включенным или выключенным для каждой платы, пока не получит другое сообщение UDP.

203dd094acca1f97.png

9bbd96d9b1c63504.png

Прошитые вами устройства представляют собой особый вид полнопоточного устройства (FTD), называемого конечным устройством, подходящим для маршрутизатора (REED). Это означает, что они могут функционировать как маршрутизатор или как конечное устройство и могут продвигаться с конечного устройства на маршрутизатор.

Thread может поддерживать до 32 маршрутизаторов, но пытается сохранить количество маршрутизаторов от 16 до 23. Если REED подключается как конечное устройство, а количество маршрутизаторов меньше 16, он автоматически продвигается к маршрутизатору. Это изменение должно происходить в случайный момент времени в пределах количества секунд , что вы установить otThreadSetRouterSelectionJitter значение в приложении (20 секунд).

Каждая сеть Thread также имеет лидера, который представляет собой маршрутизатор, который отвечает за управление набором маршрутизаторов в сети Thread. Когда все устройства включены, через 20 секунд одно из них должно стать лидером (светодиод 1 включен), а два других - маршрутизатором (светодиод 2 включен).

4e1e885861a66570.png

Удалить лидера

Если лидер удаляется из сети потока, другой маршрутизатор продвигается до лидера, чтобы гарантировать, что в сети все еще есть лидер.

Выключите Leader питание (один с LED1 горит) с помощью переключателя питания. Подождите около 20 секунд. На одной из двух оставшихся плат LED2 (Маршрутизатор) выключится, а LED1 (Ведущий) включится. Это устройство теперь является лидером сети потоков.

4c57c87adb40e0e3.png

Снова включите исходную таблицу лидеров. Он должен автоматически присоединиться к сети потоков в качестве конечного устройства (светодиод 3 горит). В течение 20 секунд (джиттер выбора маршрутизатора) он переходит в маршрутизатор (светодиод 2 горит).

5f40afca2dcc4b5b.png

Сбросьте платы

Выключите все три платы, затем снова включите их и посмотрите на светодиоды. Первая плата, на которую было подано питание, должна быть запущена в роли лидера (светодиод 1 горит) - первый маршрутизатор в сети потоков автоматически становится лидером.

Две другие платы изначально подключаются к сети как конечные устройства (светодиод 3 горит), но должны продвигаться к маршрутизаторам (светодиод 2 горит) в течение 20 секунд.

Сетевые разделы

Если ваши платы не получают достаточного питания или радиосвязь между ними слабая, сеть потоков может разделиться на разделы, и у вас может быть более одного устройства, отображаемого как ведущее.

Поток является самовосстанавливающимся, поэтому в конечном итоге разделы должны снова объединиться в один раздел с одним лидером.

Если продолжить с предыдущего упражнения, светодиод LED4 не должен гореть ни на одном устройстве.

Выберите любую доску и нажмите кнопку Button1. LED4 на всех других платах в сети Thread, в которой запущено приложение, должен переключать свое состояние. Если продолжить предыдущее упражнение, они должны быть включены.

f186a2618fdbe3fd.png

Снова нажмите кнопку Button1 для той же платы. LED4 на всех остальных платах должен снова переключиться.

Нажмите кнопку Button1 на другой плате и посмотрите, как LED4 переключается на других платах. Нажмите кнопку Button1 на одной из плат, на которой в данный момент горит светодиод 4. LED4 остается включенным для этой платы, но переключается на другие.

f5865ccb8ab7aa34.png

Сетевые разделы

Если ваши доски разделены и среди них больше одного лидера, результат многоадресного сообщения будет отличаться для разных доск. Если вы нажмете кнопку Button1 на плате, которая разделена (и, таким образом, является единственным участником разделенной сети потоков), LED4 на других платах не загорится в ответ. Если это произойдет, перезагрузите платы - в идеале они реформируют сеть с одним потоком, и обмен сообщениями UDP должен работать правильно.

Вы создали приложение, использующее API OpenThread!

Теперь вы знаете:

  • Как запрограммировать кнопки и светодиоды на платах разработки Nordic nRF52840
  • Как использовать общие интерфейсы OpenThread и otInstance класс
  • Как отслеживать и реагировать на изменения состояния OpenThread
  • Как отправлять сообщения UDP на все устройства в сети Thread
  • Как изменить Makefile

Следующие шаги

Основываясь на этой Codelab, попробуйте выполнить следующие упражнения:

  • Измените модуль GPIO, чтобы использовать контакты GPIO вместо встроенных светодиодов, и подключите внешние светодиоды RGB, которые меняют цвет в зависимости от роли маршрутизатора.
  • Добавить поддержку GPIO для другой примерной платформы
  • Вместо того , чтобы использовать групповой свистеть все устройства от нажатия кнопки, используйте API маршрутизатора / Leader , чтобы найти и свистеть отдельное устройство
  • Подключите ячеистую сеть к Интернету с помощью OpenThread пограничного маршрутизатора и многоадресного их из внешней сети Thread на свет светодиодов

дальнейшее чтение

Проверьте openthread.io и GitHub для различных OpenThread ресурсов, в том числе:

Ссылка: