Google is committed to advancing racial equity for Black communities. See how.
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Programowanie za pomocą interfejsów API OpenThread

26b7f4f6b3ea0700.png

OpenThread wydany przez Nest to implementacja protokołu sieciowego Thread® o otwartym kodzie źródłowym. Nest wypuściło OpenThread, aby udostępnić programistom technologię stosowaną w produktach Nest w celu przyspieszenia rozwoju produktów dla inteligentnego domu.

Specyfikacja Thread definiuje oparty na protokole IPv6 niezawodny, bezpieczny i energooszczędny protokół komunikacji bezprzewodowej urządzenie-urządzenie do zastosowań domowych. OpenThread implementuje wszystkie warstwy sieciowe wątków, w tym IPv6, 6LoWPAN, IEEE 802.15.4 z zabezpieczeniami MAC, Mesh Link Establishment i Mesh Routing.

W tym Codelab będziesz używać interfejsów API OpenThread do uruchamiania sieci Thread, monitorowania i reagowania na zmiany ról urządzeń oraz wysyłania komunikatów UDP, a także powiązania tych działań z przyciskami i diodami LED na rzeczywistym sprzęcie.

2a6db2e258c32237.png

Czego się nauczysz

  • Jak zaprogramować przyciski i diody LED na płytach dev Nordic nRF52840
  • Jak korzystać z typowych interfejsów API OpenThread i klasy otInstance
  • Jak monitorować i reagować na zmiany stanu OpenThread
  • Jak wysyłać wiadomości UDP do wszystkich urządzeń w sieci Thread
  • Jak modyfikować pliki Makefiles

Co będziesz potrzebował

Sprzęt komputerowy:

  • 3 płytki dev Nordic Semiconductor nRF52840
  • 3 kable USB do Micro-USB do podłączenia płyt
  • Maszyna Linux z co najmniej 3 portami USB

Oprogramowanie:

  • GNU Toolchain
  • Narzędzia wiersza poleceń Nordic nRF5x
  • Oprogramowanie Segger J-Link
  • OpenThread
  • Git

O ile nie zaznaczono inaczej, zawartość tego Codelab jest objęta licencją na podstawie licencji Creative Commons Attribution 3.0 , a próbki kodu są objęte licencją na podstawie licencji Apache 2.0 .

Ukończ Hardware Codelab

Przed uruchomieniem tego Codelab, powinieneś ukończyć tworzenie sieci wątków za pomocą płyt nRF52840 i OpenThread Codelab, które:

  • Wyszczególnia całe oprogramowanie potrzebne do tworzenia i flashowania
  • Uczy, jak zbudować OpenThread i sflashować go na płytach Nordic nRF52840
  • Pokazuje podstawy sieci Thread

Żadne środowisko wymagane do zbudowania OpenThread i flashowania płyt nie jest szczegółowo opisane w tym Codelab - tylko podstawowe instrukcje dotyczące flashowania płyt. Zakłada się, że ukończyłeś już Codelab tworzenia sieci wątków.

Wypełnij Codelab tworzenia sieci wątków

Maszyna z systemem Linux

Ten Codelab został zaprojektowany do używania maszyny Linux z procesorem i386 lub x86 do flashowania wszystkich płyt programistycznych Thread. Wszystkie kroki zostały przetestowane na Ubuntu 14.04.5 LTS (Trusty Tahr).

Płyty Nordic Semiconductor nRF52840

Ten Codelab wykorzystuje trzy płyty nRF52840 PDK .

a6693da3ce213856.png

Zainstaluj oprogramowanie

Aby zbudować i flashować OpenThread, musisz zainstalować SEGGER J-Link, narzędzia wiersza poleceń nRF5x, ARM GNU Toolchain i różne pakiety Linuksa. Jeśli ukończyłeś procedurę Build a Thread Network Codelab zgodnie z wymaganiami, wszystko, czego potrzebujesz, jest już zainstalowane. Jeśli nie, ukończ tę Codelab, zanim przejdziesz dalej, aby upewnić się, że możesz tworzyć i flashować karty deweloperskie OpenThread na nRF52840.

Ukończ Codelab tworzenia sieci wątków

OpenThread zawiera przykładowy kod aplikacji, którego możesz użyć jako punktu wyjścia dla tego Codelab.

Sklonuj i zainstaluj OpenThread:

$ cd ~
$ git clone --recursive https://github.com/openthread/openthread.git
$ cd openthread
$ ./bootstrap

Publiczne interfejsy API OpenThread znajdują się pod adresem include/openthread w repozytorium OpenThread. Te interfejsy API zapewniają dostęp do różnych funkcji i funkcjonalności OpenThread zarówno na poziomie wątku, jak i platformy do użytku w aplikacjach:

  • Informacje i sterowanie instancją OpenThread
  • Usługi aplikacji, takie jak IPv6, UDP i CoAP
  • Zarządzanie poświadczeniami sieciowymi wraz z rolami komisarza i stolarza
  • Zarządzanie routerem granicznym
  • Ulepszone funkcje, takie jak nadzór dzieci i wykrywanie zacięć

Informacje referencyjne na temat wszystkich interfejsów API OpenThread są dostępne pod adresem openthread.io/reference .

Korzystanie z interfejsu API

Aby użyć interfejsu API, dołącz jego plik nagłówkowy do jednego z plików aplikacji. Następnie wywołaj żądaną funkcję.

Na przykład przykładowa aplikacja CLI dołączona do OpenThread używa następujących nagłówków API:

przykłady / 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>

Wystąpienie OpenThread

Struktura otInstance jest często używana podczas pracy z interfejsami API OpenThread. Po zainicjowaniu struktura ta reprezentuje statyczne wystąpienie biblioteki OpenThread i umożliwia użytkownikowi wykonywanie wywołań API OpenThread.

Na przykład instancja OpenThread jest inicjowana w funkcji main() przykładowej aplikacji CLI:

przykłady / 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;
}

Funkcje specyficzne dla platformy

Jeśli chcesz dodać funkcje specyficzne dla platformy do jednej z przykładowych aplikacji zawartych w OpenThread, najpierw zadeklaruj je w nagłówku examples/platforms/openthread-system.h , używając przestrzeni nazw otSys dla wszystkich funkcji. Następnie zaimplementuj je w pliku źródłowym specyficznym dla platformy. Podsumowując w ten sposób, możesz użyć tych samych nagłówków funkcji dla innych przykładowych platform.

Na przykład funkcje GPIO, których będziemy używać do podpięcia się do przycisków i diod nRF52840, muszą być zadeklarowane w openthread-system.h .

Otwórz plik examples/platforms/openthread-system.h w preferowanym edytorze tekstu.

przykłady / platformy / openthread-system.h

DZIAŁANIE: Dodaj deklaracje funkcji GPIO specyficzne dla platformy

Dodaj te deklaracje funkcji w dowolnym miejscu w pliku:

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

Zaimplementujemy je w następnym kroku.

Zauważ, że deklaracja funkcji otSysButtonProcess używa otInstance . W ten sposób aplikacja może uzyskać dostęp do informacji o instancji OpenThread po naciśnięciu przycisku, jeśli to konieczne. Wszystko zależy od potrzeb Twojej aplikacji. Jeśli nie potrzebujesz tego w implementacji funkcji, możesz użyć makra OT_UNUSED_VARIABLE z interfejsu API OpenThread, aby wyeliminować błędy kompilacji wokół nieużywanych zmiennych dla niektórych łańcuchów narzędzi. Przykłady tego zobaczymy później.

W poprzednim kroku omówiliśmy deklaracje funkcji specyficzne dla platformy w examples/platforms/openthread-system.h które mogą być używane w GPIO. Aby uzyskać dostęp do przycisków i diod LED na płytach dev nRF52840, musisz zaimplementować te funkcje dla platformy nRF52840. W tym kodzie dodasz funkcje, które:

  • Zainicjuj piny i tryby GPIO
  • Kontroluj napięcie na pinie
  • Włącz przerwania GPIO i zarejestruj oddzwonienie

W katalogu examples/platforms/nrf528xx/src utwórz plik o nazwie gpio.c W tym nowym pliku dodaj następującą zawartość.

przykłady / platformy / nrf528xx / src / gpio.c (NOWY PLIK)

DZIAŁANIE: Dodaj definicje

Te definicje służą jako abstrakcje między wartościami specyficznymi dla nRF52840 a zmiennymi używanymi na poziomie aplikacji 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

Więcej informacji na temat przycisków i diod LED nRF52840 można znaleźć w Nordic Semiconductor Infocenter .

DZIAŁANIE: Dodaj nagłówek zawiera

Następnie dodaj nagłówek zawierający funkcje 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"

DZIAŁANIE: Dodaj funkcje oddzwaniania i przerywania dla przycisku 1

Dodaj ten kod dalej. Funkcja in_pin1_handler to wywołanie zwrotne, które jest rejestrowane po zainicjowaniu funkcji naciśnięcia przycisku (w dalszej części tego pliku).

Zwróć uwagę, jak to wywołanie zwrotne używa makra OT_UNUSED_VARIABLE , ponieważ zmienne przekazywane do in_pin1_handler nie są w rzeczywistości używane w funkcji.

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

DZIAŁANIE: Dodaj funkcję do konfiguracji diod LED

Dodaj ten kod, aby skonfigurować tryb i stan wszystkich diod LED podczas inicjalizacji.

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

DZIAŁANIE: Dodaj funkcję, aby ustawić tryb diody LED.

Ta funkcja będzie używana w przypadku zmiany roli urządzenia.

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

DZIAŁANIE: Dodaj funkcję przełączania trybu diody LED.

Ta funkcja będzie używana do przełączania diody LED4, gdy urządzenie otrzyma wiadomość multiemisji 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;
    }
}

DZIAŁANIE: Dodaj funkcje do inicjalizacji i przetwarzania naciśnięć przycisków.

Pierwsza funkcja inicjuje płytkę po naciśnięciu przycisku, a druga wysyła wiadomość multicast UDP po naciśnięciu przycisku 1.

/**
 * @brief Function to toggle the mode of an LED.
 */
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);
    }
}

DZIAŁANIE: Zapisz i zamknij plik gpio.c

W naszej aplikacji chcemy, aby różne diody świeciły się w zależności od roli urządzenia. Prześledźmy następujące role: Lider, Router, Urządzenie końcowe. Możemy je przypisać do diod LED w następujący sposób:

  • LED1 = Lider
  • LED2 = router
  • LED3 = urządzenie końcowe

Aby włączyć tę funkcję, aplikacja musi wiedzieć, kiedy zmieniła się rola urządzenia i jak w odpowiedzi włączyć odpowiednią diodę LED. W pierwszej części użyjemy instancji OpenThread, aw drugiej abstrakcji platformy GPIO.

Otwórz plik examples/apps/cli/main.c w preferowanym edytorze tekstu.

przykłady / apps / cli / main.c

DZIAŁANIE: Dodaj nagłówek zawiera

W sekcji include w pliku main.c dodaj pliki nagłówkowe API, które będą potrzebne do funkcji zmiany roli.

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

DZIAŁANIE: Dodaj deklarację funkcji obsługi dla zmiany stanu instancji OpenThread

Dodaj tę deklarację do main.c , po #IF nagłówka i przed wszelkimi instrukcjami #IF . Ta funkcja zostanie zdefiniowana po aplikacji głównej.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

DZIAŁANIE: Dodaj rejestrację wywołania zwrotnego dla funkcji obsługi zmiany stanu

W main.c dodaj tę funkcję do funkcji main() po wywołaniu otCliUartInit . Ta rejestracja wywołania zwrotnego mówi OpenThread, aby wywoływał funkcję handleNetifStateChange za każdym razem, gdy zmieni się stan wystąpienia OpenThread.

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

DZIAŁANIE: Dodaj implementację zmiany stanu

W main.c , po funkcji main() , zaimplementuj funkcję handleNetifStateChanged . Kontrole tej funkcji OT_CHANGED_THREAD_ROLE Flaga instancji OpenThread a jeśli to się nie zmieniło, włącza diody LED on / off, ile potrzeba.

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

W naszej aplikacji chcemy również wysyłać wiadomości UDP do wszystkich innych urządzeń w sieci po naciśnięciu przycisku Button1 na jednej płytce. Aby potwierdzić otrzymanie wiadomości, w odpowiedzi przełączymy LED4 na innych płytach.

Aby włączyć tę funkcję, aplikacja musi:

  • Zainicjuj połączenie UDP po uruchomieniu
  • Możliwość wysyłania wiadomości UDP na lokalny adres multiemisji typu mesh
  • Obsługuj przychodzące wiadomości UDP
  • Przełącz LED4 w odpowiedzi na przychodzące wiadomości UDP

Otwórz plik examples/apps/cli/main.c w wybranym edytorze tekstu.

przykłady / apps / cli / main.c

DZIAŁANIE: Dodaj nagłówek zawiera

W sekcji main.c u góry pliku main.c dodaj pliki nagłówkowe API, które będą potrzebne do funkcji multiemisji UDP.

#include <string.h>

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

#include "utils/code_utils.h"

Nagłówek code_utils.h jest używany dla makr otEXPECT i otEXPECT_ACTION które sprawdzają warunki czasu wykonywania i wdzięcznie obsługują błędy.

DZIAŁANIE: dodaj definicje i stałe:

W pliku main.c , po sekcji include i przed wszelkimi instrukcjami #IF , dodaj stałe specyficzne dla UDP i #IF :

#define UDP_PORT 1212

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

ff03::1 to lokalny adres multiemisji typu mesh. Wszelkie wiadomości wysyłane na ten adres będą wysyłane do wszystkich urządzeń z pełnym wątkiem w sieci. Zobacz Multicast na openthread.io, aby uzyskać więcej informacji na temat obsługi multiemisji w OpenThread.

DZIAŁANIE: Dodaj deklaracje funkcji

W pliku main.c , po definicji otTaskletsSignalPending i przed funkcją main() , dodaj funkcje specyficzne dla UDP, a także zmienną statyczną reprezentującą gniazdo 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;

DZIAŁANIE: Dodaj wywołania, aby zainicjować diody LED i przycisk GPIO

W main.c , dodaj te wywołania funkcji do funkcji main() po wywołaniu otSetStateChangedCallback . Te funkcje inicjują piny GPIO i GPIOTE oraz ustawiają obsługę przycisku do obsługi zdarzeń naciśnięcia przycisku.

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

DZIAŁANIE: Dodaj wywołanie inicjujące UDP

W main.c dodaj tę funkcję do funkcji main() po wywołaniu otSysButtonInit które właśnie dodałeś:

initUdp(instance);

To wywołanie zapewnia inicjalizację gniazda UDP podczas uruchamiania aplikacji. Bez tego urządzenie nie może wysyłać ani odbierać wiadomości UDP.

DZIAŁANIE: Dodaj wywołanie, aby przetworzyć zdarzenie przycisku GPIO

W main.c , dodaj wywołanie funkcji main() funkcji po otSysProcessDrivers wywołać w while pętli. Ta funkcja, zadeklarowana w gpio.c , sprawdza, czy przycisk został naciśnięty, a jeśli tak, wywołuje procedurę obsługi ( handleButtonInterrupt ), która została ustawiona w powyższym kroku.

otSysButtonProcess(instance);

DZIAŁANIE: Zaimplementuj obsługę przerwań przycisku

W main.c dodaj implementację funkcji handleButtonInterrupt po funkcji handleNetifStateChanged którą dodałeś w poprzednim kroku:

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

DZIAŁANIE: Zaimplementuj inicjalizację UDP

W main.c dodaj implementację funkcji initUdp po handleButtonInterrupt funkcji 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(&sUdpSocket, &listenSockAddr);
}

UDP_PORT to port zdefiniowany wcześniej (1212), a OT_NETIF_INTERFACE_ID_THREAD to identyfikator interfejsu sieciowego wątku. Funkcja otUdpOpen otwiera gniazdo i rejestruje funkcję zwrotną ( handleUdpReceive ) na handleUdpReceive wiadomości UDP.

DZIAŁANIE: Zaimplementuj przesyłanie wiadomości UDP

W main.c dodaj implementację funkcji sendUdp po initUdp funkcji 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(&sUdpSocket, message, &messageInfo);

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

Zwróć uwagę na makra otEXPECT i otEXPECT_ACTION . Zapewniają one, że komunikat UDP jest prawidłowy i prawidłowo przydzielony w buforze, a jeśli nie, funkcja z wdziękiem obsługuje błędy, przechodząc do bloku exit , gdzie zwalnia bufor.

