1. Einführung

OpenThread von Nest ist eine Open-Source-Implementierung des Thread®-Netzwerkprotokolls. Nest hat OpenThread veröffentlicht, um die in Nest-Produkten verwendete Technologie Entwicklern allgemein zugänglich zu machen und so die Entwicklung von Produkten für das vernetzte Zuhause zu beschleunigen.
Die Thread-Spezifikation definiert ein zuverlässiges, sicheres und energieeffizientes IPv6-basiertes Protokoll für die drahtlose Kommunikation zwischen Geräten für Anwendungen im Haushalt. OpenThread implementiert alle Thread-Netzwerkschichten, einschließlich IPv6, 6LoWPAN, IEEE 802.15.4 mit MAC-Sicherheit, Mesh Link Establishment und Mesh Routing.
In diesem Codelab verwenden Sie OpenThread-APIs, um ein Thread-Netzwerk zu starten, Änderungen an Geräterollen zu überwachen und darauf zu reagieren sowie UDP-Nachrichten zu senden. Außerdem verknüpfen Sie diese Aktionen mit Schaltflächen und LEDs auf echter Hardware.

Lerninhalte
- Schaltflächen und LEDs auf Nordic nRF52840-Entwicklerboards programmieren
- Verwendung gängiger OpenThread-APIs und der Klasse
otInstance - OpenThread-Statusänderungen überwachen und darauf reagieren
- UDP-Nachrichten an alle Geräte in einem Thread-Netzwerk senden
- Makefiles ändern
Voraussetzungen
Hardware:
- 3 Nordic Semiconductor nRF52840-Entwicklerboards
- 3 USB-zu-Micro-USB-Kabel zum Verbinden der Boards
- Ein Linux-Computer mit mindestens drei USB-Anschlüssen
Software:
- GNU-Toolchain
- Nordic nRF5x-Befehlszeilentools
- Segger J-Link-Software
- OpenThread
- Git
Sofern nicht anders angegeben, sind die Inhalte dieses Codelabs unter der Creative Commons-Lizenz „Namensnennung 3.0“ und die Codebeispiele unter der Apache 2.0-Lizenz lizenziert.
2. Erste Schritte
Hardware-Codelab abschließen
Bevor Sie mit diesem Codelab beginnen, sollten Sie das Codelab Build a Thread Network with nRF52840 Boards and OpenThread durcharbeiten. Darin wird Folgendes behandelt:
- Hier finden Sie alle Software, die Sie zum Erstellen und Flashen benötigen.
- Hier erfahren Sie, wie Sie OpenThread erstellen und auf Nordic nRF52840-Boards flashen.
- Grundlagen eines Thread-Netzwerks
In diesem Codelab wird keine der für das Erstellen von OpenThread und das Flashen der Boards erforderlichen Umgebungseinrichtungen beschrieben. Es enthält nur grundlegende Anleitungen zum Flashen der Boards. Es wird davon ausgegangen, dass Sie das Codelab „Thread-Netzwerk erstellen“ bereits abgeschlossen haben.
Linux-Computer
Dieses Codelab wurde für die Verwendung einer i386- oder x86-basierten Linux-Maschine zum Flashen aller Thread-Entwicklungsboards entwickelt. Alle Schritte wurden unter Ubuntu 14.04.5 LTS (Trusty Tahr) getestet.
Nordic Semiconductor nRF52840-Boards
In diesem Codelab werden drei nRF52840 PDK-Boards verwendet.

Software installieren
Um OpenThread zu erstellen und zu flashen, müssen Sie SEGGER J-Link, die nRF5x-Befehlszeilentools, die ARM GNU-Toolchain und verschiedene Linux-Pakete installieren. Wenn Sie das Codelab „Thread-Netzwerk erstellen“ wie erforderlich durchgearbeitet haben, ist bereits alles installiert, was Sie benötigen. Wenn nicht, führen Sie dieses Codelab aus, bevor Sie fortfahren, damit Sie OpenThread auf nRF52840-Entwicklerboards erstellen und flashen können.
3. Repository klonen
OpenThread enthält Beispielanwendungscode, den Sie als Ausgangspunkt für dieses Codelab verwenden können.
Klonen Sie das OpenThread-Beispiel-Repository für Nordic nRF528xx und erstellen Sie OpenThread:
$ git clone --recursive https://github.com/openthread/ot-nrf528xx $ cd ot-nrf528xx $ ./script/bootstrap
4. OpenThread API – Grundlagen
Die öffentlichen APIs von OpenThread befinden sich im OpenThread-Repository unter ./openthread/include/openthread. Diese APIs bieten Zugriff auf eine Vielzahl von OpenThread-Funktionen und -Funktionalitäten sowohl auf Thread- als auch auf Plattformebene für die Verwendung in Ihren Anwendungen:
- OpenThread-Instanzinformationen und ‑Steuerung
- Anwendungsdienste wie IPv6, UDP und CoAP
- Verwaltung von Netzwerk-Anmeldedaten sowie Commissioner- und Joiner-Rollen
- Border-Router-Verwaltung
- Erweiterte Funktionen wie die Elternaufsicht und die Stauerkennung
Referenzinformationen zu allen OpenThread-APIs finden Sie unter openthread.io/reference.
API verwenden
Wenn Sie eine API verwenden möchten, fügen Sie die zugehörige Header-Datei in eine Ihrer Anwendungsdateien ein. Rufen Sie dann die gewünschte Funktion auf.
Die in OpenThread enthaltene CLI-Beispiel-App verwendet beispielsweise die folgenden API-Header:
./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>
Die OpenThread-Instanz
Die otInstance-Struktur wird häufig verwendet, wenn Sie mit den OpenThread-APIs arbeiten. Nach der Initialisierung stellt diese Struktur eine statische Instanz der OpenThread-Bibliothek dar und ermöglicht dem Nutzer, OpenThread-API-Aufrufe zu tätigen.
Die OpenThread-Instanz wird beispielsweise in der Funktion main() der CLI-Beispiel-App initialisiert:
./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;
}
Plattformspezifische Funktionen
Wenn Sie einer der Beispielanwendungen, die in OpenThread enthalten sind, plattformspezifische Funktionen hinzufügen möchten, deklarieren Sie sie zuerst im ./openthread/examples/platforms/openthread-system.h-Header und verwenden Sie den otSys-Namespace für alle Funktionen. Implementieren Sie sie dann in einer plattformspezifischen Quelldatei. Auf diese Weise können Sie dieselben Funktionsheadern für andere Beispielplattformen verwenden.
Die GPIO-Funktionen, die wir verwenden, um die nRF52840-Schaltflächen und ‑LEDs anzuschließen, müssen beispielsweise in openthread-system.h deklariert werden.
Öffnen Sie die Datei ./openthread/examples/platforms/openthread-system.h in Ihrem bevorzugten Texteditor.
./openthread/examples/platforms/openthread-system.h
ACTION: Add platform-specific GPIO function declarations.
Fügen Sie diese Funktionsdeklarationen nach dem #include für den Header openthread/instance.h ein:
/** * 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);
Wir werden sie im nächsten Schritt implementieren.
Beachten Sie, dass in der Funktionsdeklaration otSysButtonProcess ein otInstance verwendet wird. So kann die Anwendung bei Bedarf auf Informationen zur OpenThread-Instanz zugreifen, wenn eine Taste gedrückt wird. Das hängt von den Anforderungen Ihrer Anwendung ab. Wenn Sie sie in Ihrer Implementierung der Funktion nicht benötigen, können Sie das Makro OT_UNUSED_VARIABLE aus der OpenThread API verwenden, um Build-Fehler im Zusammenhang mit nicht verwendeten Variablen für einige Toolchains zu unterdrücken. Dazu sehen wir uns später Beispiele an.
5. GPIO-Plattformabstraktion implementieren
Im vorherigen Schritt haben wir uns die plattformspezifischen Funktionsdeklarationen in ./openthread/examples/platforms/openthread-system.h angesehen, die für GPIO verwendet werden können. Wenn Sie auf Tasten und LEDs auf den nRF52840-Entwicklerboards zugreifen möchten, müssen Sie diese Funktionen für die nRF52840-Plattform implementieren. In diesem Code fügen Sie Funktionen hinzu, die:
- GPIO-Pins und ‑Modi initialisieren
- Spannung an einem Pin steuern
- GPIO-Unterbrechungen aktivieren und Callback registrieren
Erstellen Sie im Verzeichnis ./src/src eine neue Datei mit dem Namen gpio.c. Fügen Sie in diese neue Datei den folgenden Inhalt ein.
./src/src/gpio.c (neue Datei)
AKTION: Definitionen hinzufügen
Diese Definitionen dienen als Abstraktionen zwischen nRF52840-spezifischen Werten und Variablen, die auf der OpenThread-Anwendungsebene verwendet werden.
/** * @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
Weitere Informationen zu den Tasten und LEDs des nRF52840 finden Sie im Nordic Semiconductor Infocenter.
AKTION: Header-Includes hinzufügen.
Fügen Sie als Nächstes die Header-Includes hinzu, die Sie für die GPIO-Funktionalität benötigen.
/* 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"
AKTION: Füge Callback- und Interrupt-Funktionen für Schaltfläche 1 hinzu.
Fügen Sie als Nächstes diesen Code hinzu. Die Funktion in_pin1_handler ist der Callback, der registriert wird, wenn die Schaltflächenfunktion initialisiert wird (später in dieser Datei).
Beachten Sie, dass in diesem Callback das Makro OT_UNUSED_VARIABLE verwendet wird, da die an in_pin1_handler übergebenen Variablen in der Funktion nicht verwendet werden.
/* 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: Add a function to configure the LEDs. (AKTION: Füge eine Funktion zum Konfigurieren der LEDs hinzu.)
Fügen Sie diesen Code hinzu, um den Modus und den Status aller LEDs während der Initialisierung zu konfigurieren.
/**
* @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);
}
AKTION: Füge eine Funktion hinzu, um den Modus einer LED festzulegen.
Diese Funktion wird verwendet, wenn sich die Rolle des Geräts ändert.
/**
* @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: Add a function to toggle the mode of an LED. (AKTION: Füge eine Funktion hinzu, um den Modus einer LED umzuschalten.)
Diese Funktion wird verwendet, um LED4 umzuschalten, wenn das Gerät eine Multicast-UDP-Nachricht empfängt.
/**
* @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;
}
}
AKTION: Funktionen zum Initialisieren und Verarbeiten von Tastendrücken hinzufügen.
Die erste Funktion initialisiert das Board für einen Tastendruck und die zweite sendet die Multicast-UDP-Nachricht, wenn Taste 1 gedrückt wird.
/**
* @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);
}
}
AKTION: Speichern und schließen Sie die Datei gpio.c.
6. API: Auf Änderungen der Geräterolle reagieren
In unserer Anwendung sollen je nach Geräterolle unterschiedliche LEDs aufleuchten. Wir betrachten die folgenden Rollen: Leader, Router und Endgerät. Wir können sie so LEDs zuweisen:
- LED1 = Leader
- LED2 = Router
- LED3 = Endgerät
Damit diese Funktion aktiviert werden kann, muss die Anwendung wissen, wann sich die Geräterolle ändert, und wie die richtige LED als Reaktion darauf eingeschaltet wird. Für den ersten Teil verwenden wir die OpenThread-Instanz und für den zweiten die GPIO-Plattformabstraktion.
Öffnen Sie die Datei ./openthread/examples/apps/cli/main.c in Ihrem bevorzugten Texteditor.
./openthread/examples/apps/cli/main.c
AKTION: Header-Includes hinzufügen.
Fügen Sie im Abschnitt „includes“ der Datei main.c die API-Headerdateien hinzu, die Sie für die Funktion zum Ändern von Rollen benötigen.
#include <openthread/instance.h> #include <openthread/thread.h> #include <openthread/thread_ftd.h>
ACTION: Add handler function declaration for the OpenThread instance state change.
Fügen Sie diese Deklaration in main.c nach den Header-Includes und vor allen #if-Anweisungen ein. Diese Funktion wird nach der Hauptanwendung definiert.
void handleNetifStateChanged(uint32_t aFlags, void *aContext);
ACTION: Add a callback registration for the state change handler function. (AKTION: Callback-Registrierung für die Handler-Funktion für Statusänderungen hinzufügen)
Fügen Sie in main.c diese Funktion der Funktion main() nach dem Aufruf von otAppCliInit hinzu. Durch diese Callback-Registrierung wird OpenThread angewiesen, die Funktion handleNetifStateChange aufzurufen, wenn sich der Status der OpenThread-Instanz ändert.
/* Register Thread state change handler */ otSetStateChangedCallback(instance, handleNetifStateChanged, instance);
AKTION: Implementierung für Statusänderung hinzufügen
Implementieren Sie in main.c nach der Funktion main() die Funktion handleNetifStateChanged. Diese Funktion prüft das OT_CHANGED_THREAD_ROLE-Flag der OpenThread-Instanz und schaltet LEDs bei Bedarf ein oder aus.
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: Multicast zum Einschalten einer LED verwenden
In unserer Anwendung möchten wir auch UDP-Nachrichten an alle anderen Geräte im Netzwerk senden, wenn auf einer Platine die Schaltfläche 1 gedrückt wird. Um den Empfang der Nachricht zu bestätigen, schalten wir LED4 auf den anderen Boards ein.
Damit diese Funktion genutzt werden kann, muss die Anwendung folgende Voraussetzungen erfüllen:
- Beim Starten eine UDP-Verbindung initialisieren
- UDP-Nachrichten an die Mesh-lokale Multicast-Adresse senden können
- Eingehende UDP-Nachrichten verarbeiten
- LED4 als Reaktion auf eingehende UDP-Nachrichten ein- und ausschalten
Öffnen Sie die Datei ./openthread/examples/apps/cli/main.c in Ihrem bevorzugten Texteditor.
./openthread/examples/apps/cli/main.c
AKTION: Header-Includes hinzufügen.
Fügen Sie im Abschnitt „includes“ oben in der Datei main.c die API-Headerdateien hinzu, die Sie für die UDP-Multicast-Funktion benötigen.
#include <string.h> #include <openthread/message.h> #include <openthread/udp.h> #include "utils/code_utils.h"
Der code_utils.h-Header wird für die Makros otEXPECT und otEXPECT_ACTION verwendet, mit denen Laufzeitbedingungen validiert und Fehler ordnungsgemäß behandelt werden.
AKTION: Defines und Konstanten hinzufügen:
Fügen Sie in der Datei main.c nach dem Abschnitt „includes“ und vor allen #if-Anweisungen UDP-spezifische Konstanten und Definitionen hinzu:
#define UDP_PORT 1212 static const char UDP_DEST_ADDR[] = "ff03::1"; static const char UDP_PAYLOAD[] = "Hello OpenThread World!";
ff03::1 ist die Mesh-lokale Multicast-Adresse. Alle Nachrichten, die an diese Adresse gesendet werden, werden an alle Full Thread Devices im Netzwerk gesendet. Weitere Informationen zur Multicast-Unterstützung in OpenThread finden Sie unter Multicast auf openthread.io.
ACTION: Add function declarations. (AKTION: Funktionsdeklarationen hinzufügen.)
Fügen Sie in der Datei main.c nach der Definition von otTaskletsSignalPending und vor der Funktion main() UDP-spezifische Funktionen sowie eine statische Variable zur Darstellung eines UDP-Sockets hinzu:
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;
AKTION: Füge Aufrufe hinzu, um die GPIO-LEDs und die Schaltfläche zu initialisieren.
Fügen Sie in main.c diese Funktionsaufrufe der Funktion main() nach dem Aufruf von otSetStateChangedCallback hinzu. Diese Funktionen initialisieren die GPIO- und GPIOTE-Pins und legen einen Schaltflächen-Handler fest, der Schaltflächenereignisse verarbeitet.
/* init GPIO LEDs and button */ otSysLedInit(); otSysButtonInit(handleButtonInterrupt);
AKTION: UDP-Initialisierungsaufruf hinzufügen
Fügen Sie in main.c diese Funktion der Funktion main() nach dem otSysButtonInit-Aufruf hinzu, den Sie gerade hinzugefügt haben:
initUdp(instance);
Mit diesem Aufruf wird dafür gesorgt, dass beim Start der Anwendung ein UDP-Socket initialisiert wird. Andernfalls kann das Gerät keine UDP-Nachrichten senden oder empfangen.
AKTION: Füge einen Aufruf hinzu, um das GPIO-Schaltflächenereignis zu verarbeiten.
Fügen Sie in main.c diesen Funktionsaufruf der Funktion main() nach dem Aufruf von otSysProcessDrivers im while-Loop hinzu. Diese Funktion, die in gpio.c deklariert ist, prüft, ob die Schaltfläche gedrückt wurde. Wenn ja, wird der Handler (handleButtonInterrupt) aufgerufen, der im vorherigen Schritt festgelegt wurde.
otSysButtonProcess(instance);
WICHTIG: Implementiere einen Handler für die Unterbrechung durch die Schaltfläche.
Fügen Sie in main.c die Implementierung der Funktion handleButtonInterrupt nach der Funktion handleNetifStateChanged hinzu, die Sie im vorherigen Schritt hinzugefügt haben:
/**
* Function to handle button push event
*/
void handleButtonInterrupt(otInstance *aInstance)
{
sendUdp(aInstance);
}
AKTION: UDP-Initialisierung implementieren
Fügen Sie in main.c die Implementierung der Funktion initUdp nach der Funktion handleButtonInterrupt hinzu, die Sie gerade hinzugefügt haben:
/**
* 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 ist der Port, den Sie zuvor definiert haben (1212). Die Funktion otUdpOpen öffnet den Socket und registriert eine Callback-Funktion (handleUdpReceive) für den Empfang einer UDP-Nachricht. Mit otUdpBind wird der Socket an die Thread-Netzwerkschnittstelle gebunden, indem OT_NETIF_THREAD übergeben wird. Weitere Optionen für die Netzwerkschnittstelle finden Sie in der otNetifIdentifier-Aufzählung in der UDP API-Referenz.
AKTION: UDP-Messaging implementieren.
Fügen Sie in main.c die Implementierung der Funktion sendUdp nach der Funktion initUdp hinzu, die Sie gerade hinzugefügt haben:
/**
* 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);
}
}
Achten Sie auf die Makros otEXPECT und otEXPECT_ACTION. Diese sorgen dafür, dass die UDP-Nachricht gültig ist und korrekt im Puffer zugewiesen wird. Andernfalls werden Fehler von der Funktion ordnungsgemäß behandelt, indem zum exit-Block gesprungen wird, in dem der Puffer freigegeben wird.
Weitere Informationen zu den Funktionen, die zum Initialisieren von UDP verwendet werden, finden Sie in den IPv6- und UDP-Referenzen auf openthread.io.
AKTION: UDP-Nachrichtenverarbeitung implementieren
Fügen Sie in main.c die Implementierung der Funktion handleUdpReceive nach der Funktion sendUdp ein, die Sie gerade hinzugefügt haben. Diese Funktion schaltet LED4 einfach um.
/**
* 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: Thread-Netzwerk konfigurieren
Um die Demonstration zu vereinfachen, sollen unsere Geräte sofort mit Thread beginnen und sich zu einem Netzwerk verbinden, wenn sie eingeschaltet werden. Dazu verwenden wir die Struktur otOperationalDataset. Diese Struktur enthält alle Parameter, die zum Übertragen von Thread-Netzwerk-Anmeldedaten an ein Gerät erforderlich sind.
Durch die Verwendung dieser Struktur werden die in OpenThread integrierten Netzwerkstandardeinstellungen überschrieben, um unsere Anwendung sicherer zu machen und Thread-Knoten in unserem Netzwerk auf diejenigen zu beschränken, auf denen die Anwendung ausgeführt wird.
Öffnen Sie die Datei ./openthread/examples/apps/cli/main.c noch einmal in Ihrem bevorzugten Texteditor.
./openthread/examples/apps/cli/main.c
AKTION: Header-Include hinzufügen.
Fügen Sie im Abschnitt „includes“ oben in der Datei main.c die API-Headerdatei hinzu, die Sie zum Konfigurieren des Thread-Netzwerks benötigen:
#include <openthread/dataset_ftd.h>
ACTION: Add function declaration for setting the network configuration. (AKTION: Funktionsdeklaration zum Festlegen der Netzwerkkonfiguration hinzufügen.)
Fügen Sie diese Deklaration in main.c nach den Header-Includes und vor allen #if-Anweisungen ein. Diese Funktion wird nach der Hauptanwendungsfunktion definiert.
static void setNetworkConfiguration(otInstance *aInstance);
ACTION: Add the network configuration call. (AKTION: Netzwerkkonfigurationsaufruf hinzufügen.)
Fügen Sie in main.c diesen Funktionsaufruf der Funktion main() nach dem Aufruf von otSetStateChangedCallback hinzu. Mit dieser Funktion wird das Thread-Netzwerk-Dataset konfiguriert.
/* Override default network credentials */ setNetworkConfiguration(instance);
AKTION: Füge Aufrufe hinzu, um die Thread-Netzwerkschnittstelle und den Thread-Stack zu aktivieren.
Fügen Sie in main.c diese Funktionsaufrufe der Funktion main() nach dem Aufruf von otSysButtonInit hinzu.
/* Start the Thread network interface (CLI cmd > ifconfig up) */ otIp6SetEnabled(instance, true); /* Start the Thread stack (CLI cmd > thread start) */ otThreadSetEnabled(instance, true);
AKTION: Thread-Netzwerkkonfiguration implementieren.
Fügen Sie in main.c die Implementierung der Funktion setNetworkConfiguration nach der Funktion main() hinzu:
/**
* 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);
}
Wie in der Funktion beschrieben, sind die Thread-Netzwerkparameter, die wir für diese Anwendung verwenden:
- Channel = 15
- PAN‑ID = 0x2222
- Erweiterte PAN-ID = C0DE1AB5C0DE1AB5
- Netzwerkschlüssel = 1234C0DE1AB51234C0DE1AB51234C0DE
- Network Name = OTCodelab
Außerdem wird hier der Router Selection Jitter verringert, damit unsere Geräte für Demozwecke schneller die Rolle wechseln. Dies erfolgt nur, wenn der Knoten ein FTD (Full Thread Device) ist. Mehr dazu im nächsten Schritt.
9. API: Eingeschränkte Funktionen
Einige OpenThread-APIs ändern Einstellungen, die nur zu Demo- oder Testzwecken geändert werden sollten. Diese APIs sollten nicht in einer Produktionsbereitstellung einer Anwendung verwendet werden, die OpenThread verwendet.
Mit der Funktion otThreadSetRouterSelectionJitter wird beispielsweise die Zeit (in Sekunden) angepasst, die ein Endgerät benötigt, um sich selbst zum Router zu befördern. Der Standardwert für diesen Wert ist gemäß der Thread-Spezifikation 120. Zur besseren Nutzung in diesem Codelab ändern wir den Wert in 20, damit Sie nicht lange warten müssen, bis sich die Rolle eines Thread-Knotens ändert.
Hinweis: MTD-Geräte werden nicht zu Routern und die Unterstützung für eine Funktion wie otThreadSetRouterSelectionJitter ist nicht in einem MTD-Build enthalten. Später müssen wir die CMake-Option -DOT_MTD=OFF angeben, da es sonst zu einem Build-Fehler kommt.
Sie können dies anhand der otThreadSetRouterSelectionJitter-Funktionsdefinition bestätigen, die in einer Präprozessordirektive von OPENTHREAD_FTD enthalten ist:
./openthread/src/core/api/thread_ftd_api.cpp
#if OPENTHREAD_FTD
#include <openthread/thread_ftd.h>
...
void otThreadSetRouterSelectionJitter(otInstance *aInstance, uint8_t aRouterJitter)
{
Instance &instance = *static_cast<Instance *>(aInstance);
instance.GetThreadNetif().GetMle().SetRouterSelectionJitter(aRouterJitter);
}
...
#endif // OPENTHREAD_FTD
10. CMake-Updates
Bevor Sie Ihre Anwendung erstellen, sind einige kleinere Aktualisierungen für drei CMake-Dateien erforderlich. Sie werden vom Build-System verwendet, um Ihre Anwendung zu kompilieren und zu verknüpfen.
./third_party/NordicSemiconductor/CMakeLists.txt
Fügen Sie nun einige Flags zum NordicSemiconductor-Gerätebaum CMakeLists.txt hinzu, um sicherzustellen, dass GPIO-Funktionen in der Anwendung definiert sind.
AKTION: Fügen Sie der Datei CMakeLists.txt Flags hinzu.
Öffnen Sie ./third_party/NordicSemiconductor/CMakeLists.txt in Ihrem bevorzugten Texteditor und fügen Sie die folgenden Zeilen im Abschnitt COMMON_FLAG hinzu.
...
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
Bearbeiten Sie die Datei ./src/CMakeLists.txt, um die neue Quelldatei gpio.c hinzuzufügen:
AKTION: Fügen Sie die GPIO-Quelle der Datei ./src/CMakeLists.txt hinzu.
Öffnen Sie ./src/CMakeLists.txt in Ihrem bevorzugten Texteditor und fügen Sie die Datei in den Abschnitt NRF_COMM_SOURCES ein.
... set(NRF_COMM_SOURCES ... src/gpio.c ... ) ...
./third_party/NordicSemiconductor/CMakeLists.txt
Fügen Sie zum Schluss die Treiberdatei nrfx_gpiote.c der Datei CMakeLists.txt von NordicSemiconductor hinzu, damit sie im Bibliotheks-Build der Nordic-Treiber enthalten ist.
AKTION: Fügen Sie den GPIO-Treiber der Datei CMakeLists.txt NordicSemiconductor hinzu.
Öffnen Sie ./third_party/NordicSemiconductor/CMakeLists.txt in Ihrem bevorzugten Texteditor und fügen Sie die Datei in den Abschnitt COMMON_SOURCES ein.
... set(COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c ... ) ...
11. Geräte einrichten
Nachdem Sie alle Code-Updates vorgenommen haben, können Sie die Anwendung erstellen und auf alle drei Nordic nRF52840-Entwicklerboards flashen. Jedes Gerät fungiert als Full Thread Device (FTD).
OpenThread erstellen
Erstellen Sie die OpenThread FTD-Binärdateien für die nRF52840-Plattform.
$ cd ~/ot-nrf528xx $ ./script/build nrf52840 UART_trans -DOT_MTD=OFF -DOT_APP_RCP=OFF -DOT_RCP=OFF
Wechseln Sie zum Verzeichnis mit der binären Datei der OpenThread FTD-Befehlszeile und konvertieren Sie sie mit der ARM Embedded Toolchain in das Hexadezimalformat:
$ cd build/bin $ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex
Boards flashen
Flashen Sie die ot-cli-ftd.hex-Datei auf jedes nRF52840-Board.
Schließen Sie das USB-Kabel an den Micro-USB-Debug-Port neben dem externen Stromanschluss auf dem nRF52840-Board an und stecken Sie es dann in Ihren Linux-Computer. Wenn die LED5 leuchtet, ist die richtige Einstellung ausgewählt.

Notieren Sie sich wie zuvor die Seriennummer des nRF52840-Boards:

Rufen Sie den Speicherort der nRFx Command Line Tools auf und flashen Sie die OpenThread CLI FTD-Hex-Datei auf das nRF52840-Board. Verwenden Sie dazu die Seriennummer des Boards:
$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --verify --chiperase --program \
~/openthread/output/nrf52840/bin/ot-cli-ftd.hex --reset
LED5 schaltet sich während des Blinkens kurz aus. Bei Erfolg wird die folgende Ausgabe generiert:
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.
Wiederhole diesen Schritt („Boards flashen“) für die beiden anderen Boards. Jedes Board sollte auf dieselbe Weise mit dem Linux-Computer verbunden werden. Der Befehl zum Flashen ist derselbe, mit Ausnahme der Seriennummer des Boards. Verwenden Sie für jedes Board die eindeutige Seriennummer im
nrfjprog Befehl zum Flashen
Wenn der Vorgang erfolgreich ist, leuchtet auf jeder Platine entweder LED1, LED2 oder LED3. Möglicherweise wechselt die leuchtende LED kurz nach dem Blinken von 3 zu 2 (oder von 2 zu 1) (Funktion zum Ändern der Geräterolle).
12. Funktionen der Anwendung
Alle drei nRF52840-Boards sollten jetzt mit Strom versorgt werden und unsere OpenThread-Anwendung ausführen. Wie bereits beschrieben, hat diese Anwendung zwei primäre Funktionen.
Geräterollenindikatoren
Die leuchtende LED auf jeder Platine gibt die aktuelle Rolle des Thread-Knotens an:
- LED1 = Leader
- LED2 = Router
- LED3 = Endgerät
Wenn sich die Rolle ändert, ändert sich auch die leuchtende LED. Sie sollten diese Änderungen innerhalb von 20 Sekunden nach dem Einschalten der Geräte auf ein oder zwei Boards sehen.
UDP-Multicast
Wenn auf einem Board die Schaltfläche 1 gedrückt wird, wird eine UDP-Nachricht an die Mesh-lokale Multicast-Adresse gesendet, die alle anderen Knoten im Thread-Netzwerk enthält. Wenn diese Nachricht empfangen wird, wird LED4 auf allen anderen Boards ein- oder ausgeschaltet. LED4 bleibt auf jedem Board an oder aus, bis eine weitere UDP-Nachricht empfangen wird.


13. Demo: Änderungen an Geräterollen beobachten
Die Geräte, die du geflasht hast, sind eine spezielle Art von Full Thread Device (FTD), die als Router Eligible End Device (REED) bezeichnet wird. Das bedeutet, dass sie entweder als Router oder als Endgerät fungieren und sich von einem Endgerät zu einem Router hochstufen können.
Thread kann bis zu 32 Router unterstützen, versucht aber, die Anzahl der Router zwischen 16 und 23 zu halten. Wenn ein REED als Endgerät angehängt wird und die Anzahl der Router unter 16 liegt, wird es automatisch zum Router hochgestuft. Diese Änderung sollte zu einem zufälligen Zeitpunkt innerhalb der Anzahl von Sekunden erfolgen, die Sie für den Wert otThreadSetRouterSelectionJitter in der Anwendung festgelegt haben (20 Sekunden).
Jedes Thread-Netzwerk hat auch einen Leader, einen Router, der für die Verwaltung der Router in einem Thread-Netzwerk zuständig ist. Nach 20 Sekunden sollte eines der Geräte ein Leader (LED1 an) und die beiden anderen Router (LED2 an) sein.

Führungsperson entfernen
Wenn der Leader aus dem Thread-Netzwerk entfernt wird, wird ein anderer Router zum Leader ernannt, damit das Netzwerk weiterhin einen Leader hat.
Schalten Sie das Leaderboard (mit der leuchtenden LED1) mit dem Ein/Aus-Schalter aus. Warten Sie etwa 20 Sekunden. Auf einer der verbleibenden zwei Platinen schaltet sich LED2 (Router) aus und LED1 (Leader) ein. Dieses Gerät ist jetzt der Leader des Thread-Netzwerks.

Aktivieren Sie die ursprüngliche Bestenliste wieder. Es sollte sich automatisch wieder mit dem Thread-Netzwerk als Endgerät verbinden (LED 3 leuchtet). Innerhalb von 20 Sekunden (dem Router-Auswahl-Jitter) wird es zum Router hochgestuft (LED2 leuchtet).

Boards zurücksetzen
Schalte alle drei Boards aus und wieder ein und beobachte die LEDs. Das erste Board, das mit Strom versorgt wurde, sollte die Rolle „Leader“ übernehmen (LED1 leuchtet). Der erste Router in einem Thread-Netzwerk wird automatisch zum Leader.
Die anderen beiden Boards stellen zunächst als Endgeräte eine Verbindung zum Netzwerk her (LED3 leuchtet), sollten sich aber innerhalb von 20 Sekunden zu Routern hochstufen (LED2 leuchtet).
Netzwerkpartitionen
Wenn deine Boards nicht ausreichend mit Strom versorgt werden oder die Funkverbindung zwischen ihnen schwach ist, kann sich das Thread-Netzwerk in Partitionen aufteilen und es wird möglicherweise mehr als ein Gerät als Leader angezeigt.
Der Thread heilt sich selbst. Partitionen sollten also irgendwann wieder zu einer einzelnen Partition mit einem Leader zusammengeführt werden.
14. Demo: UDP-Multicast senden
Wenn Sie mit der vorherigen Übung fortfahren, sollte LED4 auf keinem Gerät leuchten.
Wähle ein beliebiges Board aus und drücke die Taste 1. Die LED4 auf allen anderen Boards im Thread-Netzwerk, auf denen die Anwendung ausgeführt wird, sollte ihren Status ändern. Wenn Sie mit der vorherigen Übung fortfahren, sollten sie jetzt eingeschaltet sein.

Drücken Sie noch einmal die Taste 1 für dasselbe Board. LED4 sollte auf allen anderen Boards wieder umschalten.
Drücken Sie die Taste 1 auf einem anderen Board und beobachten Sie, wie LED4 auf den anderen Boards umgeschaltet wird. Drücken Sie die Taste 1 auf einer der Platinen, auf der LED4 gerade leuchtet. LED4 bleibt auf dieser Platine an, blinkt aber auf den anderen.

Netzwerkpartitionen
Wenn Ihre Boards partitioniert sind und es mehr als einen Leader gibt, unterscheidet sich das Ergebnis der Multicast-Nachricht zwischen den Boards. Wenn Sie auf einem Board, das partitioniert ist und somit das einzige Mitglied des partitionierten Thread-Netzwerks ist, die Taste 1 drücken, leuchtet die LED4 auf den anderen Boards nicht auf. Wenn das passiert, setze die Boards zurück. Idealerweise bilden sie dann ein einzelnes Thread-Netzwerk und die UDP-Nachrichtenübermittlung sollte korrekt funktionieren.
15. Glückwunsch!
Sie haben eine Anwendung erstellt, die OpenThread-APIs verwendet.
Sie wissen jetzt:
- Schaltflächen und LEDs auf Nordic nRF52840-Entwicklerboards programmieren
- Verwendung gängiger OpenThread-APIs und der Klasse
otInstance - OpenThread-Statusänderungen überwachen und darauf reagieren
- UDP-Nachrichten an alle Geräte in einem Thread-Netzwerk senden
- Makefiles ändern
Nächste Schritte
Versuchen Sie, die folgenden Übungen zu absolvieren:
- Das GPIO-Modul so ändern, dass GPIO-Pins anstelle der integrierten LEDs verwendet werden, und externe RGB-LEDs anschließen, die ihre Farbe je nach Routerrolle ändern
- GPIO-Unterstützung für eine andere Beispielplattform hinzufügen
- Anstatt Multicast zu verwenden, um alle Geräte durch Drücken einer Taste zu pingen, verwenden Sie die Router-/Leader-API, um ein einzelnes Gerät zu finden und zu pingen.
- Verbinde dein Mesh-Netzwerk über einen OpenThread Border Router mit dem Internet und sende Multicast-Pakete von außerhalb des Thread-Netzwerks, um die LEDs zu aktivieren.
Weitere Informationen
Auf openthread.io und GitHub finden Sie verschiedene OpenThread-Ressourcen, darunter:
- Unterstützte Plattformen: Hier finden Sie alle Plattformen, die OpenThread unterstützen.
- OpenThread erstellen – Weitere Informationen zum Erstellen und Konfigurieren von OpenThread
- Thread Primer – eine gute Referenz zu Thread-Konzepten
Referenz: