How can you make LoRa Nodes communicate with Arduino IOT Cloud? Check out my project :-)
Things used in this project
Hardware components
Software apps and online services
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
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
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
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
The schematics is the following:
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
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
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
/*
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
#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
/*
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 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
/*
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
}