Desenvolvendo com APIs OpenThread

1. Introdução

26b7f4f6b3ea0700.png

OpenThread lançado pela Nest é uma implementação open-source do Thread® protocolo de rede. A Nest lançou o OpenThread para tornar a tecnologia usada nos produtos Nest amplamente disponível aos desenvolvedores para acelerar o desenvolvimento de produtos para a casa conectada.

A especificação da linha define um protocolo de comunicação sem fios baseados em IPv6 fiável, segura e de baixo consumo de dispositivo para dispositivo para aplicações domésticas. OpenThread implementa todas as camadas de rede Thread, incluindo IPv6, 6LoWPAN, IEEE 802.15.4 com segurança MAC, Mesh Link Establishment e Mesh Routing.

Neste Codelab, você usará APIs OpenThread para iniciar uma rede Thread, monitorar e reagir a mudanças nas funções do dispositivo e enviar mensagens UDP, bem como vincular essas ações a botões e LEDs em hardware real.

2a6db2e258c32237.png

O que você aprenderá

  • Como programar os botões e LEDs nas placas nRF52840 dev nórdicas
  • Como usar APIs OpenThread comuns ea otInstance classe
  • Como monitorar e reagir às mudanças de estado do OpenThread
  • Como enviar mensagens UDP para todos os dispositivos em uma rede Thread
  • Como modificar Makefiles

O que você precisará

Hardware:

  • 3 placas dev Nordic Semiconductor nRF52840
  • 3 cabos USB para micro-USB para conectar as placas
  • Uma máquina Linux com pelo menos 3 portas USB

Programas:

  • GNU Toolchain
  • Ferramentas de linha de comando Nordic nRF5x
  • Software Segger J-Link
  • OpenThread
  • Git

Excepto como indicado de outro modo, o conteúdo deste Codelab está licenciado sob a 3.0 licença Atribuição , e amostras de código estão licenciadas sob a 2.0 Apache License .

2. Primeiros passos

Conclua o Codelab de Hardware

Antes de iniciar este Codelab, você deve completar a construir uma rede de rosca com nRF52840 Boards e OpenThread Codelab, que:

  • Detalha todo o software que você precisa para construir e atualizar
  • Ensina como construir OpenThread e atualizá-lo em placas nRF52840 nórdicas
  • Demonstra os fundamentos de uma rede Thread

Nenhuma configuração de ambiente necessária para construir OpenThread e atualizar as placas é detalhada neste Codelab - apenas as instruções básicas para atualizar as placas. Presume-se que você já tenha concluído o Codelab Build a Thread Network.

Conclua o Codelab Construir um Thread Network

Máquina Linux

Este Codelab foi projetado para usar uma máquina Linux baseada em i386 ou x86 para atualizar todas as placas de desenvolvimento de Thread. Todas as etapas foram testadas no Ubuntu 14.04.5 LTS (Trusty Tahr).

Placas Nordic Semiconductor nRF52840

Este Codelab usa três placas nRF52840 PDK .

a6693da3ce213856.png

Instalar software

Para construir e atualizar o OpenThread, você precisa instalar o SEGGER J-Link, as ferramentas de linha de comando nRF5x, o ARM GNU Toolchain e vários pacotes Linux. Se você concluiu o Build a Thread Network Codelab conforme necessário, já terá tudo o que precisa instalado. Caso contrário, conclua o Codelab antes de continuar para garantir que você pode construir e atualizar OpenThread para placas de desenvolvimento nRF52840.

Conclua o Codelab Construir um Thread Network

3. Clone o repositório

OpenThread vem com um exemplo de código de aplicativo que você pode usar como ponto de partida para este Codelab.

Clone e instale o OpenThread:

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

4. Fundamentos da API OpenThread

APIs públicas do OpenThread estão localizados na ./openthread/include/openthread no repositório OpenThread. Essas APIs fornecem acesso a uma variedade de recursos e funcionalidades OpenThread em nível de Thread e plataforma para uso em seus aplicativos:

  • Informações e controle da instância OpenThread
  • Serviços de aplicativo como IPv6, UDP e CoAP
  • Gerenciamento de credenciais de rede, juntamente com funções de Comissário e Joiner
  • Gerenciamento de Border Router
  • Recursos aprimorados, como supervisão infantil e detecção de atolamento

Informações de referência sobre todas as APIs OpenThread estão disponíveis em openthread.io/reference .

Usando uma API

Para usar uma API, inclua seu arquivo de cabeçalho em um dos arquivos do seu aplicativo. Em seguida, chame a função desejada.

Por exemplo, o aplicativo de exemplo CLI incluído com OpenThread usa os seguintes cabeçalhos de API:

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

#include <openthread/config.h>
#include <openthread/cli.h>
#include <openthread/diag.h>
#include <openthread/tasklet.h>
#include <openthread/platform/logging.h>

A instância OpenThread

O otInstance estrutura é algo que você vai usar com freqüência ao trabalhar com as APIs OpenThread. Depois de inicializada, essa estrutura representa uma instância estática da biblioteca OpenThread e permite ao usuário fazer chamadas de API OpenThread.

Por exemplo, o exemplo OpenThread é inicializado no main() a função do exemplo CLI app:

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

Funções específicas da plataforma

Se você quer adicionar funções específicas de plataforma para um dos aplicativos de exemplo incluídos com OpenThread, primeiro declará-los na ./openthread/examples/platforms/openthread-system.h cabeçalho, usando o otSys namespace para todas as funções. Em seguida, implemente-os em um arquivo de origem específico da plataforma. Abstratos dessa forma, você pode usar os mesmos cabeçalhos de função para outras plataformas de exemplo.

Por exemplo, as funções GPIO que vamos usar para ligar para os botões e LEDs nRF52840 deve ser declarado em openthread-system.h .

Abra o ./openthread/examples/platforms/openthread-system.h arquivo em seu editor de texto preferido.

./openthread/examples/platforms/openthread-system.h

AÇÃO: Adicionar declarações de função GPIO específicas da plataforma

Adicione estas declarações de função após o #include para o openthread/instance.h cabeçalho:

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

Vamos implementar isso na próxima etapa.

Note que o otSysButtonProcess declaração da função usa um otInstance . Dessa forma, o aplicativo pode acessar informações sobre a instância OpenThread quando um botão é pressionado, se necessário. Tudo depende das necessidades de seu aplicativo. Se você não precisa dele na sua implementação da função, você pode usar o OT_UNUSED_VARIABLE macro da API OpenThread a erros de compilação suprimir cerca de variáveis não utilizado para algumas toolchains. Veremos exemplos disso mais tarde.

5. Implementar a abstração da plataforma GPIO

Na etapa anterior, nós fomos sobre as declarações de função específicos da plataforma em ./openthread/examples/platforms/openthread-system.h que podem ser utilizadas para GPIO. Para acessar os botões e LEDs nas placas de desenvolvimento nRF52840, você precisa implementar essas funções para a plataforma nRF52840. Neste código, você adicionará funções que:

  • Inicializar pinos e modos GPIO
  • Controle a tensão em um pino
  • Habilite interrupções GPIO e registre um retorno de chamada

No ./src/src diretório, crie um arquivo chamado gpio.c . Neste novo arquivo, adicione o seguinte conteúdo.

./src/src/gpio.c (NOVO ARQUIVO)

AÇÃO: Adicionar define

Essas definições servem como abstrações entre os valores específicos do nRF52840 e as variáveis ​​usadas no nível do aplicativo 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

Para mais informações sobre nRF52840 botões e LEDs, consulte o Nordic Semiconductor Infocenter .

AÇÃO: Adicionar cabeçalho inclui

Em seguida, adicione o cabeçalho que você precisará para a funcionalidade 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"

AÇÃO: Adicionar funções de retorno de chamada e interrupção para o Botão 1

Adicione este código a seguir. O in_pin1_handler função é o retorno de chamada que é registrado quando a funcionalidade do botão de imprensa é inicializado (mais adiante neste arquivo).

Observe como este callback usa o OT_UNUSED_VARIABLE macro, como as variáveis passadas para in_pin1_handler não são realmente utilizados na função.

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

AÇÃO: Adicione uma função para configurar os LEDs

Adicione este código para configurar o modo e o estado de todos os LEDs durante a inicialização.

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

AÇÃO: Adicione uma função para definir o modo de um LED.

Esta função será usada quando a função do dispositivo mudar.

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

AÇÃO: Adicione uma função para alternar o modo de um LED.

Esta função será usada para alternar o LED4 quando o dispositivo receber uma mensagem 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;
    }
}

AÇÃO: Adicionar funções para inicializar e processar o pressionamento de botões.

A primeira função inicializa a placa ao pressionar o botão, e a segunda envia a mensagem UDP multicast quando o Botão 1 é pressionado.

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

AÇÃO: salvar e fechar o gpio.c arquivo.

6. API: Reage às mudanças de função do dispositivo

Em nossa aplicação, queremos que diferentes LEDs acendam dependendo da função do dispositivo. Vamos rastrear as seguintes funções: Líder, Roteador, Dispositivo final. Podemos atribuí-los a LEDs da seguinte forma:

  • LED1 = Líder
  • LED2 = Roteador
  • LED3 = Dispositivo final

Para habilitar essa funcionalidade, o aplicativo precisa saber quando a função do dispositivo mudou e como ligar o LED correto em resposta. Usaremos a instância OpenThread para a primeira parte e a abstração da plataforma GPIO para a segunda.

Abra o ./openthread/examples/apps/cli/main.c arquivo em seu editor de texto preferido.

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

AÇÃO: Adicionar cabeçalho inclui

Na seção do inclui main.c arquivo, adicione os arquivos de cabeçalho API que você precisa para o recurso de alteração de função.

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

AÇÃO: Adicionar declaração de função de manipulador para a mudança de estado da instância OpenThread

Adicionar esta declaração para main.c , após o cabeçalho inclui e antes de qualquer #if declarações. Esta função será definida após a aplicação principal.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

AÇÃO: Adicionar um registro de retorno de chamada para a função de manipulador de mudança de estado

Em main.c , adicione esta função para o main() função após a otAppCliInit chamada. Este registro callback diz OpenThread para chamar a handleNetifStateChange função sempre que a instância OpenThread estado muda.

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

AÇÃO: Adicionar a implementação de mudança de estado

Em main.c , após o main() função, implementar o handleNetifStateChanged função. Esta função verifica o OT_CHANGED_THREAD_ROLE bandeira da instância OpenThread e se mudou, transforma LEDs on / off, conforme necessário.

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: Use multicast para ligar um LED

Em nosso aplicativo, também queremos enviar mensagens UDP para todos os outros dispositivos da rede quando o Botão1 é pressionado em uma placa. Para confirmar o recebimento da mensagem, alternaremos o LED4 nas outras placas em resposta.

Para habilitar essa funcionalidade, o aplicativo precisa:

  • Inicialize uma conexão UDP na inicialização
  • Ser capaz de enviar uma mensagem UDP para o endereço multicast local da malha
  • Lidar com mensagens UDP de entrada
  • Alternar LED4 em resposta às mensagens UDP de entrada

Abra o ./openthread/examples/apps/cli/main.c arquivo em seu editor de texto preferido.

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

AÇÃO: Adicionar cabeçalho inclui

Na seção no topo do inclui main.c arquivo, adicione os arquivos de cabeçalho API que você vai precisar para o recurso UDP multicast.

#include <string.h>

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

#include "utils/code_utils.h"

O code_utils.h cabeçalho é usado para os otEXPECT e otEXPECT_ACTION macros que validam as condições em tempo de execução e graciosamente lidar com erros.

AÇÃO: Adicionar definições e constantes:

No main.c arquivo, após a seção e antes de qualquer inclui #if declarações, adicione constantes e define específicas-UDP:

#define UDP_PORT 1212

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

ff03::1 é o endereço de multicast malha local. Todas as mensagens enviadas para este endereço serão enviadas para todos os Full Thread Devices da rede. Veja Multicast em openthread.io para mais informações sobre suporte a multicast em OpenThread.

AÇÃO: Adicionar declarações de função

No main.c arquivo, após a otTaskletsSignalPending definição e antes do main() função, adicionar funções específicas do UDP, bem como uma variável estática para representar um soquete 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;

AÇÃO: Adicione chamadas para inicializar os LEDs GPIO e o botão

Em main.c , adicionar essas chamadas de função para o main() função após a otSetStateChangedCallback chamada. Essas funções inicializam os pinos GPIO e GPIOTE e definem um manipulador de botão para manipular eventos de pressionamento de botão.

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

AÇÃO: Adicione a chamada de inicialização UDP

Em main.c , adicione esta função para o main() função após a otSysButtonInit chamada que você acabou de adicionar:

initUdp(instance);

Essa chamada garante que um soquete UDP seja inicializado na inicialização do aplicativo. Sem isso, o dispositivo não pode enviar ou receber mensagens UDP.

AÇÃO: Adicionar chamada para processar o evento do botão GPIO

Em main.c , adicione esta chamada de função para o main() função após as otSysProcessDrivers chamar, no while loop. Esta função, declarou em gpio.c , verifica se o botão foi pressionado, e se assim for, chama o manipulador ( handleButtonInterrupt ), que foi criado na etapa anterior.

otSysButtonProcess(instance);

AÇÃO: Implementar manipulador de interrupção de botão

Em main.c , adicione a implementação do handleButtonInterrupt função após a handleNetifStateChanged função que você adicionou na etapa anterior:

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

AÇÃO: Implementar inicialização UDP

Em main.c , adicione a implementação do initUdp função após a handleButtonInterrupt função que você acabou de adicionar:

/**
 * 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 é a porta que você definiu anteriormente (1212). O otUdpOpen função abre o soquete e registros uma função de retorno ( handleUdpReceive ) para quando uma mensagem UDP é recebido. otUdpBind liga o soquete para a interface de rede Tópico passando OT_NETIF_THREAD . Para outras opções de interface de rede, consulte o otNetifIdentifier enumeração em UDP API Reference .

AÇÃO: Implementar mensagens UDP

Em main.c , adicione a implementação do sendUdp função após a initUdp função que você acabou de adicionar:

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

Observe as otEXPECT e otEXPECT_ACTION macros. Estes fazem-se de que a mensagem UDP é válido e alocado corretamente no buffer, e se não, a função lida graciosamente erros saltando à exit do bloco, onde ele libera o buffer.

Veja as IPv6 e UDP Referências sobre openthread.io para mais informações sobre as funções usadas para inicializar UDP.

AÇÃO: Implementar tratamento de mensagens UDP

Em main.c , adicione a implementação do handleUdpReceive função após a sendUdp função que você acabou de adicionar. Esta função simplesmente alterna o 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);
}

8. API: configurar a rede Thread

Para facilitar a demonstração, queremos que nossos dispositivos iniciem imediatamente o Thread e se unam em uma rede quando são ligados. Para isso, usaremos o otOperationalDataset estrutura. Essa estrutura contém todos os parâmetros necessários para transmitir credenciais de rede Thread para um dispositivo.

O uso dessa estrutura substituirá os padrões de rede integrados ao OpenThread, para tornar nosso aplicativo mais seguro e limitar os nós de Thread em nossa rede apenas para aqueles que executam o aplicativo.

Mais uma vez, abra o ./openthread/examples/apps/cli/main.c arquivo em seu editor de texto preferido.

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

AÇÃO: Adicionar cabeçalho incluir

Dentro da seção no topo do inclui main.c arquivo, adicione o arquivo de cabeçalho API você precisa configurar a rede Tópico:

#include <openthread/dataset_ftd.h>

AÇÃO: Adicionar declaração de função para definir a configuração de rede

Adicionar esta declaração para main.c , após o cabeçalho inclui e antes de qualquer #if declarações. Esta função será definida após a função principal do aplicativo.

static void setNetworkConfiguration(otInstance *aInstance);

AÇÃO: Adicionar a chamada de configuração de rede

Em main.c , adicione esta chamada de função para o main() função após a otSetStateChangedCallback chamada. Esta função configura o conjunto de dados da rede Thread.

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

AÇÃO: Adicionar chamadas para habilitar a interface de rede Thread e empilhar

Em main.c , adicionar essas chamadas de função para o main() função após a otSysButtonInit chamada.

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

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

AÇÃO: Implementar configuração de rede Thread

Em main.c , adicione a implementação do setNetworkConfiguration função após o main() função:

/**
 * Override default network settings, such as panid, so the devices can join a
 network
 */
void setNetworkConfiguration(otInstance *aInstance)
{
    static char          aNetworkName[] = "OTCodelab";
    otOperationalDataset aDataset;

    memset(&aDataset, 0, sizeof(otOperationalDataset));

    /*
     * Fields that can be configured in otOperationDataset to override defaults:
     *     Network Name, Mesh Local Prefix, Extended PAN ID, PAN ID, Delay Timer,
     *     Channel, Channel Mask Page 0, Network Key, PSKc, Security Policy
     */
    aDataset.mActiveTimestamp                      = 1;
    aDataset.mComponents.mIsActiveTimestampPresent = true;

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

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

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

    /* Set network key to 1234C0DE1AB51234C0DE1AB51234C0DE */
    uint8_t key[OT_NETWORK_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE};
    memcpy(aDataset.mNetworkKey.m8, key, sizeof(aDataset.mNetworkKey));
    aDataset.mComponents.mIsNetworkKeyPresent = true;

    /* Set Network Name to OTCodelab */
    size_t length = strlen(aNetworkName);
    assert(length <= OT_NETWORK_NAME_MAX_SIZE);
    memcpy(aDataset.mNetworkName.m8, aNetworkName, length);
    aDataset.mComponents.mIsNetworkNamePresent = true;

    otDatasetSetActive(aInstance, &aDataset);
    /* Set the router selection jitter to override the 2 minute default.
       CLI cmd > routerselectionjitter 20
       Warning: For demo purposes only - not to be used in a real product */
    uint8_t jitterValue = 20;
    otThreadSetRouterSelectionJitter(aInstance, jitterValue);
}

Conforme detalhado na função, os parâmetros de rede Thread que estamos usando para este aplicativo são:

  • Canal = 15
  • PAN ID = 0x2222
  • PAN ID estendido = C0DE1AB5C0DE1AB5
  • Chave de rede = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Nome da rede = OTCodelab

Além disso, é aqui que diminuímos o Jitter de seleção do roteador, para que nossos dispositivos mudem de função mais rapidamente para fins de demonstração. Observe que isso só é feito se o nó for um FTD (Full Thread Device). Mais sobre isso na próxima etapa.

9. API: funções restritas

Algumas das APIs do OpenThread modificam configurações que devem ser modificadas apenas para fins de demonstração ou teste. Essas APIs não devem ser usadas em uma implantação de produção de um aplicativo usando OpenThread.

Por exemplo, o otThreadSetRouterSelectionJitter função ajusta o tempo (em segundos) que leva para um dispositivo final para se promover a um Router. O padrão para este valor é 120, de acordo com a especificação de thread. Para facilitar o uso neste Codelab, vamos alterá-lo para 20, então você não precisa esperar muito para que um nó Thread mude as funções.

Nota: Os dispositivos MTD não se tornam routers, e suporte para uma função como otThreadSetRouterSelectionJitter não está incluído em uma compilação MTD. Mais tarde precisamos especificar CMake opção -DOT_MTD=OFF , caso contrário, vamos encontrar uma falha de construção.

Você pode confirmar isso olhando para o otThreadSetRouterSelectionJitter definição de função, que está contido dentro de um pré-processador diretiva de OPENTHREAD_FTD :

./openthread/src/core/api/thread_ftd_api.cpp

#if OPENTHREAD_FTD

#include <openthread/thread_ftd.h>

...

void otThreadSetRouterSelectionJitter(otInstance *aInstance, uint8_t aRouterJitter)
{
    Instance &instance = *static_cast<Instance *>(aInstance);

    instance.GetThreadNetif().GetMle().SetRouterSelectionJitter(aRouterJitter);
}

...

#endif // OPENTHREAD_FTD

10. Atualizações CMake

Antes de construir seu aplicativo, existem algumas pequenas atualizações necessárias para três arquivos CMake. Eles são usados ​​pelo sistema de construção para compilar e vincular seu aplicativo.

./third_party/NordicSemiconductor/CMakeLists.txt

Agora adicione algumas bandeiras ao NordicSemiconductor CMakeLists.txt , para assegurar funções GPIO são definidos na aplicação.

AÇÃO: Adicionar sinalizadores ao CMakeLists.txt

Abrir ./third_party/NordicSemiconductor/CMakeLists.txt em seu editor de texto preferido, e adicione as seguintes linhas em COMMON_FLAG seção.

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

Agora adicione o novo gpio.c arquivo para o ./src/CMakeLists.txt arquivo.

AÇÃO: Adicione a fonte gpio ao

./src/CMakeLists.txt

Arquivo

Abrir ./src/CMakeLists.txt em seu editor de texto preferido, e adicionar o arquivo para o NRF_COMM_SOURCES seção.

...

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

...

./third_party/NordicSemiconductor/CMakeLists.txt

Por último, adicione o nrfx_gpiote.c arquivo de driver para o NordicSemiconductor CMakeLists.txt arquivo, então ele está incluído com a construção da biblioteca dos pilotos nórdicos.

AÇÃO: Adicione o driver gpio ao NordicSemiconductor CMakeLists.txt

Abrir ./third_party/NordicSemiconductor/CMakeLists.txt em seu editor de texto preferido, e adicionar o arquivo para o COMMON_SOURCES seção.

...

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

11. Configure os dispositivos

Com todas as atualizações de código feitas, você está pronto para construir e atualizar o aplicativo para todas as três placas de desenvolvimento nRF52840 nórdicas. Cada dispositivo funcionará como um Full Thread Device (FTD).

Construir OpenThread

Construa os binários OpenThread FTD para a plataforma nRF52840.

$ cd ~/ot-nrf528xx
$ ./script/build nrf52840 UART_trans -DOT_MTD=OFF -DOT_APP_RCP=OFF -DOT_RCP=OFF

Navegue até o diretório com o binário OpenThread FTD CLI e converta-o para o formato hexadecimal com o ARM Embedded Toolchain:

$ cd build/bin
$ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex

Flash as placas

Piscar o ot-cli-ftd.hex arquivo para cada placa nRF52840.

Conecte o cabo USB à porta de depuração Micro-USB ao lado do pino de alimentação externa na placa nRF52840, e depois ligá-lo em sua máquina Linux. Defina o corretamente, LED5 está ligado.

20a3b4b480356447.png

Como antes, observe o número de série da placa nRF52840:

c00d519ebec7e5f0.jpeg

Navegue até a localização das ferramentas de linha de comando nRFx e atualize o arquivo hex OpenThread CLI FTD na placa nRF52840, usando o número de série da placa:

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

LED5 desligará brevemente durante o piscar. A seguinte saída é gerada em caso de sucesso:

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.

Repita este passo "Flash the boards" para as outras duas placas. Cada placa deve ser conectada à máquina Linux da mesma forma, e o comando para piscar é o mesmo, exceto pelo número de série da placa. Certifique-se de usar o número de série exclusivo de cada placa no

nrfjprog comando piscar.

Se for bem-sucedido, o LED1, LED2 ou LED3 acenderá em cada placa. Você pode até ver o LED aceso mudar de 3 para 2 (ou 2 para 1) logo após piscar (o recurso de mudança de função do dispositivo).

12. Funcionalidade do aplicativo

Todas as três placas nRF52840 agora devem estar ligadas e rodando nosso aplicativo OpenThread. Conforme detalhado anteriormente, este aplicativo possui dois recursos principais.

Indicadores de função do dispositivo

O LED aceso em cada placa reflete a função atual do nó Thread:

  • LED1 = Líder
  • LED2 = Roteador
  • LED3 = Dispositivo final

Conforme a função muda, o LED aceso também muda. Você já deve ter visto essas mudanças em uma ou duas placas dentro de 20 segundos após a inicialização de cada dispositivo.

UDP Multicast

Quando Button1 é pressionado em uma placa, uma mensagem UDP é enviada ao endereço multicast local da malha, que inclui todos os outros nós na rede Thread. Em resposta a receber esta mensagem, LED4 em todos os outros alterna placas ligado ou desligado. O LED4 permanece aceso ou apagado para cada placa até receber outra mensagem UDP.

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. Demonstração: Observe as mudanças de função do dispositivo

Os dispositivos que você atualizou são um tipo específico de Full Thread Device (FTD) chamado Router Eligible End Device (REED). Isso significa que eles podem funcionar como um roteador ou dispositivo final e podem se promover de um dispositivo final a um roteador.

O thread pode suportar até 32 roteadores, mas tenta manter o número de roteadores entre 16 e 23. Se um REED for anexado como um dispositivo final e o número de roteadores for inferior a 16, ele será automaticamente promovido a um roteador. Esta mudança deve ocorrer em um horário aleatório dentro do número de segundos que você defina o otThreadSetRouterSelectionJitter valor no pedido (20 segundos).

Toda rede Thread também possui um Líder, que é um Roteador responsável por gerenciar o conjunto de Roteadores em uma rede Thread. Com todos os dispositivos ligados, após 20 segundos um deles deverá ser Líder (LED1 aceso) e os outros dois deverão ser Roteadores (LED2 aceso).

4e1e885861a66570.png

Remova o líder

Se o Líder for removido da rede Thread, um Roteador diferente se autopromoverá a Líder, para garantir que a rede ainda tenha um Líder.

Desligue a placa do líder (aquele com LED1 aceso) usando o interruptor de alimentação. Espere cerca de 20 segundos. Em uma das duas placas restantes, o LED2 (Roteador) apagará e o LED1 (Líder) acenderá. Este dispositivo é agora o líder da rede Thread.

4c57c87adb40e0e3.png

Ligue a Tabela de Líderes original. Ele deve se reconectar automaticamente à rede Thread como um dispositivo final (o LED3 está aceso). Em 20 segundos (o Jitter de seleção do roteador), ele se autopromoverá a um roteador (o LED2 está aceso).

5f40afca2dcc4b5b.png

Reinicialize as placas

Desligue todas as três placas, ligue-as novamente e observe os LEDs. A primeira placa que foi ligada deve começar na função de Líder (LED1 aceso) - o primeiro Roteador em uma rede Thread torna-se automaticamente o Líder.

As outras duas placas se conectam inicialmente à rede como Dispositivos Finais (o LED3 está aceso), mas devem se promover como Roteadores (o LED2 está aceso) em 20 segundos.

Partições de rede

Se suas placas não estão recebendo energia suficiente, ou a conexão de rádio entre elas está fraca, a rede Thread pode se dividir em partições e você pode ter mais de um dispositivo sendo exibido como Líder.

Thread é autocurável, então as partições devem eventualmente se fundir em uma única partição com um Líder.

14. Demonstração: Enviar multicast UDP

Se continuar do exercício anterior, o LED4 não deve acender em nenhum dispositivo.

Escolha qualquer placa e pressione o Botão1. O LED4 em todas as outras placas na rede Thread que executa o aplicativo deve alternar seu estado. Se estiver continuando do exercício anterior, eles devem estar ligados agora.

f186a2618fdbe3fd.png

Pressione o Botão1 para a mesma placa novamente. O LED4 em todas as outras placas deve alternar novamente.

Pressione o Button1 em uma placa diferente e observe como o LED4 alterna nas outras placas. Pressione o Botão1 em uma das placas onde o LED4 está aceso. O LED4 permanece aceso para aquela placa, mas alterna nas outras.

f5865ccb8ab7aa34.png

Partições de rede

Se seus fóruns estiverem particionados e houver mais de um Líder entre eles, o resultado da mensagem multicast será diferente entre os fóruns. Se você pressionar o Botão1 em uma placa que foi particionada (e, portanto, é o único membro da rede Thread particionada), o LED4 nas outras placas não acenderá em resposta. Se isso acontecer, reinicie as placas - o ideal é que elas reformam uma única rede Thread e as mensagens UDP devem funcionar corretamente.

15. Parabéns!

Você criou um aplicativo que usa APIs OpenThread!

Agora você sabe:

  • Como programar os botões e LEDs nas placas nRF52840 dev nórdicas
  • Como usar APIs OpenThread comuns ea otInstance classe
  • Como monitorar e reagir às mudanças de estado do OpenThread
  • Como enviar mensagens UDP para todos os dispositivos em uma rede Thread
  • Como modificar Makefiles

Próximos passos

Partindo deste Codelab, tente os seguintes exercícios:

  • Modifique o módulo GPIO para usar pinos GPIO em vez dos LEDs integrados e conecte LEDs RGB externos que mudam de cor com base na função do roteador
  • Adicionar suporte GPIO para uma plataforma de exemplo diferente
  • Em vez de usar multicast o ping todos os dispositivos de pressionar um botão, utilize a API Router / Líder de localizar e executar ping um dispositivo individual
  • Conectar a sua rede de malha para a internet usando um OpenThread Border Router e multicast-los de fora da rede Tópico a luz dos LEDs

Leitura adicional

Confira openthread.io e GitHub para uma variedade de recursos OpenThread, incluindo:

Referência: