Google is committed to advancing racial equity for Black communities. See how.
Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Sviluppo con API OpenThread

26b7f4f6b3ea0700.png

OpenThread rilasciato da Nest è un'implementazione open source del protocollo di rete Thread® . Nest ha rilasciato OpenThread per rendere la tecnologia utilizzata nei prodotti Nest ampiamente disponibile agli sviluppatori per accelerare lo sviluppo di prodotti per la casa connessa.

La specifica Thread definisce un protocollo di comunicazione da dispositivo a dispositivo wireless affidabile, sicuro ea basso consumo basato su IPv6 per applicazioni domestiche. OpenThread implementa tutti i livelli di rete Thread inclusi IPv6, 6LoWPAN, IEEE 802.15.4 con sicurezza MAC, Mesh Link Establishment e Mesh Routing.

In questo Codelab, utilizzerai le API OpenThread per avviare una rete Thread, monitorare e reagire ai cambiamenti nei ruoli dei dispositivi e inviare messaggi UDP, nonché collegare queste azioni a pulsanti e LED sull'hardware reale.

2a6db2e258c32237.png

Cosa imparerai

  • Come programmare pulsanti e LED sulle schede di sviluppo Nordic nRF52840
  • Come utilizzare le API OpenThread comuni e la classe otInstance
  • Come monitorare e reagire ai cambiamenti di stato di OpenThread
  • Come inviare messaggi UDP a tutti i dispositivi in ​​una rete Thread
  • Come modificare i Makefile

Di cosa avrai bisogno

Hardware:

  • 3 schede di sviluppo Nordic Semiconductor nRF52840
  • 3 cavi da USB a Micro-USB per collegare le schede
  • Una macchina Linux con almeno 3 porte USB

Software:

  • GNU Toolchain
  • Strumenti da riga di comando Nordic nRF5x
  • Software Segger J-Link
  • OpenThread
  • Idiota

Ad eccezione di quanto diversamente indicato, il contenuto di questo Codelab è concesso in licenza con la licenza Creative Commons Attribution 3.0 e gli esempi di codice sono concessi in licenza con la licenza Apache 2.0 .

Completa il codelab hardware

Prima di iniziare questo Codelab, è necessario completare il Build a Thread Network with nRF52840 Boards e OpenThread Codelab, che:

  • Descrive in dettaglio tutto il software necessario per la creazione e il flashing
  • Ti insegna come costruire OpenThread e lampeggiarlo su schede Nordic nRF52840
  • Dimostra le basi di una rete Thread

Nessuno degli ambienti impostati necessari per costruire OpenThread e aggiornare le schede è descritto in dettaglio in questo Codelab, solo le istruzioni di base per il flashing delle schede. Si presume che tu abbia già completato il codelab Build a Thread Network.

Completa il codelab Build a Thread Network

Macchina Linux

Questo Codelab è stato progettato per utilizzare una macchina Linux basata su i386 o x86 per eseguire il flashing di tutte le schede di sviluppo Thread. Tutti i passaggi sono stati testati su Ubuntu 14.04.5 LTS (Trusty Tahr).

Schede Nordic Semiconductor nRF52840

Questo Codelab utilizza tre schede PDK nRF52840 .

a6693da3ce213856.png

Installa il software

Per creare ed eseguire il flashing di OpenThread, è necessario installare SEGGER J-Link, gli strumenti della riga di comando nRF5x, ARM GNU Toolchain e vari pacchetti Linux. Se hai completato il Build a Thread Network Codelab come richiesto, avrai già tutto il necessario installato. In caso contrario, completare tale Codelab prima di continuare per assicurarsi di poter creare e aggiornare OpenThread su schede di sviluppo nRF52840.

Completa il codelab Build a Thread Network

OpenThread viene fornito con un codice applicativo di esempio che puoi utilizzare come punto di partenza per questo Codelab.

Clona e installa OpenThread:

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

Le API pubbliche di OpenThread si trovano in include/openthread nel repository OpenThread. Queste API forniscono l'accesso a una varietà di caratteristiche e funzionalità di OpenThread sia a livello di thread che di piattaforma da utilizzare nelle tue applicazioni:

  • Informazioni e controllo sull'istanza OpenThread
  • Servizi applicativi come IPv6, UDP e CoAP
  • Gestione delle credenziali di rete, insieme ai ruoli di Commissario e Falegname
  • Gestione Border Router
  • Funzionalità migliorate come la supervisione dei bambini e il rilevamento degli inceppamenti

