Introduction: You Have to Know This: GATEWAY!
Because of the many questions I received about how data sending works not only from one LoRa to another, but also to a server, today we are going to discuss two things that we have already shown previously, but in separate videos. I will make a LoRa (the EndPoint) send to another LoRa (the Gateway) data of a BME280 sensor of temperature, humidity, and pressure. Who receives the information will send all this to IBM Watson through the MQTT protocol.
To understand clearer, I suggest you watch these two videos concerning this subject:
Step 1: Communication
Here is the outline of our project.
I want to highlight one thing that I consider important: when EndPoint sends data to the Gateway, it is not TCP-IP and LoRaWAN, but rather by LoRa, which is the LoRa radio protocol.
Step 2: Graphic
This is the IBM Watson graphical screen. You track variations in decimal places. This is practical and fast to work with, without the need to make or program any type of chart.
Step 3: Heltec WiFi LoRa 32 Pinout
Step 4: BME280
I leave here also the pinout of the BME280. I really liked this because it is i2c.
Step 5: Assembly
Step 6: Demonstration
This video shows a demonstration of the circuit running. I can say that this is a very complete example of IoT (Internet of Things).
Step 7: Library BME280
In the Arduino IDE, go to Sketch-> Include Library-> Manage Libraries ...
Look for bme280 and install the Adafruit BME280 Library
Step 8: Adafruit Unified Sensor Library
In the Arduino IDE, go to Sketch-> Include Library-> Manage Libraries ...
Search for Adafruit sensor based libraries and install the Adafruit Unified Sensor
Step 9: PubSubClient Library
In the Arduino IDE, go to Sketch-> Include Library-> Manage Libraries ...
Search for pubsubclient and install PubSubClient
Step 10: Modify LoRa 32 WiFi I2c Pinout
Open the file:
C: \ Users \ ALU \ Documents and Settings \ Arduino \ hardware \ hel32 \ var32 \ variants \ wifi_lora_32 \ pins_arduino.h and change the SDA pins to 4 and the SCL pin to 15
Step 11: Source Code
I have explained several parts of the source code. This includes the two codes we have here, as we have the Master and the Slave. Our main focus of this video, therefore, will be on the Master, which in this case is the router in the part concerning Master.ino - Receive.
I will show in this article all parts of the code again, so you can view it with ease.
Step 12: LoRaSendReceiveBME280MQTT.ino
At the beginning, we will include the libraries and define the destinations of the GPIOs, in addition to the frequency of the radio. Also, we will create constants to inform the Slave about the data work, as well as the return of these to the Master. We also structure the sensor data and point the variable to control the 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
In this first Setup, we will act on the configuration of the 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
Here, we’ll look at the initial LoRa settings.
//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(); }
Step 13: Master.ino
In this step, only compile if MASTER is set in the main file. We include the PubSubCliente.h and WiFi.h libraries; we replace the SSID of the network itself, as well as the password and Server MQTT that we will use. Also, we give a name to the topic from where we should send the data, so that they appear in the charts. We point out the ID we will use to connect. Finally, QUICK_STAR should remain as is.
//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:";
In DEVICE_ID, we have changed it to a unique id. In this example, we use the MAC Address of the device we are using. It will serve as identification on the 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);
We define intervals between the sends, and the variables to store the values of temperature, humidity, and pressure, as well as the time of the last sending and the location of the data that arrives from the other LoRa device.
//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
We make in this part the configurations that involve the Master, calling the initial configurations of the display and the LoRa. We also connect to the WiFi network.
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
Here, we have the function responsible for connecting to the MQTT server.
//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
Already in this step, we work with the function responsible for connecting the WiFi network.
//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
In this Loop, we define the sending of the package to inform the Slave the desire to receive data and also check if there are packages to be received.
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
We initialize the package and send what is contained in "GETDATA", to finalize and send the package later.
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
In this step, we check if the packet has the minimum length of characters that we expect and store a string. We still analyze if the header is what we expect, and we start to read the data and show it on the display.
We then create the json that we will send to the MQTT server. We publish on the topic where the server waits to receive and generate the graph.
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
Master.ino - showData
Finally, we show the time the Master took to create the package, send it, receive the Slave, read it, create a new package, and send it to the Master, who receives it and reads it. This is printed on the 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
Here we have the function responsible for creating a Json with the data read.
//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
Step 14: Slave.ino
Starting the Slave code, we only compiled if the Master is not defined in the main file. We have included the libraries and pointed out the individual responsible for reading the temperature, pressure, and humidity.
//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
In Slave Setup, we call up the initial display and LoRa settings.
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
As in the Master, in this part of the Slave Loop, we check if it has the expected number of characters and store the data in a String. We also simulated the reading of the data. We create the package, finalize it, and send it. Finally, we show the information on the 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
Finally, the data is displayed.
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
Step 15: Graphic
To view the sensor graph, go to https://quickstart.internetofthings.ibmcloud.com
In the Device id field, enter the DEVICE_ID that you defined in the code.
It is important to change in the code this due id for a unique id, which is used only by you.
This avoids conflict with data sent by another person.
Accept the terms and click Go.