IOT Cloud with LoRa integration

0 18255 Medium

How can you make LoRa Nodes communicate with Arduino IOT Cloud? Check out my project :-)

 

IOT Cloud with LoRa integration

Things used in this project

 

Hardware components

HARDWARE LIST
Espressif ESP32 Development Board - Developer Edition
Pushbutton Switch, Momentary
DFRobot Monochrome 0.91”128x32 I2C OLED Display with Chip Pad
Buzzer
RFM95W Module
SparkFun Arduino Pro Mini 328 - 5V/16MHz
DHT22 Temperature Sensor

Software apps and online services

 

Arduino IoT Cloud

 

Arduino IDE

 

 

Hand tools and fabrication machines

 

Soldering iron (generic)

 

Solder Wire, Lead Free

Story

 

Introduction

 

This project has been done because I had the need to communicate data and text from areas where the WiFi was not present.

 

Therefore I setup a gateway that had the possibility to connect to WiFi and to have a LoRa antenna so that can reach also LoRa nodes up to a distance of 600 mt. The convenience of this is that I can place LoRa nodes in areas where the WiFi is not reachable but still have bidirectional data communication between the LoRa nodes and the Arduino IOT Cloud.

 

Diagram

 

The diagram picture explains the way I have integrated the pieces.

 

LoRa Arduino IOT Cloud Diagram

 

LoRa Arduino IOT Cloud Diagram

 

The Arduino IOT Cloud communicated with the gateway using the WiFi connection. For this component I've used the ES32 development board but any Arduino IOT compatible board should work.

 

Gateway

 

On the ESP32 Gateway, I've connected a RFM95W module with an antenna so that the ESP32 could then interface with the nodes. The library used for the LoRa side of the gateway is the LoRa Sandeep Mistry library.

 

Gateway

 

Gateway

 

 

ESP32 LoRa Arduino IOT Cloud Gateway Hardware diagram

 

ESP32 LoRa Arduino IOT Cloud Gateway Hardware diagram

 

The software of the gateway is coded so that any messages to the nodes are sent to ALL nodes. It can be adjusted to address only one specific node if required. To do so you need to change the "destination" from the value "FF" (all nodes) to the address of the node you want to address. For example "BA".

 

ESP Nodes

 

The nodes that I have used are mobile ESP LoRa nodes with Display, Push button and Buzzer. The buzzer is used to advice when a new message arrives. The hardware diagram is the following.

 

Messaging LoRa Node

 

Messaging LoRa Node

 

 

ESP32 LoRa node with Display, Push button and Buzzer

 

ESP32 LoRa node with Display, Push button and Buzzer

 

The node is programmed to send predefined messages to all the nodes including the Arduino IOT Cloud. If you want to address only the IOT Cloud then you need to change the "destination" variable from "FF" to "01".

 

Arduino Pro Mini 328 Node

 

The Arduino Pro Mini node has the function to collect Humidity, Temperature and Air Quality.

 

Air Quality LoRa Node

 

Air Quality LoRa Node

 

The schematics is the following:

 

Arduino Pro Mini LoRa Node

 

Arduino Pro Mini LoRa Node

 

The components used for the air monitoring are DHT22 and MQ135. LoRa s based on the usual RFM95W.

 

Arduino IOT Message Dashboard

 

The dashboard used for the messaging is very simple but can be modified to make it more complete.

 

LoRa Arduino IOT message dashboard

 

LoRa Arduino IOT message dashboard

 

This dashboard is very cool on the mobile because t looks like a messaging app.

The system allows to send and receive messages on both LoRa node(s) and Arduino IOT Cloud.

 

Arduino IOT Air Quality Dashboard

 

The dashboard used for the air quality is very simple but can be modified to make it more complete.

 

LoRa Arduino IOT air quality dashboard

 

LoRa Arduino IOT air quality dashboard

 

Schematics

 

ESP32 LoRa Arduino IOT Gateway

 

 

ESP32 LoRa Node with Display, Buzzer and push button

 

 

 

Arduino Mini Pro loRa node

 

 


 

Code

 

LoRaWK_Carlo.ino

Arduino

This is the LoRa Node with Display, Buzzer and Push Button

CODE
/*
  LoRa Messaging with ESP32

  This code send messages between Devices.
  
  created 19 06 2022
  by Carlo Stramaglia
*/

#include <SPI.h>              // include libraries
#include <LoRa.h>
#include <U8g2lib.h>
#include "OneButton.h"
#include "messages.h"

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);

#define PIN_INPUT 15 // PIN for push button
#define PIN_BUZ 4 // PIN for buzzer
// current Buzzer state, staring with LOW (0)
int buzState = LOW;
OneButton button(PIN_INPUT, true);

int exitFlag = 0;
int stringNumber = -1;

const int csPin = 5;          // LoRa radio chip select
const int resetPin = 14;       // LoRa radio reset
const int irqPin = 2;         // change for your board; must be a hardware interrupt pin

String outgoing;              // outgoing message

byte msgCount = 0;            // count of outgoing messages
byte localAddress = 0xBA;     // address of this device
byte destination = 0xFF;      // destination to send to
long lastSendTime = 0;        // last send time
int interval = 2000;          // interval between sends



void setup() {
  Serial.begin(9600);                   // initialize serial
  u8g2.begin();

  // enable the buzzer
  pinMode(PIN_BUZ, OUTPUT); // sets the digital pin as output
  digitalWrite(PIN_BUZ, buzState); // sets the buzzer in mute

  // link the doubleclick function to be called on a doubleclick event.
  button.attachClick(Click);
  button.attachLongPressStart(longPressStart);
  
  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin

  if (!LoRa.begin(868E6)) {             // initialize ratio at 868 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

  Serial.println("LoRa init succeeded.");
}

void loop() {

  delay(10);
  button.tick();
  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket());
}

void sendMessage(String outgoing) {
  LoRa.beginPacket();                   // start packet
  LoRa.write(destination);              // add destination address
  LoRa.write(localAddress);             // add sender address
  LoRa.write(msgCount);                 // add message ID
  LoRa.write(outgoing.length());        // add payload length
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}

void onReceive(int packetSize) {
  if (packetSize == 0) return;          // if there's no packet, return

  // read packet header bytes:
  int recipient = LoRa.read();          // recipient address
  byte sender = LoRa.read();            // sender address
  byte incomingMsgId = LoRa.read();     // incoming msg ID
  byte incomingLength = LoRa.read();    // incoming msg length

  String incoming = "";

  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }

  if (incomingLength != incoming.length()) {   // check length for error
    Serial.println("error: message length does not match length");
    return;                             // skip rest of function
  }



  // if the recipient isn't this device or broadcast,
  if (recipient != localAddress && recipient != 0xFF) {
    Serial.println("This message is not for me.");
    return;                             // skip rest of function
  }

  // if message is for this device, or broadcast, print details:
  Serial.println("Received from: 0x" + String(sender, HEX));
  Serial.println("Sent to: 0x" + String(recipient, HEX));
  Serial.println("Message ID: " + String(incomingMsgId));
  Serial.println("Message length: " + String(incomingLength));
  Serial.println("Message: " + incoming);
  Serial.println("RSSI: " + String(LoRa.packetRssi()));
  Serial.println("Snr: " + String(LoRa.packetSnr()));
  Serial.println();
  digitalWrite(PIN_BUZ, !buzState);
  Serial.println("Buzzer ON");
  delay (100);
  digitalWrite(PIN_BUZ, buzState);
  Serial.println("Buzzer OFF");
  sendDisplay(sender, incoming);
}

void sendDisplay(unsigned int nodeID, String messageLoRa) {
  char messageLocal[MAX_STRING_SIZE];
  u8g2.clearBuffer();          
  u8g2.setFont(u8g2_font_ncenB08_tr);
  if (nodeID == 1) 
    sprintf (messageLocal, "Message From: Cloud");
  else
    sprintf (messageLocal, "Message From: %d", nodeID);
  u8g2.drawStr(0,10, messageLocal);
  messageLoRa.toCharArray(messageLocal, MAX_STRING_SIZE);
  u8g2.drawStr(0,30, messageLocal); 
  u8g2.sendBuffer();          
  delay(1000);  
}


void Click()
{
  Serial.println("x1");
  stringNumber++;
  if (stringNumber > (NUMBER_OF_STRING-1))
    stringNumber = 0;
  displayString (stringNumber+1, arr[stringNumber]);
  
  exitFlag = 0;

} // Click

void longPressStart()
{
  Serial.println("Long");
  displaySend (stringNumber+1, arr[stringNumber]);
  // Send data to the node
  //LoRaNow.clear();
  sendMessage(arr[stringNumber]);
  exitFlag = 1;
 
} // LongPressStart


void displayString (unsigned int stringNo, char* stringToDisplay) {
  char messageLocal[MAX_STRING_SIZE];
  u8g2.clearBuffer();          
  u8g2.setFont(u8g2_font_ncenB08_tr); 
  sprintf (messageLocal, "Destination: %d", destination);   
  u8g2.drawStr(0,10, messageLocal);
  sprintf (messageLocal, "Text selection: %d of %d", stringNo, NUMBER_OF_STRING); 
  u8g2.drawStr(0,20, messageLocal);
  u8g2.drawStr(0,30, stringToDisplay); 
  u8g2.sendBuffer();          
  delay(100);  
}

void displaySend (unsigned int stringNo, char* stringToDisplay) {
  char messageLocal[MAX_STRING_SIZE];
  u8g2.clearBuffer();          
  u8g2.setFont(u8g2_font_ncenB08_tr); 
  sprintf (messageLocal, "Destination: %d", destination);
  u8g2.drawStr(0,10, messageLocal);
  sprintf (messageLocal, "Sending Message: %d of %d", stringNo, NUMBER_OF_STRING);  
  u8g2.drawStr(0,20, messageLocal);
  u8g2.drawStr(0,30, stringToDisplay); 
  u8g2.sendBuffer();          
  delay(100);  
}

messages.h

Arduino

Pre-Defined messages inlude file

CODE
#define NUMBER_OF_STRING 4
#define MAX_STRING_SIZE 40

char arr[NUMBER_OF_STRING][MAX_STRING_SIZE] =
{ "Get on the phone!",
  "The music is too loud",
  "I'm ready to move",
  "See you at 10:00 AM"
};

LoRa_ESP32_Cloud.ino

Arduino

ESP32 LoRa Arduino IOT Cloud gateway

CODE
/* 
  LoRa Cloud Arduino IOT Gateway with ESP32

  Created by Carlo Stramaglia 26 07 2022
  
*/

#include "thingProperties.h"
#include <SPI.h>              // include libraries
#include <LoRa.h>

const int csPin = 5;          // LoRa radio chip select
const int resetPin = 14;       // LoRa radio reset
const int irqPin = 2;         // change for your board; must be a hardware interrupt pin

byte msgCount = 0;            // count of outgoing messages
byte localAddress = 0x01;     // address of this device
byte destination = 0xFF;      // destination to send to
long lastSendTime = 0;        // last send time
int interval = 2000;          // interval between sends

String incoming;
String outgoing;              // outgoing message

void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500);
   
  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin

  if (!LoRa.begin(868E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }
  
  Serial.println("LoRa init succeeded.");

  delay(1500);

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  // Your code here
  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket()); 
  //ciaoString = incoming;
  
  
}



/*
  Since CiaoString is READ_WRITE variable, onCiaoStringChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onCiaoStringChange()  {
  // Add your code here to act upon CiaoString change
  sendMessage(ciaoString);
}

void sendMessage(String outgoing) {
  LoRa.beginPacket();                   // start packet
  LoRa.write(destination);              // add destination address
  LoRa.write(localAddress);             // add sender address
  LoRa.write(msgCount);                 // add message ID
  LoRa.write(outgoing.length());        // add payload length
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}

void onReceive(int packetSize) {
  if (packetSize == 0) return;          // if there's no packet, return

  // read packet header bytes:
  int recipient = LoRa.read();          // recipient address
  byte sender = LoRa.read();            // sender address
  byte incomingMsgId = LoRa.read();     // incoming msg ID
  byte incomingLength = LoRa.read();    // incoming msg length

  Serial.println("Received from: 0x" + String(sender, HEX));
  Serial.println("Sent to: 0x" + String(recipient, HEX));
  Serial.println("Message ID: " + String(incomingMsgId));
  Serial.println("Message length: " + String(incomingLength));
  Serial.println();

  incoming = "";
  byte incomingData[20];
  int i=0;
  
  if (sender != 0x2) {
    while (LoRa.available()) {
      incoming += (char)LoRa.read();
    }
/*    if (incomingLength != incoming.length()) {   // check length for error
    Serial.println("error: message length does not match length");
    return;                             // skip rest of function
    } */
  }
  else {
    Serial.println("We are in the case of data rceiving");    
    while (LoRa.available()) {
    incomingData[i] = LoRa.read();
    i++;
    }    
  }
  
  

  // if the recipient isn't this device or broadcast,
  if (recipient != localAddress && recipient != 0xFF) {
    Serial.println("This message is not for me.");
    return;                             // skip rest of function
  }

  if (sender != 0x2) {
    // if message is for this device, or broadcast, print details:
    Serial.println("Received from: 0x" + String(sender, HEX));
    Serial.println("Sent to: 0x" + String(recipient, HEX));
    Serial.println("Message ID: " + String(incomingMsgId));
    Serial.println("Message length: " + String(incomingLength));
    Serial.println("Message: " + incoming);
    Serial.println("RSSI: " + String(LoRa.packetRssi()));
    Serial.println("Snr: " + String(LoRa.packetSnr()));
    Serial.println(); 
    ciaoString = "Device " + String (sender,DEC) + ": " + incoming;
    }
  else {
    humidity = ((incomingData[2] << 8) | incomingData[3]);
    humidity = humidity/10;
    temperature = ((incomingData[0] << 8) | incomingData[1]);
    temperature = temperature/10;
    rzero = ((incomingData[4] << 16) | incomingData[5] << 8 | incomingData[6]);
    rzero = rzero/100;
    crzero = ((incomingData[7] << 16) | incomingData[8] << 8 | incomingData[9]);
    crzero = crzero/100;
    resistance = ((incomingData[10] << 16) | incomingData[11] << 8 | incomingData[12]);
    resistance = resistance/100;
    ppm = ((incomingData[13] << 16) | incomingData[14] << 8 | incomingData[15]);
    ppm = ppm/100;
    cppm = ((incomingData[16] << 16) | incomingData[17] << 8 | incomingData[18]);
    cppm = cppm/100;
    Serial.println("Final Temperature: " + String (temperature));
    Serial.println("Final Humidity: " + String (humidity));
    Serial.println("Final rzero: " + String (rzero));
    Serial.println("Final crzero: " + String (crzero));
    Serial.println("Final resistance: " + String (resistance));
    Serial.println("Final ppm: " + String (ppm));
    Serial.println("Final cppm: " + String (cppm));
  }
}

thingProperties.h

Arduino

CODE
// Code generated by Arduino IoT Cloud, DO NOT EDIT.

#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>

const char DEVICE_LOGIN_NAME[]  = "TO BE REPLACED WITH YOUR DATA";

const char SSID[]               = "TO BE REPLACED WITH YOUR DATA";    // Network SSID (name)
const char PASS[]               = "TO BE REPLACED WITH YOUR DATA";    // Network password (use for WPA, or use as key for WEP)
const char DEVICE_KEY[]  = "TO BE REPLACED WITH YOUR DATA";    // Secret device password

void onCiaoStringChange();

String ciaoString;
float cppm;
float crzero;
float ppm;
float resistance;
float rzero;
CloudTemperatureSensor temperature;
CloudRelativeHumidity humidity;

void initProperties(){

  ArduinoCloud.setBoardId(DEVICE_LOGIN_NAME);
  ArduinoCloud.setSecretDeviceKey(DEVICE_KEY);
  ArduinoCloud.addProperty(ciaoString, READWRITE, ON_CHANGE, onCiaoStringChange);
  ArduinoCloud.addProperty(cppm, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(crzero, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(ppm, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(resistance, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(rzero, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(temperature, READ, ON_CHANGE, NULL);
  ArduinoCloud.addProperty(humidity, READ, ON_CHANGE, NULL);

}

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

LoRa_Temperature_and_Air_Node.ino

Arduino

 


CODE
/*
  LoRa Temperature and Air Node.

  This code send Temperature and MQ135 data to a Gateway for Arduino IOT data collection.
  
  created 25 07 2022
  by Carlo Stramaglia
*/

#include <SPI.h>              // include libraries
#include <LoRa.h>
#include <MQ135.h>
#include <DHT.h>

const int csPin = 10;          // LoRa radio chip select
const int resetPin = 7;       // LoRa radio reset
const int irqPin = 4;         // change for your board; must be a hardware interrupt pin

String outgoing;              // outgoing message

byte msgCount = 0;            // count of outgoing messages
byte localAddress = 0x02;     // address of this device
byte destination = 0x01;      // destination to send to

int Temperature = 0;        // Temperature Integer
int Humidity = 0;           // Humidity Integer
int Rzero = 0;
int cRzero = 0;
int Resistance = 0;
int Ppm = 0;
int Cppm = 0;

int payloadLenght = 19;
byte Data[19];

#define PIN_MQ135 A0 // MQ135 Analog Input Pin
#define DHTPIN 3 // DHT Digital Input Pin
#define DHTTYPE DHT22 // DHT11 or DHT22, depends on your sensor

MQ135 mq135_sensor(PIN_MQ135);
DHT dht(DHTPIN, DHTTYPE);

float temperature, humidity; // Temp and Humid floats, will be measured by the DHT

void setup() {
  Serial.begin(9600);                   // initialize serial
  dht.begin();
  delay (1000);  
  
  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin

  if (!LoRa.begin(868E6)) {             // initialize ratio at 868 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

  Serial.println("LoRa init succeeded.");
}

void loop() {

  delay(4000);
  
  humidity = dht.readHumidity();
  temperature = dht.readTemperature();

  // Check if any reads failed and exit early (to try again).
  if (isnan(humidity) || isnan(temperature)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  float rzero = mq135_sensor.getRZero();
  float correctedRZero = mq135_sensor.getCorrectedRZero(temperature, humidity);
  float resistance = mq135_sensor.getResistance();
  float ppm = mq135_sensor.getPPM();
  float correctedPPM = mq135_sensor.getCorrectedPPM(temperature, humidity);

  Serial.print("MQ135 RZero: ");
  Serial.print(rzero);
  Serial.print("\t Corrected RZero: ");
  Serial.print(correctedRZero);
  Serial.print("\t Resistance: ");
  Serial.print(resistance);
  Serial.print("\t PPM: ");
  Serial.print(ppm);
  Serial.print("ppm");
  Serial.print("\t Corrected PPM: ");
  Serial.print(correctedPPM);
  Serial.println("ppm");
  Serial.println("Temperature and Humidity before the calculation");
  Serial.print(temperature);
  Serial.print("  ");
  Serial.println(humidity);

  Temperature = int (temperature*10);
  Humidity = int (humidity*10);
  Rzero = int (rzero*100);
  cRzero = int (correctedRZero*100);
  Resistance = int (resistance*100);
  Ppm = int(ppm*100);
  Cppm = int (correctedPPM*100);
  // prepare and schedule data for transmission 
    Data[0] = Temperature >> 8; 
    Data[1] = Temperature;
    Data[2] = Humidity >> 8;
    Data[3] = Humidity;
    Data[4] = Rzero >> 16;
    Data[5] = Rzero >> 8;
    Data[6] = Rzero;
    Data[7] = cRzero >> 16;
    Data[8] = cRzero >> 8;
    Data[9] = cRzero;
    Data[10] = Resistance >> 16;
    Data[11] = Resistance >> 8;
    Data[12] = Resistance;
    Data[13] = Ppm >> 16;
    Data[14] = Ppm >> 8; 
    Data[15] = Ppm;
    Data[16] = Cppm >> 16;
    Data[17] = Cppm >> 8; 
    Data[18] = Cppm;
    //Data[0] = 0xa; 
    //Data[1] = 0xb;
    //Data[2] = 0xc;
    //Data[3] = 0xd;
    Serial.println("Data of Temp and Hum after manipulation");
    Serial.println(Temperature);
    Serial.println(Humidity);
    //Serial.println(Data[2],DEC);
    //Serial.println(Data[3],DEC);
     
  sendMessage(Data);
}

void sendMessage(byte* outgoing) {
  LoRa.beginPacket();                   // start packet
  LoRa.write(destination);              // add destination address
  LoRa.write(localAddress);             // add sender address
  LoRa.write(msgCount);                 // add message ID
  //LoRa.write(outgoing.length());        // add payload length
  LoRa.write(payloadLenght);        // add payload length
  LoRa.write(outgoing, payloadLenght);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}

The article was first published in hackster, Jul 26, 2022

cr: https://www.hackster.io/cstram/iot-cloud-with-lora-integration-3f344d

author: cstram

License
All Rights
Reserved
licensBg
0