Le informazioni di riferimento su tutte le API OpenThread sono disponibili su openthread.io/reference .

Utilizzando un'API

Per utilizzare un'API, includi il suo file di intestazione in uno dei file dell'applicazione. Quindi chiama la funzione desiderata.

Ad esempio, l'app di esempio della CLI inclusa con OpenThread utilizza le seguenti intestazioni API:

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

L'istanza di OpenThread

La struttura otInstance è qualcosa che userai frequentemente quando lavori con le API OpenThread. Una volta inizializzata, questa struttura rappresenta un'istanza statica della libreria OpenThread e consente all'utente di effettuare chiamate API OpenThread.

Ad esempio, l'istanza OpenThread viene inizializzata nella funzione main() dell'app di esempio CLI:

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

Funzioni specifiche della piattaforma

Se si desidera aggiungere funzioni specifiche della piattaforma a una delle applicazioni di esempio incluse con OpenThread, prima dichiararle nell'intestazione examples/platforms/openthread-system.h , utilizzando lo spazio dei nomi otSys per tutte le funzioni. Quindi implementali in un file sorgente specifico della piattaforma. Astratte in questo modo, puoi utilizzare le stesse intestazioni di funzione per altre piattaforme di esempio.

Ad esempio, le funzioni GPIO che useremo per agganciarci ai pulsanti e ai LED di nRF52840 devono essere dichiarate in openthread-system.h .

Apri il file examples/platforms/openthread-system.h nel tuo editor di testo preferito.

esempi / piattaforme / openthread-system.h

AZIONE: aggiungi dichiarazioni di funzioni GPIO specifiche della piattaforma

Aggiungi queste dichiarazioni di funzione ovunque all'interno del file:

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

Li implementeremo nel passaggio successivo.

Si noti che la dichiarazione della funzione otSysButtonProcess utilizza un otInstance . In questo modo l'applicazione può accedere alle informazioni sull'istanza di OpenThread quando viene premuto un pulsante, se necessario. Tutto dipende dalle esigenze della tua applicazione. Se non ne hai bisogno nella vostra implementazione della funzione, è possibile utilizzare la OT_UNUSED_VARIABLE macro dalla API OpenThread per errori di generazione Sopprimere Intorno alle variabili non utilizzate per alcuni toolchain. Ne vedremo esempi più avanti.

Nel passaggio precedente, abbiamo examples/platforms/openthread-system.h dichiarazioni di funzioni specifiche della examples/platforms/openthread-system.h che possono essere utilizzate per GPIO. Per accedere ai pulsanti e ai LED sulle schede di sviluppo nRF52840, è necessario implementare queste funzioni per la piattaforma nRF52840. In questo codice, aggiungerai funzioni che:

  • Inizializza pin e modalità GPIO
  • Controlla la tensione su un pin
  • Abilita gli interrupt GPIO e registra una richiamata

Nella directory examples/platforms/nrf528xx/src , crea un file chiamato gpio.c In questo nuovo file, aggiungi il seguente contenuto.

esempi / piattaforme / nrf528xx / src / gpio.c (NUOVO FILE)

AZIONE: Aggiungi definisce

Queste definizioni servono come astrazioni tra i valori specifici di nRF52840 e le variabili utilizzate a livello di applicazione 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

Per ulteriori informazioni sui pulsanti e sui LED nRF52840, vedere il Nordic Semiconductor Infocenter .

AZIONE: Aggiungi intestazione include

Successivamente, aggiungi l'intestazione che ti servirà per la funzionalità 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"

AZIONE: aggiungi funzioni di richiamata e interruzione per il pulsante 1

Aggiungi questo codice dopo. La funzione in_pin1_handler è il callback registrato quando viene inizializzata la funzionalità di pressione del pulsante (più avanti in questo file).

Notare come questo callback utilizza la macro OT_UNUSED_VARIABLE , poiché le variabili passate a in_pin1_handler non sono effettivamente utilizzate nella funzione.

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

AZIONE: aggiungere una funzione per configurare i LED

Aggiungere questo codice per configurare la modalità e lo stato di tutti i LED durante l'inizializzazione.

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

AZIONE: aggiungi una funzione per impostare la modalità di un LED.

Questa funzione verrà utilizzata quando il ruolo del dispositivo cambia.

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

AZIONE: aggiungi una funzione per alternare la modalità di un LED.

Questa funzione verrà utilizzata per attivare o disattivare il LED4 quando il dispositivo riceve un messaggio UDP multicast.

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

AZIONE: aggiungere funzioni per inizializzare ed elaborare le pressioni dei pulsanti.

La prima funzione inizializza la scheda per la pressione di un pulsante e la seconda invia il messaggio UDP multicast quando viene premuto il pulsante 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);
    }
}

AZIONE: salva e chiudi il file

gpio.c file.

Nella nostra applicazione, vogliamo che diversi LED si accendano a seconda del ruolo del dispositivo. Monitoriamo i seguenti ruoli: Leader, Router, End Device. Possiamo assegnarli ai LED in questo modo:

  • LED1 = Leader
  • LED2 = Router
  • LED3 = End Device

Per abilitare questa funzionalità, l'applicazione deve sapere quando il ruolo del dispositivo è cambiato e come attivare il LED corretto in risposta. Useremo l'istanza OpenThread per la prima parte e l'astrazione della piattaforma GPIO per la seconda.

Apri il file examples/apps/cli/main.c nel tuo editor di testo preferito.

esempi / apps / cli / main.c

AZIONE: Aggiungi intestazione include

Nella sezione include del file main.c , aggiungi i file di intestazione API necessari per la funzione di cambio di ruolo.

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

AZIONE: aggiungere la dichiarazione della funzione del gestore per la modifica dello stato dell'istanza OpenThread

Aggiungi questa dichiarazione a main.c , dopo che l'intestazione include e prima di qualsiasi istruzione #if . Questa funzione verrà definita dopo l'applicazione principale.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

AZIONE: aggiungere una registrazione di callback per la funzione di gestione del cambio di stato

In main.c , aggiungi questa funzione alla funzione main() dopo la chiamata otCliUartInit . Questa registrazione di callback indica a OpenThread di chiamare la funzione handleNetifStateChange ogni volta che lo stato dell'istanza di OpenThread cambia.

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

AZIONE: aggiungere l'implementazione del cambio di stato

In main.c , dopo la funzione main() , implementa la funzione handleNetifStateChanged . Questa funzione verifica OT_CHANGED_THREAD_ROLE bandiera dell'istanza OpenThread e se è cambiato, si trasforma LED on / off come necessario.

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

Nella nostra applicazione, vogliamo anche inviare messaggi UDP a tutti gli altri dispositivi nella rete quando Button1 viene premuto su una scheda. Per confermare la ricezione del messaggio, in risposta attiveremo il LED4 sulle altre schede.

Per abilitare questa funzionalità, l'applicazione deve:

  • Inizializza una connessione UDP all'avvio
  • Essere in grado di inviare un messaggio UDP all'indirizzo multicast mesh locale
  • Gestisci i messaggi UDP in arrivo
  • Attiva / disattiva il LED4 in risposta ai messaggi UDP in arrivo

Apri il file examples/apps/cli/main.c nel tuo editor di testo preferito.

esempi / apps / cli / main.c

AZIONE: Aggiungi intestazione include

Nella sezione include nella parte superiore del file main.c , aggiungi i file di intestazione API necessari per la funzione UDP multicast.

#include <string.h>

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

#include "utils/code_utils.h"

L'intestazione code_utils.h viene utilizzata per le macro otEXPECT e otEXPECT_ACTION che convalidano le condizioni di otEXPECT e otEXPECT_ACTION correttamente gli errori.

AZIONE: aggiungi definizioni e costanti:

Nel file main.c , dopo la sezione include e prima di qualsiasi istruzione #if , aggiungi costanti specifiche dell'UDP e definisce:

#define UDP_PORT 1212

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

ff03::1 è l'indirizzo multicast mesh locale. Tutti i messaggi inviati a questo indirizzo verranno inviati a tutti i dispositivi Full Thread nella rete. Vedere Multicast su openthread.io per ulteriori informazioni sul supporto multicast in OpenThread.

AZIONE: aggiungi dichiarazioni di funzione

Nel file main.c , dopo la definizione otTaskletsSignalPending e prima della funzione main() , aggiungi le funzioni specifiche dell'UDP, oltre a una variabile statica per rappresentare un socket 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;

AZIONE: aggiungi chiamate per inizializzare i LED e il pulsante GPIO

In main.c , aggiungi queste chiamate di funzione alla funzione main() dopo la chiamata otSetStateChangedCallback . Queste funzioni inizializzano i pin GPIO e GPIOTE e impostano un gestore di pulsanti per gestire gli eventi di pressione dei pulsanti.

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

AZIONE: aggiungere la chiamata di inizializzazione UDP

In main.c , aggiungi questa funzione alla funzione main() dopo la chiamata otSysButtonInit che hai appena aggiunto:

initUdp(instance);

Questa chiamata garantisce che un socket UDP venga inizializzato all'avvio dell'applicazione. Senza questo, il dispositivo non può inviare o ricevere messaggi UDP.

AZIONE: aggiungi una chiamata per elaborare l'evento del pulsante GPIO

In main.c , aggiungi questa chiamata alla funzione main() dopo la chiamata otSysProcessDrivers , nel ciclo while . Questa funzione, dichiarata in gpio.c , controlla se il pulsante è stato premuto e, in tal caso, chiama il gestore ( handleButtonInterrupt ) che è stato impostato nel passaggio precedente.

otSysButtonProcess(instance);

AZIONE: implementare il gestore degli interrupt dei pulsanti

In main.c , aggiungi l'implementazione della funzione handleButtonInterrupt dopo la funzione handleNetifStateChanged aggiunta nel passaggio precedente:

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

AZIONE: implementare l'inizializzazione UDP

In main.c , aggiungi l'implementazione della funzione initUdp dopo la funzione handleButtonInterrupt hai appena aggiunto:

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

UDP_PORT è la porta definita in precedenza (1212). La funzione otUdpOpen apre il socket e registra una funzione di callback ( handleUdpReceive ) per quando viene ricevuto un messaggio UDP.

AZIONE: implementare la messaggistica UDP

In main.c , aggiungi l'implementazione della funzione sendUdp dopo la funzione initUdp hai appena aggiunto:

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

Notare le macro otEXPECT e otEXPECT_ACTION . Questi assicurano che il messaggio UDP sia valido e allocato correttamente nel buffer e, in caso contrario, la funzione gestisce correttamente gli errori saltando al blocco di exit , dove libera il buffer.

Vedere i riferimenti IPv6 e UDP su openthread.io per ulteriori informazioni sulle funzioni utilizzate per inizializzare UDP.

AZIONE: implementare la gestione dei messaggi UDP

In main.c , aggiungi l'implementazione della funzione handleUdpReceive dopo la funzione sendUdp appena aggiunta. Questa funzione commuta semplicemente 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);
}

Per facilità di dimostrazione, desideriamo che i nostri dispositivi avviino immediatamente Thread e si uniscano a una rete quando vengono accesi. Per fare ciò, useremo la struttura otOperationalDataset . Questa struttura contiene tutti i parametri necessari per trasmettere le credenziali di rete del thread a un dispositivo.

L'uso di questa struttura sovrascriverà le impostazioni predefinite di rete integrate in OpenThread, per rendere la nostra applicazione più sicura e limitare i nodi Thread nella nostra rete solo a quelli che eseguono l'applicazione.

Di nuovo, apri il file examples/apps/cli/main.c nel tuo editor di testo preferito.

esempi / apps / cli / main.c

AZIONE: Aggiungi intestazione include

All'interno della sezione include nella parte superiore del file main.c , aggiungi il file di intestazione API necessario per configurare la rete Thread:

#include <openthread/dataset_ftd.h>

AZIONE: Aggiungere la dichiarazione di funzione per impostare la configurazione di rete

Aggiungi questa dichiarazione a main.c , dopo che l'intestazione include e prima di qualsiasi istruzione #if . Questa funzione verrà definita dopo la funzione dell'applicazione principale.

static void setNetworkConfiguration(otInstance *aInstance);

AZIONE: aggiungere la chiamata alla configurazione di rete

In main.c , aggiungi questa chiamata alla funzione main() dopo la chiamata otSetStateChangedCallback . Questa funzione configura il dataset di rete Thread.

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

AZIONE: aggiungere chiamate per abilitare l'interfaccia di rete Thread e lo stack

In main.c , aggiungi queste chiamate di funzione alla funzione main() dopo la chiamata otSysButtonInit .

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

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

AZIONE: implementare la configurazione di rete del thread

In main.c , aggiungi l'implementazione della funzione setNetworkConfiguration dopo la funzione 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, 0x12, 0x34, 0xC0, 0xDE};
    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
}

Come dettagliato nella funzione, i parametri di rete Thread che stiamo usando per questa applicazione sono:

  • Canale = 15
  • ID PAN = 0x2222
  • ID PAN esteso = C0DE1AB5C0DE1AB5
  • Chiave principale di rete = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Nome di rete = OTCodelab

Inoltre, è qui che diminuiamo il jitter nella selezione del router, in modo che i nostri dispositivi cambino ruolo più rapidamente per scopi dimostrativi. Notare che questo viene fatto solo se il nodo è un FTD (Full Thread Device). Maggiori informazioni al riguardo nel passaggio successivo.

Alcune delle API di OpenThread modificano le impostazioni che dovrebbero essere modificate solo a scopo dimostrativo o di test. Queste API non devono essere utilizzate in una distribuzione di produzione di un'applicazione che utilizza OpenThread.

Ad esempio, la funzione otThreadSetRouterSelectionJitter regola il tempo (in secondi) necessario affinché un dispositivo finale si promuova a un router. Il valore predefinito per questo valore è 120, secondo la specifica del thread. Per facilità d'uso in questo Codelab, lo cambieremo in 20, quindi non devi aspettare molto a lungo affinché un nodo Thread cambi i ruoli.

Tuttavia, potresti aver notato che questa funzione viene chiamata all'interno di una direttiva del preprocessore:

#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

Questo per evitare che venga aggiunto a una build MTD (Minimal Thread Device). I dispositivi MTD non diventano router e il supporto per una funzione come otThreadSetRouterSelectionJitter non è incluso in una build MTD.

Puoi confermarlo guardando la definizione della funzione otThreadSetRouterSelectionJitter , contenuta anche in una direttiva del preprocessore 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

Prima di creare la tua applicazione, sono necessari alcuni aggiornamenti minori per tre Makefile. Questi sono usati dal sistema di compilazione per compilare e collegare la tua applicazione.

esempi / Makefile-nrf52840

Ora aggiungi alcuni flag al Makefile nrf52840, per assicurarti che le funzioni GPIO siano definite nell'applicazione.

AZIONE: aggiungi flag al Makefile nrf52840

Apri examples/Makefile-nrf52840 nel tuo editor di testo preferito e aggiungi i seguenti COMMONCFLAGS dopo la riga 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

...

esempi / piattaforme / nrf528xx / nrf52840 / Makefile.am

Ora aggiungi il nuovo file gpio.c al file Makefile.am della piattaforma nrf52840.

AZIONE: aggiungi il sorgente gpio al Makefile della piattaforma nrf52840

Apri examples/platforms/nrf528xx/nrf52840/Makefile.am nel tuo editor di testo preferito e aggiungi il file alla fine della sezione PLATFORM_COMMON_SOURCES , subito prima examples/platforms/nrf528xx/nrf52840/Makefile.am $(NULL) . Non dimenticare di inserire una barra rovesciata alla fine della riga!

...

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

...

third_party / NordicSemiconductor / Makefile.am

Infine, aggiungi il file del driver nrfx_gpiote.c al Makefile di terze parti NordicSemiconductor, in modo che sia incluso nella build della libreria dei driver Nordic.

AZIONE: aggiungi il driver gpio al Makefile della piattaforma nrf52840

Apri third_party/NordicSemiconductor/Makefile.am nel tuo editor di testo preferito e aggiungi il file alla fine della sezione NORDICSEMI_COMMON_SOURCES , subito prima NORDICSEMI_COMMON_SOURCES $(NULL) . Non dimenticare di inserire una barra rovesciata alla fine della riga!

...

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

$(NULL)

...

Dopo aver completato tutti gli aggiornamenti del codice, sei pronto per creare e aggiornare l'applicazione su tutte e tre le schede di sviluppo Nordic nRF52840. Ogni dispositivo funzionerà come un Full Thread Device (FTD).

Crea OpenThread

Costruisci la piattaforma di esempio nRF52840. Poiché i Makefile sono stati modificati nel passaggio precedente, è necessario eseguire lo script di bootstrap prima di creare:

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

Passa alla directory con il binario della CLI OpenThread FTD e convertilo in formato esadecimale con ARM Embedded Toolchain:

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

Lampeggia le schede

Flash il file ot-cli-ftd.hex su ciascuna scheda nRF52840.

Collega il cavo USB alla porta di debug Micro-USB accanto al pin di alimentazione esterna sulla scheda nRF52840, quindi collegalo alla tua macchina Linux. Impostato correttamente, il LED5 è acceso.

20a3b4b480356447.png

Come prima, annotare il numero di serie della scheda nRF52840:

c00d519ebec7e5f0.jpeg

Passare alla posizione degli strumenti della riga di comando nRFx e lampeggiare il file esadecimale OpenThread CLI FTD sulla scheda nRF52840, utilizzando il numero di serie della scheda:

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

Il LED5 si spegnerà brevemente durante il lampeggiamento. In caso di successo, viene generato il seguente output:

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.

Ripetere questo passaggio "Flash le schede" per le altre due schede. Ogni scheda dovrebbe essere collegata alla macchina Linux allo stesso modo e il comando per eseguire il flash è lo stesso, tranne per il numero di serie della scheda. Assicurati di utilizzare il numero di serie univoco di ciascuna scheda nel file

nrfjprog lampeggiante nrfjprog .

In caso di successo, il LED1, LED2 o LED3 si accenderà su ciascuna scheda. Potresti anche vedere il LED acceso da 3 a 2 (o da 2 a 1) subito dopo aver lampeggiato (la funzione di cambio del ruolo del dispositivo).

Tutte e tre le schede nRF52840 dovrebbero ora essere alimentate ed eseguire la nostra applicazione OpenThread. Come spiegato in precedenza, questa applicazione ha due caratteristiche principali.

Indicatori di ruolo del dispositivo

Il LED acceso su ciascuna scheda riflette il ruolo corrente del nodo Thread:

  • LED1 = Leader
  • LED2 = Router
  • LED3 = End Device

Man mano che il ruolo cambia, cambia anche il LED acceso. Dovresti aver già visto questi cambiamenti su una o due schede entro 20 secondi dall'accensione di ciascun dispositivo.

Multicast UDP

Quando Button1 viene premuto su una scheda, viene inviato un messaggio UDP all'indirizzo multicast locale della mesh, che include tutti gli altri nodi nella rete Thread. In risposta alla ricezione di questo messaggio, il LED4 su tutte le altre schede si accende o si spegne . Il LED4 rimane acceso o spento per ogni scheda fino a quando non riceve un altro messaggio UDP.

203dd094acca1f97.png

9bbd96d9b1c63504.png

I dispositivi che hai lampeggiato sono un tipo specifico di Full Thread Device (FTD) chiamato Router Eligible End Device (REED). Ciò significa che possono funzionare sia come router che come dispositivo finale e possono promuoversi da un dispositivo finale a un router.

Il thread può supportare fino a 32 router, ma cerca di mantenere il numero di router tra 16 e 23. Se un REED si collega come dispositivo finale e il numero di router è inferiore a 16, si promuove automaticamente a router. Questa modifica dovrebbe verificarsi in un momento casuale entro il numero di secondi su cui si imposta il valore otThreadSetRouterSelectionJitter nell'applicazione (20 secondi).

Ogni rete Thread ha anche un Leader, che è un Router responsabile della gestione dell'insieme di Router in una rete Thread. Con tutti i dispositivi accesi, dopo 20 secondi uno di essi dovrebbe essere un Leader (LED1 acceso) e gli altri due dovrebbero essere Router (LED2 acceso).

4e1e885861a66570.png

Rimuovi il leader

Se il Leader viene rimosso dalla rete Thread, un router diverso si promuove a Leader, per garantire che la rete abbia ancora un Leader.

Spegnere la scheda Leader (quella con il LED1 acceso) utilizzando l'interruttore di alimentazione . Attendi circa 20 secondi. Su una delle restanti due schede, il LED2 (Router) si spegnerà e il LED1 (Leader) si accenderà. Questo dispositivo è ora il leader della rete Thread.

4c57c87adb40e0e3.png

Riattiva la classifica originale. Dovrebbe rientrare automaticamente nella rete Thread come End Device (il LED3 è acceso). Entro 20 secondi (il jitter di selezione del router) si promuove a un router (il LED2 è acceso).

5f40afca2dcc4b5b.png

Resetta le schede

Spegnere tutte e tre le schede, quindi riaccenderle e osservare i LED. La prima scheda che è stata accesa dovrebbe iniziare nel ruolo di Leader (il LED1 è acceso): il primo Router in una rete Thread diventa automaticamente il Leader.

Le altre due schede si connettono inizialmente alla rete come dispositivi finali (il LED3 è acceso) ma dovrebbero promuoversi a router (il LED2 è acceso) entro 20 secondi.

Partizioni di rete

Se le tue schede non ricevono alimentazione sufficiente o la connessione radio tra di loro è debole, la rete Thread potrebbe dividersi in partizioni e potresti avere più di un dispositivo visualizzato come Leader.

Il thread si risolve automaticamente, quindi le partizioni dovrebbero alla fine unirsi di nuovo in una singola partizione con un leader.

Se si continua dall'esercizio precedente, il LED4 non dovrebbe essere acceso su nessun dispositivo.

Scegli un tabellone e premi Button1. Il LED4 su tutte le altre schede nella rete Thread su cui è in esecuzione l'applicazione dovrebbe cambiare il loro stato. Se si continua dall'esercizio precedente, ora dovrebbero essere attivi.

f186a2618fdbe3fd.png

Premere di nuovo il pulsante 1 per la stessa scheda. Il LED4 su tutte le altre schede dovrebbe essere nuovamente attivato.

Premere il pulsante1 su una scheda diversa e osservare come LED4 si attiva sulle altre schede. Premere il pulsante1 su una delle schede su cui è attualmente acceso LED4. Il LED4 rimane acceso per quella scheda ma attiva le altre.

f5865ccb8ab7aa34.png

Partizioni di rete

Se le tue bacheche sono partizionate e c'è più di un leader tra di loro, il risultato del messaggio multicast sarà diverso tra le bacheche. Se si preme il pulsante1 su una scheda che è stata partizionata (e quindi è l'unico membro della rete Thread partizionata), il LED4 sulle altre schede non si accenderà in risposta. In tal caso, reimpostare le schede: idealmente esse riformeranno una singola rete Thread e la messaggistica UDP dovrebbe funzionare correttamente.

Hai creato un'applicazione che utilizza le API OpenThread!

Ora sai:

  • Come programmare pulsanti e LED sulle schede di sviluppo Nordic nRF52840
  • Come utilizzare le API OpenThread comuni e la classe otInstance
  • Come monitorare e reagire ai cambiamenti di stato di OpenThread
  • Come inviare messaggi UDP a tutti i dispositivi in ​​una rete Thread
  • Come modificare i Makefile

Prossimi passi

Partendo da questo Codelab, prova i seguenti esercizi:

  • Modifica il modulo GPIO per utilizzare i pin GPIO al posto dei LED integrati e collega i LED RGB esterni che cambiano colore in base al ruolo del router
  • Aggiungi il supporto GPIO per una piattaforma di esempio diversa
  • Invece di utilizzare il multicast per eseguire il ping di tutti i dispositivi premendo un pulsante, utilizzare l' API Router / Leader per individuare e eseguire il ping di un singolo dispositivo
  • Collega la tua rete mesh a Internet utilizzando un OpenThread Border Router e trasmetti in multicast dall'esterno della rete Thread per accendere i LED

Ulteriore lettura

Dai un'occhiata a openthread.io e GitHub per una varietà di risorse OpenThread, tra cui:

Riferimento: