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.
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.
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 .
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.
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.
Come prima, annotare il numero di serie della scheda nRF52840:
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.
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).
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.
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).
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.
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.
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:
- Piattaforme supportate : scopri tutte le piattaforme che supportano OpenThread
- Build OpenThread : ulteriori dettagli sulla creazione e configurazione di OpenThread
- Thread Primer : un ottimo riferimento sui concetti di Thread
Riferimento: