ESP32 LoRa tutorial simples

Os rádios subgiga são incríveis – em particular, o LoRa (e mais intrinsecamente, o ESP32 LoRA) . A grande questão é que tudo tem contrapartida; quanto maior a banda, maior a velocidade de transmissão, mas menor alcance. Quanto menor a banda, menor velocidade de transmissão, mas maior alcance.

Um rádio LoRa rodando em 915MHz consegue transferir pouco mais de 37kbps. Não é pouco para mensagem, porém para arquivos binários é algo impactante. Mas ainda que venhamos a utilizar apenas mensagens, devemos ter determinados cuidados para evitar que sobrecarreguemos a banda de transmissão, não apenas do transmissor para o receptor, mas cuidando também para não sobrecarregar o receptor, ainda mais se ele for um concentrador de rádios.

AFMultiRadio Black Edition

Concentrador de rádios LoRa – AFMultiRadio

Crie um protocolo de mensagens

Lhe parece bastante intuitivo uma mensagem como essa abaixo?

temp=32,umid=40,rele1=on,rele2=off,ad0=524

Parece um ótimo protocolo. Fazendo split em “,” já temos os campos separados. Depois, só validar os campos e fazer split no sinal “=”. Daí o segundo campo é o valor, a ser convertido para inteiro. Mas vamos às considerações;

de todos esses 43 Bytes, apenas 12 serão utilizados. E pior, poderiam ser apenas 8 (desconsiderando algo importante que falta nessa mensagem).

Usar mensagens nesse formato é péssimo para a transmissão e para o processamento. Se chegar uma mensagem de ruído de outro dispositivo que não pertença à sua rede LoRa, essa mensagem passará por processamento desnecessariamente.

Mais uma coisa importantíssima; esse formato é ótimo para leitura humana, mas quem vai “ver” a mensagem é uma microcontroladora, nada disso é necessário. Depois de recebido, se for necessário exibir para o usuário, então adiciona-se o respectivo texto, fora da transmissão do ESP32 LoRa.

Pode parecer exagero pensando 1 para 1, mas em indústria, agro-negócio, condomínios, muito provavelmente diversos dispositivos poderão estar se comunicando com um concentrador. E pode ser pior.

Imagine se toda a informação do seu dispositivo for enviada para um broker MQTT na nuvem, onde clientes do país inteiro concentram seus dados em um sistema de gerenciamento. Por isso, “menos é mais”. Fui claro?

Vejamos uma mensagem em um formato mais adequado:

32 40 1 0 524

Os valores sempre serão posicionais, portanto não há razão para usar identificadores.

Parece bem pequena a mensagem, mas o ideal é mandar os bytes. Desse modo (como mostrado anteriormente), ainda consome-se mais recursos do que o necessário. Então o formato melhorado seria:

20 28 1 0 00 ff ff 0e

“Hummm, aumentou de tamanho!” – Você pode ter pensado.
Se mandarmos em formato literal, cada caractere será 1 Byte, totalizando 13 Bytes. Mandando em Bytes, cada Byte é, bem; 1 Byte.

Não se incomode em ver o formato em hexa. Você não precisará calcular nada disso, o trabalho será todo do programa, como pode ser visto no código mais adiante.

Para finalizar, ainda será necessário garantir a origem da mensagem minimamente. Não podemos ler qualquer coisa que chegar e considerar verdadeira, por isso o ideal é colocar um identificador de início e um de fim de mensagem. Explico.

$ 32 40 1 0 524 \n

Voltando ao formato literal, podemos fazer interpretação humana, por isso o fiz na mensagem acima. Repare que ela inicia com “$”. Isso é, se a mensagem que chegar não tiver esse primeiro Byte, descarta-se imediatamente.

Se a mensagem que chegar iniciar com “$”, validamos o último Byte. Se a mensagem não terminar com “\n”, descarta-se a mensagem. Mas antes de tudo, temos que ler o buffer de qualquer maneira, então a primeira validação deveria ser o tamanho da mensagem.

Agora sim, temos um protocolo definido! Vejamos:
0 – início da mensagem
1 – temperatura
2 – umidade
3 – relay 1
4 – relay 2
5, 6, 7 e 8 – leitura do AD, que vai de 0 a 1023, portanto 4 Bytes.
9 – terminador

Desse modo, nossa mensagem final seria parecido com:

24 20 28 1 0 00 ff ff 0e 0a

Agora podemos escrever nosso código.

Código para comunicação com ESP32 LoRa

As bibliotecas estão disponíveis no repositório do Arduino, portanto não há o que se falar a respeito da instalação. Já em relação à configuração, um pequeno ajuste deve ser feito para funcionar adequadamente o rádio LoRa. Siga esse tutorial, suba o sketch. Tendo funcionado, apenas substitua o código de teste pelo código a seguir:

#include <Arduino.h>
#include <SPI.h>
#include <LoRa.h>
#include <WiFi.h>
#include "SSD1306.h"
#include "board_def.h"

#define OLED_ADDRESS 0x3c
#define I2C_SDA 21
#define I2C_SCL 22

SSD1306Wire display(OLED_ADDRESS, I2C_SDA, I2C_SCL, GEOMETRY_128_32);

#define WIFI_SSID       "SuhankoFamily"
#define WIFI_PASSWORD   "fsjmr112"

String topics[5];
int adc_sum = 0;

struct msgRecv {
    uint8_t start = '$';

    uint8_t temperature = 0;
    uint8_t humidity    = 0;
    uint8_t relay_1     = 0;
    uint8_t relay_2     = 0;

    int adc_value       = 0;

    uint8_t end         = '\n';
} msg_recv;

uint8_t msg_array[10] = {0};

void setup()
{
    topics[0] = "Temp: ";
    topics[1] = "Umidade: ";
    topics[2] = "Rele 1: ";
    topics[3] = "Rele 2: ";
    topics[4] = "AD: ";

    Serial.begin(9600);
    while (!Serial);

    if (OLED_RST > 0) {
        pinMode(OLED_RST, OUTPUT);
        digitalWrite(OLED_RST, HIGH);
        delay(100);
        digitalWrite(OLED_RST, LOW);
        delay(100);
        digitalWrite(OLED_RST, HIGH);
    }

    display.init();
    display.flipScreenVertically();
    display.clear();
    display.setFont(ArialMT_Plain_16);
    display.setTextAlignment(TEXT_ALIGN_CENTER);
    display.drawString(display.getWidth() / 2, display.getHeight() / 2, LORA_SENDER ? "LoRa Sender" : "LoRa Receiver");
    display.display();
    delay(2000);


    String info = "(bB)";
    if (info != "") {
        display.clear();
        display.setFont(ArialMT_Plain_16);
        display.setTextAlignment(TEXT_ALIGN_CENTER);
        display.drawString(display.getWidth() / 2, display.getHeight() / 2, info);
        display.display();
        delay(2000);
    }

    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        display.clear();
        Serial.println("WiFi Connect Fail");
        display.drawString(display.getWidth() / 2, display.getHeight() / 2, "WiFi Connect Fail");
        display.display();
        delay(2000);
        esp_restart();
    }
    Serial.print("Connected : ");
    Serial.println(WiFi.SSID());
    Serial.print("IP:");
    Serial.println(WiFi.localIP().toString());
    display.clear();
    display.drawString(display.getWidth() / 2, display.getHeight() / 2, "IP:" + WiFi.localIP().toString());
    display.display();
    delay(2000);

    SPI.begin(CONFIG_CLK, CONFIG_MISO, CONFIG_MOSI, CONFIG_NSS);
    LoRa.setPins(CONFIG_NSS, CONFIG_RST, CONFIG_DIO0);
    if (!LoRa.begin(BAND)) {
        Serial.println("Starting LoRa failed!");
        while (1);
    }
    if (!LORA_SENDER) {
        display.clear();
        display.drawString(display.getWidth() / 2, display.getHeight() / 2, "LoraRecv Ready");
        display.display();
    }
}

void loop()
{
#if LORA_SENDER
    int32_t rssi;
    if (WiFi.status() == WL_CONNECTED) {
        rssi = WiFi.RSSI();
        display.clear();
        display.setTextAlignment(TEXT_ALIGN_CENTER);
        display.drawString(display.getWidth() / 2, display.getHeight() / 2, "Send RSSI:" + String(rssi));
        display.display();
        LoRa.beginPacket();
        LoRa.print("WiFi RSSI: ");
        LoRa.print(rssi);
        LoRa.endPacket();
    } else {
        Serial.println("WiFi Connect lost ...");
    }
    delay(2500);
#else
    if (LoRa.parsePacket()){
        memset(msg_array,0,10);
        uint8_t counter = 0;

        //enquanto tiver dados para ler, tem que ler.
        while (LoRa.available()){
            
            //armazena no buffer...
            msg_array[counter] = (char)LoRa.read();

            if (msg_array[0] == '$'){
                //...mas só incrementa a posição se o primeiro Byte for $
                counter++;
            }

            //Se a mensagem chegou ao Byte 9 sem o LF, então sai do loop, porque o buffer está limitado a 10 Bytes
            if (counter > 9){
                break; //mensagem alem do tamanho permitido
            }
            
        }

        //Saiu do loop. A mensagem que está no buffer parece ser legítima?
        if (msg_array[0] == msg_recv.start && msg_array[9] == msg_recv.end){
            
            //Então compõe uma string para o usuário com o resultado de cada valor
            for (uint8_t i=1;i<5;i++){
                display.clear();
                String msg = topics[i-1] + String(msg_array[i]);
                display.drawString(display.getWidth() / 2, display.getHeight() / 2, msg.c_str());
                display.display();
                delay(800);
            }   
            //E o valor AD tem 4 Bytes, portanto é necessário somá-los para entregar o valor correto ao usuário
            adc_sum = msg_array[5] + msg_array[6] + msg_array[7] + msg_array[8];

            Serial.println(adc_sum);
            display.clear();
            String msg2 = topics[4] +  String(adc_sum);
            display.drawString(display.getWidth() / 2, display.getHeight() / 2, msg2.c_str());
            display.display();
            delay(800);

        }

    }
#endif
}

Repare que no código adicionei comentários nos tratamentos feitos na função loop().

Onde comprar o ESP32 LoRa Paxcounter ?

Esse lindíssimo ESP32 LoRa está disponível na UsinaInfo, e você pode conferir preço e disponibilidade através desse link. Não perca a oportunidade de ter um TTGO, você vai amar!

Sender LoRa

Para fazer os testes, utilizei o AFMultiRadio Black Edition da AFEletronica, desse outro artigo. Essa é outra placa indescritível, a qualidade é espetacular e é a primeira no mundo (pelo menos de todas as pesquisas que fiz) que possui 3 rádios LoRa 1276 e também a primeira do mundo que se comunica com os rádios diretamente pelo barramento SPI. Se quiser dar uma conferida na loja, clique nesse link.