Você tem que saber isso: GATEWAY!



Por conta de inúmeras perguntas que recebi sobre como funciona o envio de dados não só de um LoRa para outro, mas também para um servidor, hoje vamos tratar de duas coisas que, na realidade, já mostramos anteriormente, mas em vídeos separados. Ou seja, vou fazer um LoRa, o EndPoint, enviar para um outro LoRa, o Gateway, dados de um sensor BME280 sobre temperatura, umidade e pressão. Esse “cara” que recebe as informações vai enviar tudo isso para o IBM Watson através do protocolo MQTT.


Pessoal, para melhor compreensão, sugiro que vocês assistam esses dois vídeos que tratam deste assunto:






Quer comprar suas placas?
ESP32 neste link: https://bit.ly/2JH0dXN
(Não fica de "miserê". Compra logo 2 de cada porque se seu componente queimar, você vai ficar na mão!)


Comunicação

Mostro aqui o esquema do nosso projeto.
Quero destacar uma coisa que considero importante: quando o EndPoint envia dados para o Gateway, não é por TCP-IP e nem LoRaWAN, mas, sim, por LoRa, ou seja, o protocolo radio LoRa.




Gráfico

Esta aqui é a tela gráfica do IBM Watson. Você acompanha as variações em casas decimais. Isso de forma prática e rápida de trabalhar, sem necessidade de fazer ou programar qualquer tipo de gráfico.




Heltec WiFi LoRa 32 Pinout





BME280

Deixo aqui também o pinout do BME280. Gostei bastante dele por ele ser i2c.



Montagem




Demonstração

No vídeo você por ver uma demonstração do circuito funcionando. Posso dizer que esse é um exemplo bem completo de IOT (Internet of Things).



Biblioteca BME280

Na IDE do Arduino vá em Sketch->Incluir Biblioteca->Gerenciar Bibliotecas...
Procure por bme280 e instale Adafruit BME280 Library



Biblioteca Adafruit Unified Sensor

Na IDE do Arduino vá em Sketch->Incluir Biblioteca->Gerenciar Bibliotecas...
Procure por adafruit sensor based libraries e instale Adafruit Unified Sensor



Biblioteca PubSubClient

Na IDE do Arduino vá em Sketch->Incluir Biblioteca->Gerenciar Bibliotecas...
Procure por pubsubclient e instale PubSubClient



Modificar pinagem i2c do WiFi LoRa 32

Abra o arquivo:
C:\Users\<SEU_USUARIO>\Documents\Arduino\hardware\heltec\esp32\variants\wifi_lora_32\pins_arduino.h e altere os pinos SDA para 4 e o pino SCL para 15



Código fonte

Temos várias partes do código fonte que já expliquei, ou melhor, dos dois códigos que temos aqui, já que temos o Master e o Slave. Nosso foco principal deste vídeo, portanto, será no Master, que, no caso, é o roteador, na parte que tratamos do Master.ino – Receive.
De qualquer forma, vou expor aqui neste artigo todas as partes do código novamente, para facilitar sua visualização.


LoRaSendReceiveBME280MQTT.ino

De início, vamos incluir as bibliotecas e definir os destinos dos GPIOs, além da frequência do rádio. Ainda, vamos criar constantes para informar ao Slave sobre os trabalhos com os dados, bem como o retorno destes para o Master. Estruturamos ainda os dados do sensor e apontamos a variável para controlar o display.

#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <SSD1306.h>

//Deixe esta linha descomentada para compilar o Master
//Comente ou remova para compilar o Slave
#define MASTER

#define RST 14  // GPIO14 RESET
#define DI00 26 // GPIO26 IRQ(Interrupt Request)

#define BAND 433E6 //Frequência do radio - exemplo : 433E6, 868E6, 915E6

//Constante para informar ao Slave que queremos os dados
const String GETDATA = "get";
//Constante que o Slave retorna junto com os dados para o Master
const String SETDATA = "set";

//Estrutura com os dados do sensor
typedef struct {
  float temperature;
  float pressure;
  float humidity;
}Data;

//Variável para controlar o display
SSD1306 display(0x3c, SDA, SCL);


LoRaSendReceiveBME280MQTT.ino – setupDisplay

Neste primeiro Setup, vamos atuar na configuração do display.

void setupDisplay(){
  //O estado do GPIO16 é utilizado para controlar o display OLED
  pinMode(16, OUTPUT);
  //Reseta as configurações do display OLED
  digitalWrite(16, LOW);
  //Para o OLED permanecer ligado, o GPIO16 deve permanecer HIGH
  //Deve estar em HIGH antes de chamar o display.init() e fazer as demais configurações,
  //não inverta a ordem
  digitalWrite(16, HIGH);

  //Configurações do display
  display.init();
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_16);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
}


LoRaSendReceiveBME280MQTT.ino – setupLoRa

Aqui, vamos tratar das configurações iniciais do LoRa.

//Configurações iniciais do LoRa
void setupLoRa(){ 
  //Inicializa a comunicação
  SPI.begin(SCK, MISO, MOSI, SS);
  LoRa.setPins(SS, RST, DI00);

  //Inicializa o LoRa
  if (!LoRa.begin(BAND, true)){
    //Se não conseguiu inicializar, mostra uma mensagem no display
    display.clear();
    display.drawString(0, 0, "Erro ao inicializar o LoRa!");
    display.display();
    while (1);
  }

  //Ativa o crc
  LoRa.enableCrc();
  //Ativa o recebimento de pacotes
  LoRa.receive();
}


Master.ino

Nesta etapa, compila apenas se MASTER estiver definido no arquivo principal. Incluímos as bibliotecas PubSubCliente.h e WiFi.h, substituímos pelo SSID da própria rede, assim como senha e Server MQTT que iremos utilizar. Ainda, damos um nome ao tópico que devemos enviar os dados para que eles apareçam nos gráficos. Apontamos o ID que usaremos para conectar. Por fim, o QUICK_STAR deve permanecer como está.

//Compila apenas se MASTER estiver definido no arquivo principal
#ifdef MASTER

#include <PubSubClient.h>
#include <WiFi.h>

//Substitua pelo SSID da sua rede
#define SSID "TesteESP"

//Substitua pela senha da sua rede
#define PASSWORD "87654321"

//Server MQTT que iremos utlizar
#define MQTT_SERVER "quickstart.messaging.internetofthings.ibmcloud.com"

//Nome do tópico que devemos enviar os dados
//para que eles apareçam nos gráficos
#define TOPIC_NAME "iot-2/evt/status/fmt/json"

//ID que usaremos para conectar 
//QUICK_START deve permanecer como está
const String QUICK_START = "d:quickstart:arduino:";


No DEVICE_ID mudamos para um id único. Nesse exemplo utilizamos o MAC Address do dispositivo que estamos utilizando. Servirá como identificação no site https://quickstart.internetofthings.ibmcloud.com.

//No DEVICE_ID você deve mudar para um id único
//Aqui nesse exemplo utilizamos o MAC Address
//do dispositivo que estamos utilizando
//Servirá como identificação no site
//https://quickstart.internetofthings.ibmcloud.com
const String DEVICE_ID = "241ab91e0fa0";

//Concatemos o id do quickstart com o id do nosso
//dispositivo
const String CLIENT_ID =  QUICK_START + DEVICE_ID;

//Cliente WiFi que o MQTT irá utilizar para se conectar
WiFiClient wifiClient;

//Cliente MQTT, passamos a url do server, a porta
//e o cliente WiFi
PubSubClient client(MQTT_SERVER, 1883, wifiClient);


Definimos intervalos entre os envios, variáveis para guardarmos os valores de temperatura, umidade e pressão, assim como o tempo do último envio e onde ficam os dados que chegam do outro dispositivo LoRa.

//Intervalo entre os envios
#define INTERVAL 500

//Tempo do último envio
long lastSendTime = 0;

//Onde ficam os dados que chegam do outro dispositivo LoRa 
Data data;


Master.ino - setup

Fazemos nesta parte as configurações que envolvem o Master, chamando as configurações iniciais do display e do LoRa. Conectamos ainda à rede WiFi.

void setup(){
  Serial.begin(115200);
  //Chama a configuração inicial do display
  setupDisplay();
  //Chama a configuração inicial do LoRa
  setupLoRa();

  display.clear();
  display.drawString(0, 0, "Master");
  display.display();
  //Conectamos à rede WiFi
  setupWiFi();
  connectMQTTServer();
}


Master.ino – connectMQTTServer

Temos aqui a função responsável por conectar ao server MQTT.

//Função responsável por conectar ao server MQTT
void connectMQTTServer() {
  Serial.println("Connecting to MQTT Server...");
  //Se conecta ao id que definimos
  if (client.connect(CLIENT_ID.c_str())) {
    //Se a conexão foi bem sucedida
    Serial.println("connected");
  } else {
    //Se ocorreu algum erro
    Serial.print("error = ");
    Serial.println(client.state());
  }
}


Master.ino – setupWiFi

Já nesta etapa trabalhamos com a Função responsável por conectar a rede WiFi.

//Função responsável por conectar à rede WiFi
void setupWiFi() {
  Serial.println();
  Serial.print("Connecting to ");
  Serial.print(SSID);

  //Manda o esp se conectar à rede através
  //do ssid e senha
  WiFi.begin(SSID, PASSWORD);

  //Espera até que a conexão com a rede seja estabelecida
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  //Se chegou aqui é porque conectou
  Serial.println("");
  Serial.println("WiFi connected");
}


Master.ino – loop

Neste Loop, definimos o envio do pacote para informar ao Slave o desejo de receber dados e ainda verificamos se existem pacotes a serem recebidos.

void loop(){
  //Se passou o tempo definido em INTERVAL desde o último envio
  if (millis() - lastSendTime > INTERVAL){
    //Marcamos o tempo que ocorreu o último envio
    lastSendTime = millis();
    //Envia o pacote para informar ao Slave que queremos receber os dados
    send();
  }

  //Verificamos se há pacotes para recebermos
  receive();
}


Master.ino – send

Inicializamos o pacote e enviamos o que está contido em “GETDATA”, para posteriormente finalizar e enviar o pacote.

void send(){
  //Inicializa o pacote
  LoRa.beginPacket();
  //Envia o que está contido em "GETDATA"
  LoRa.print(GETDATA);
  //Finaliza e envia o pacote
  LoRa.endPacket();
}


Master.ino – receive

Nesta etapa, verificamos se o pacote tem o tamanho mínimo de caracteres que esperamos e armazenamos uma string. Ainda analisamos se o cabeçalho é o que esperamos, partimos para a leitura dos dados e os exibimos no display.
Criamos, então, o json que enviaremos para o server MQTT. Publicamos no tópico onde o servidor espera para receber e gerar o gráfico.

void receive(){
  //Tentamos ler o pacote
  int packetSize = LoRa.parsePacket();
  
  //Verificamos se o pacote tem o tamanho mínimo de caracteres que esperamos
  if (packetSize > SETDATA.length()){
    String received = "";
    //Armazena os dados do pacote em uma string
    for(int i=0; i<SETDATA.length(); i++){
      received += (char) LoRa.read();
    }

    //Se o cabeçalho é o que esperamos
    if(received.equals(SETDATA)){
      //Fazemos a leitura dos dados
      LoRa.readBytes((uint8_t*)&data, sizeof(data));
      //Mostramos os dados no display
      showData();

      Serial.print("Publish message: ");
      //Criamos o json que enviaremos para o server mqtt
      String msg = createJsonString();
      Serial.println(msg);
      //Publicamos no tópico onde o servidor espera para receber 
      //e gerar o gráfico
      client.publish(TOPIC_NAME, msg.c_str());
    }
  }
}


Master.ino – showData

Por fim, mostramos o tempo que o Master levou para criar o pacote, enviar, o Slave receber, fazer a leitura, criar um novo pacote e enviá-lo ao Master, que o recebe e faz a leitura. Este é impresso no display.

void showData(){
  //Tempo que demorou para o Master criar o pacote, enviar o pacote,
  //o Slave receber, fazer a leitura, criar um novo pacote, enviá-lo
  //e o Master receber e ler
  String waiting = String(millis() - lastSendTime);
  //Mostra no display os dados e o tempo que a operação demorou
  display.clear();
  display.drawString(0, 0, String(data.temperature) + " C");
  display.drawString(0, 16, String(data.pressure) + " Pa");
  display.drawString(0, 32, String(data.humidity) + "%");
  display.drawString(0, 48, waiting + " ms");
  display.display();
}


Master.ino – createJsonString

Temos aqui a função responsável por criar um Json com os dados lidos.

//Função responsável por criar
//um Json com os dados lidos
String createJsonString() {
  String json = "{";
    json+= "\"d\": {";
      json+="\"temperature\":";
      json+=String(data.temperature);
      json+=",";
      json+="\"humidity\":";
      json+=String(data.humidity);
      json+=",";
      json+="\"pressure\":";
      json+=String(data.pressure);
    json+="}";
  json+="}";
  return json;
}

#endif


Slave.ino

Iniciando o código do Slave, compilamos apenas se o Master não estiver definido no arquivo principal.  Incluímos as bibliotecas e apontamos a responsável pela leitura da temperatura, pressão e umidade.

//Compila apenas se MASTER não estiver definido no arquivo principal
#ifndef MASTER

#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

//Responsável pela leitura da temperatura, pressão e umidade
Adafruit_BME280 bme; 


Slave.ino – setup

No Setup do Slave, chamamos as configurações iniciais do display e do LoRa.

void setup(){
  Serial.begin(115200);
  //Chama a configuração inicial do display
  setupDisplay();
  //Chama a configuração inicial do LoRa
  setupLoRa();

  //0x76 se pino SDO do sensor estiver no GND
  //0x77 se pino SDO do sensor estiver no 3.3v 
  if (!bme.begin(0x76))
  {
    display.clear();
    display.drawString(0, 0, "Sensor não encontrado");
    display.display();
    while(1);
  }

  display.clear();
  display.drawString(0, 0, "Slave esperando...");
  display.display();
}


Slave.ino – loop

Assim como no Master, nesta parte do Loop do Slave, verificamos se o mesmo tem a quantidade de caracteres esperada e armazenamos os dados em uma String. Ainda, simulamos a leitura dos dados. Criamos o pacote, finalizamos e enviamos. Por fim, exibimos as informações no display.

void loop(){
  //Tenta ler o pacote
  int packetSize = LoRa.parsePacket();

  //Verifica se o pacote possui a quantidade de caracteres que esperamos
  if (packetSize == GETDATA.length())
  {
    String received = "";

    //Armazena os dados do pacote em uma string
    while(LoRa.available())
    {
      received += (char) LoRa.read();
    }

    if(received.equals(GETDATA))
    {
      //Faz a leitura dos dados
      Data data = readData();
      Serial.println("Criando pacote para envio");
      //Cria o pacote para envio
      LoRa.beginPacket();
      LoRa.print(SETDATA);
      LoRa.write((uint8_t*)&data, sizeof(data));
      //Finaliza e envia o pacote
      LoRa.endPacket();
      showSentData(data);
    }
  }
}


Slave.ino – showSentData

Enfim, exibidos os dados no display.

void showSentData(Data data)
{
  //Mostra no display
  display.clear();
  display.drawString(0, 0, "Enviou:");
  display.drawString(0, 16,  String(data.temperature) + " C");
  display.drawString(0, 32, String(data.pressure) + " Pa");
  display.drawString(0, 48, String(data.humidity) + "%");
  display.display();
}

#endif


Gráfico

Para visualizar o gráfico do sensor vá até https://quickstart.internetofthings.ibmcloud.com
No campo Device id digite o DEVICE_ID que você definiu no código.
É importante mudar no código este devido id para um id única, utilizado somente por você,
para não dar conflito com dados enviados por outra pessoa.
Aceite os termos e clique em Go.



Faça o download dos arquivos:

PDF

INO






Um comentário:

  1. Olá Fernando. Excelente trabalho, didática sensacional, parabéns ! Amigo, preciso de uma ajuda: nas minhas aplicações eu não tenho um WiFi disponível, trabalho em locais remotos, porém com cobertura GSM. Estou idealizando um gateway com o chip SIM800L operando em GMS/GPRS. Preciso de conectá-lo ao IMB Watson via GPRS. POde me ajudar indicando algum material de estudo ? Desde já agradeço.

    ResponderExcluir

Tecnologia do Blogger.