Więcej informacji o funkcjach używanych do inicjowania UDP można znaleźć w dokumentach dotyczących IPv6 i UDP w witrynie openthread.io.

DZIAŁANIE: Zaimplementuj obsługę komunikatów UDP

W main.c dodaj implementację funkcji handleUdpReceive po sendUdp funkcji sendUdp . Ta funkcja po prostu przełącza 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);
}

Aby ułatwić demonstrację, chcemy, aby nasze urządzenia natychmiast uruchamiały wątek i łączyły się w sieć po włączeniu. Aby to zrobić, otOperationalDataset struktury otOperationalDataset . Ta struktura zawiera wszystkie parametry potrzebne do przesłania poświadczeń sieciowych Thread do urządzenia.

Użycie tej struktury spowoduje nadpisanie domyślnych ustawień sieciowych wbudowanych w OpenThread, aby nasza aplikacja była bardziej bezpieczna i ograniczyła węzły Thread w naszej sieci tylko do tych, które ją uruchamiają.

Ponownie otwórz plik examples/apps/cli/main.c w preferowanym edytorze tekstu.

przykłady / apps / cli / main.c

DZIAŁANIE: Dodaj nagłówek include

W sekcji include u góry pliku main.c dodaj plik nagłówkowy API, który będzie potrzebny do skonfigurowania sieci wątków:

#include <openthread/dataset_ftd.h>

DZIAŁANIE: Dodaj deklarację funkcji do ustawiania konfiguracji sieci

Dodaj tę deklarację do main.c , po #IF nagłówka i przed wszelkimi instrukcjami #IF . Ta funkcja zostanie zdefiniowana po funkcji głównej aplikacji.

static void setNetworkConfiguration(otInstance *aInstance);

DZIAŁANIE: Dodaj wywołanie konfiguracji sieci

W main.c dodaj to wywołanie funkcji do funkcji main() po wywołaniu otSetStateChangedCallback . Ta funkcja służy do konfigurowania zestawu danych sieci Thread.

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

DZIAŁANIE: Dodaj wywołania, aby włączyć interfejs sieciowy wątku i stos

W main.c , dodaj te wywołania funkcji do funkcji main() po wywołaniu otSysButtonInit . Ta funkcja służy do konfigurowania zestawu danych sieci Thread.

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

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

DZIAŁANIE: Zaimplementuj konfigurację sieci wątków

W main.c dodaj implementację funkcji setNetworkConfiguration po funkcji 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 Master 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 master key to 1234C0DE1AB51234C0DE1AB51234C0DE */
    uint8_t key[OT_MASTER_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5};
    memcpy(aDataset.mMasterKey.m8, key, sizeof(aDataset.mMasterKey));
    aDataset.mComponents.mIsMasterKeyPresent = 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;

#if OPENTHREAD_FTD
    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);
#else
    OT_UNUSED_VARIABLE(aInstance);
#endif
}

Jak opisano szczegółowo w funkcji, parametry sieci wątków, których używamy w tej aplikacji, to:

  • Kanał = 15
  • Identyfikator PAN = 0x2222
  • Rozszerzony identyfikator PAN = C0DE1AB5C0DE1AB5
  • Główny klucz sieciowy = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Nazwa sieci = OTCodelab

Ponadto w tym miejscu zmniejszamy fluktuację wyboru routera, aby nasze urządzenia szybciej zmieniały role w celach demonstracyjnych. Zauważ, że jest to zrobione tylko wtedy, gdy węzeł jest FTD (Full Thread Device). Więcej o tym w następnym kroku.

Niektóre interfejsy API OpenThread modyfikują ustawienia, które powinny być modyfikowane tylko w celach demonstracyjnych lub testowych. Te interfejsy API nie powinny być używane we wdrożeniach produkcyjnych aplikacji korzystających z OpenThread.

Na przykład funkcja otThreadSetRouterSelectionJitter dostosowuje czas (w sekundach), w którym urządzenie końcowe promuje się do routera. Wartość domyślna to 120, zgodnie ze specyfikacją wątku. Aby ułatwić korzystanie z tego Codelab, zmienimy go na 20, więc nie musisz długo czekać, aż węzeł wątku zmieni role.

Jednak mogłeś zauważyć, że ta funkcja jest wywoływana w ramach dyrektywy preprocesora:

#if OPENTHREAD_FTD
    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);
#else
    OT_UNUSED_VARIABLE(aInstance);
#endif

Ma to zapobiec dodaniu go do kompilacji MTD (Minimal Thread Device). Urządzenia MTD nie stają się routerami, a obsługa funkcji takiej jak otThreadSetRouterSelectionJitter nie jest uwzględniona w kompilacji MTD.

Możesz to potwierdzić, patrząc na definicję funkcji otThreadSetRouterSelectionJitter , która jest również zawarta w dyrektywie preprocesora OPENTHREAD_FTD :

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

Zanim zbudujesz swoją aplikację, potrzeba kilku drobnych aktualizacji dla trzech plików Makefile. Są one używane przez system kompilacji do kompilowania i łączenia aplikacji.

przykłady / apps / cli / Makefile.am

Aby upewnić się, że wszystkie bloki OPENTHREAD_FTD są zawarte w FTD, dodaj je do CPPFLAGS w pliku CLI Makefile.

DZIAŁANIE: Dodaj flagę FTD

Otwórz examples/apps/cli/Makefile.am w preferowanym edytorze tekstu i zaktualizuj sekcję ot_cli_ftd_CPPFLAGS do poniższej:

ot_cli_ftd_CPPFLAGS                                                    = \
    $(CPPFLAGS_COMMON)                                                   \
    -DOPENTHREAD_FTD=1                                                   \
    $(NULL)

przykłady / Makefile-nrf52840

Teraz dodaj kilka flag do pliku Makefile nrf52840, aby upewnić się, że funkcje GPIO są zdefiniowane w aplikacji.

DZIAŁANIE: Dodaj flagi do pliku Makefile nrf52840

Otwórz examples/Makefile-nrf52840 w preferowanym edytorze tekstu i dodaj następujące COMMONCFLAGS po COMMONCFLAGS include ... common-switches.mk :

...

include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk

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

...

przykłady / platformy / nrf528xx / nrf52840 / Makefile.am

Teraz dodaj nowy plik gpio.c do pliku platformy nrf52840 Makefile.am .

DZIAŁANIE: Dodaj źródło gpio do Makefile platformy nrf52840

Otwórz examples/platforms/nrf528xx/nrf52840/Makefile.am w preferowanym edytorze tekstu i dodaj plik na końcu sekcji PLATFORM_COMMON_SOURCES , tuż przed instrukcją $(NULL) . Nie zapomnij umieścić ukośnika odwrotnego na końcu linii!

...

PLATFORM_COMMON_SOURCES
...
src/gpio.c             \
$(NULL)

...

third_party / NordicSemiconductor / Makefile.am

Na koniec dodaj plik sterownika nrfx_gpiote.c do pliku Makefile firmy zewnętrznej NordicSemiconductor, aby był dołączony do biblioteki sterowników skandynawskich.

DZIAŁANIE: Dodaj sterownik gpio do Makefile platformy nrf52840

Otwórz third_party/NordicSemiconductor/Makefile.am w preferowanym edytorze tekstu i dodaj plik na końcu sekcji NORDICSEMI_COMMON_SOURCES , tuż przed instrukcją $(NULL) . Nie zapomnij umieścić ukośnika odwrotnego na końcu linii!

...

NORDICSEMI_COMMON_SOURCES
...
nrfx/drivers/src/nrfx_gpiote.c             \

$(NULL)

...

Po wykonaniu wszystkich aktualizacji kodu jesteś gotowy do zbudowania i flashowania aplikacji na wszystkich trzech płytach deweloperskich Nordic nRF52840. Każde urządzenie będzie działać jako urządzenie z pełnym wątkiem (FTD).

Twórz OpenThread

Zbuduj przykładową platformę nRF52840. Ponieważ pliki Makefile zostały zmienione w poprzednim kroku, przed budowaniem musisz uruchomić skrypt bootstrap :

$ cd ~/openthread
$ ./bootstrap
$ make -f examples/Makefile-nrf52840

Przejdź do katalogu z plikiem binarnym OpenThread FTD CLI i przekonwertuj go na format szesnastkowy za pomocą wbudowanego łańcucha narzędzi ARM:

$ cd ~/openthread/output/nrf52840/bin
$ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex

Flashuj tablice

ot-cli-ftd.hex plik ot-cli-ftd.hex do każdej karty nRF52840.

Podłącz kabel USB do portu debugowania Micro-USB obok zewnętrznego styku zasilania na płycie nRF52840, a następnie podłącz go do komputera z systemem Linux. Ustaw prawidłowo, dioda LED5 świeci .

20a3b4b480356447.png

Tak jak poprzednio, zanotuj numer seryjny karty nRF52840:

c00d519ebec7e5f0.jpeg

Przejdź do lokalizacji narzędzi wiersza poleceń nRFx i zapisz plik szesnastkowy OpenThread CLI FTD na płycie nRF52840, używając numeru seryjnego karty:

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

Podczas migania dioda LED5 na krótko wyłączy się. Po sukcesie generowany jest następujący wynik:

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.

Powtórz ten krok „Zapamiętaj plansze” dla pozostałych dwóch plansz. Każda płyta powinna być podłączona do komputera z Linuksem w ten sam sposób, a polecenie flashowania jest takie samo, z wyjątkiem numeru seryjnego płyty. Upewnij się, że używasz unikalnego numeru seryjnego każdej karty w poleceniu flashowania nrfjprog .

Jeśli się powiedzie, na każdej płycie zaświeci się albo LED1, LED2 lub LED3. Możesz nawet zobaczyć zapaloną diodę LED z 3 na 2 (lub 2 na 1) wkrótce po miganiu (funkcja zmiany roli urządzenia).

Wszystkie trzy płyty nRF52840 powinny teraz być zasilane i uruchamiać naszą aplikację OpenThread. Jak opisano wcześniej, ta aplikacja ma dwie podstawowe funkcje.

Wskaźniki roli urządzenia

Zapalona dioda LED na każdej płycie odzwierciedla aktualną rolę węzła Thread:

  • LED1 = Lider
  • LED2 = router
  • LED3 = urządzenie końcowe

Wraz ze zmianą roli zmienia się też zapalona dioda LED. Powinieneś już zobaczyć te zmiany na tablicy lub dwóch w ciągu 20 sekund od włączenia każdego urządzenia.

UDP Multicast

Po naciśnięciu przycisku Button1 na tablicy, wiadomość UDP jest wysyłana na lokalny adres multiemisji typu mesh, który obejmuje wszystkie inne węzły w sieci Thread. W odpowiedzi na otrzymanie tej wiadomości, LED4 na wszystkich pozostałych płytach włącza się lub wyłącza . LED4 pozostaje włączony lub wyłączony dla każdej płyty, dopóki nie otrzyma kolejnego komunikatu UDP.

203dd094acca1f97.png

9bbd96d9b1c63504.png

Urządzenia, które sflashowałeś, są specyficznym rodzajem urządzenia pełnowątkowego (FTD), zwanym urządzeniem końcowym kwalifikującym się do routera (REED). Oznacza to, że mogą działać jako router lub urządzenie końcowe i mogą promować się z urządzenia końcowego do routera.

Wątek może obsługiwać do 32 routerów, ale stara się utrzymać liczbę routerów w zakresie od 16 do 23. Jeśli urządzenie REED jest podłączone jako urządzenie końcowe, a liczba routerów jest mniejsza niż 16, automatycznie awansuje do routera. Ta zmiana powinna nastąpić w losowym czasie w ciągu liczby sekund ustawionej dla wartości otThreadSetRouterSelectionJitter w aplikacji (20 sekund).

Każda sieć Thread ma również lidera, który jest routerem odpowiedzialnym za zarządzanie zestawem routerów w sieci Thread. Gdy wszystkie urządzenia są włączone, po 20 sekundach jedno z nich powinno być Leaderem (świeci się dioda LED1), a dwa pozostałe routerami (świeci dioda LED2).

4e1e885861a66570.png

Usuń lidera

Jeśli lider zostanie usunięty z sieci Thread, inny router awansuje na lidera, aby upewnić się, że sieć nadal ma lidera.

Wyłącz tablicę liderów (tę z zapaloną diodą LED1) za pomocą wyłącznika zasilania . Poczekaj około 20 sekund. Na jednej z pozostałych dwóch kart, LED2 (Router) wyłączy się, a LED1 (Leader) zaświeci się. To urządzenie jest teraz liderem sieci Thread.

4c57c87adb40e0e3.png

Włącz ponownie oryginalną tablicę liderów. Powinien automatycznie ponownie dołączyć do sieci Thread jako urządzenie końcowe (dioda LED3 świeci). W ciągu 20 sekund (fluktuacja wyboru routera) przechodzi na router (świeci dioda LED2).

5f40afca2dcc4b5b.png

Zresetuj tablice

Wyłącz wszystkie trzy karty, a następnie włącz je ponownie i obserwuj diody LED. Pierwsza karta, która została zasilona, ​​powinna rozpocząć się w roli Lidera (świeci się dioda LED1) - pierwszy router w sieci Thread automatycznie staje się Liderem.

Pozostałe dwie karty początkowo łączą się z siecią jako urządzenia końcowe (dioda LED3 świeci), ale powinny przejść do routerów (dioda LED2 świeci) w ciągu 20 sekund.

Partycje sieciowe

Jeśli twoje karty nie otrzymują wystarczającej mocy lub połączenie radiowe między nimi jest słabe, sieć Thread może zostać podzielona na partycje i możesz mieć więcej niż jedno urządzenie wyświetlane jako Lider.

Wątek sam się naprawia, więc partycje powinny w końcu połączyć się z powrotem w jedną partycję z jednym Liderem.

Jeśli kontynuujesz poprzednie ćwiczenie, dioda LED4 nie powinna świecić na żadnym urządzeniu.

Wybierz dowolną planszę i naciśnij przycisk Button1. LED4 na wszystkich innych płytach w sieci Thread, na której działa aplikacja, powinien zmienić swój stan. Jeśli kontynuujesz z poprzedniego ćwiczenia, powinny być teraz włączone.

f186a2618fdbe3fd.png

Naciśnij ponownie przycisk 1 dla tej samej płyty. Dioda LED4 na wszystkich pozostałych płytach powinna się ponownie przełączyć.

Naciśnij przycisk 1 na innej płytce i obserwuj, jak dioda LED4 przełącza się na innych płytkach. Naciśnij przycisk 1 na jednej z płyt, na której aktualnie świeci się dioda LED4. LED4 pozostaje włączone dla tej płyty, ale przełącza się na inne.

f5865ccb8ab7aa34.png

Partycje sieciowe

Jeśli Twoje tablice zostały podzielone na partycje i jest wśród nich więcej niż jeden Lider, wynik wiadomości multiemisji będzie różny dla poszczególnych tablic. Jeśli naciśniesz przycisk Button1 na płycie, która została podzielona na partycje (a zatem jest jedynym członkiem sieci podzielonej na partycje), dioda LED4 na innych płytach nie zaświeci się w odpowiedzi. Jeśli tak się stanie, zresetuj karty - w idealnym przypadku zreformują jedną sieć wątkową, a wiadomości UDP powinny działać poprawnie.

Stworzyłeś aplikację, która korzysta z interfejsów API OpenThread!

Teraz wiesz:

  • Jak zaprogramować przyciski i diody LED na płytach dev Nordic nRF52840
  • Jak korzystać z typowych interfejsów API OpenThread i klasy otInstance
  • Jak monitorować i reagować na zmiany stanu OpenThread
  • Jak wysyłać wiadomości UDP do wszystkich urządzeń w sieci Thread
  • Jak modyfikować pliki Makefiles

Następne kroki

Opierając się na tym Codelab, wykonaj następujące ćwiczenia:

  • Zmodyfikuj moduł GPIO, aby używał pinów GPIO zamiast wbudowanych diod LED i podłącz zewnętrzne diody LED RGB, które zmieniają kolor w zależności od roli routera
  • Dodaj obsługę GPIO dla innej przykładowej platformy
  • Zamiast używać multiemisji do pingowania wszystkich urządzeń po naciśnięciu przycisku, użyj interfejsu Router / Leader API, aby zlokalizować i pingować pojedyncze urządzenie
  • Połącz swoją sieć kratową z Internetem za pomocą routera OpenThread Border Router i przesyłaj je grupowo spoza sieci Thread, aby zapalić diody LED

Dalsza lektura

Sprawdź openthread.io i GitHub, aby uzyskać dostęp do różnych zasobów OpenThread, w tym:

Odniesienie: