In this tutorial, we will cover how to send sensor readings using LoRa LPWAN. In the last tutorial, we have covered how to get started with LoRa with TTGO LoRa32. Now, we want to expand the previous tutorial and we will describe how to acquire data using sensors and send it through LoRa. As described previously, we have two different TTGO LoRa32:
- a sender device that manages sensors
- a receiver
LoRa project overview
The basic idea that stands behind this project is using sensors to monitor a plant and send these parameters through LoRa LPWAN. We can suppose that the LoRa sender is placed near the plant we want to monitor and the receiver is far away from the sender in an area where we have a WiFi connection. The LoRa receiver behaves as a LoRa gateway receiving data using LoRa LWPAN and, at the same time, it exposes a set of API that can be used to read the sensor reading sent. These API can be accessed through the local WiFi and can be used to build a dashboard.

LoRa sensor
We will use:
- DHT11 to measure the temperature and humidity
- Moisture sensor (YL-38 + YL-69) to measure how wet is the plant soil
- TEMT6000 sensor to measure the light intensity
We will send all the sensor readings to a LoRa receiver using LoRa network.
How to send sensor readings using LoRa
This LoRa project has two different components the receiver and the sender. To develop this project we will use PlatformIO. In this step, we will analyze how to send data using the LoRa network.
How to connect sensors to TTGO LoRa32
Before showing the code to use, it is important to know how to connect these sensors to the TTGO LoRa32:
DHT11
DHT11 | TTGO LoRa32 |
Vcc | Vcc (3.3V) |
GND | GND |
Signal | GPIO23 |
TEMT6000
TEMT6000 | TTGO LoRa32 |
Vcc | Vcc (3.3V) |
GND | GND |
Analogic signal | GPIO13 |
Moisture sensor
Moisture sensor (YL-38 + YL-69) | TTGO LoRa32 |
Vcc | Vcc (3.3V) |
GND | GND |
Analog signal | GPIO34 |
LoRa sender sketch
The LoRa sender code is shown below:
#include <Arduino.h>
// LoRa include
#include <SPI.h>
#include <LoRa.h>
// display include
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// DHT11
#include <Adafruit_Sensor.h>
#include <DHT.h>
// Define OLED PIN
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// LoRa pins
#define LORA_MISO 19
#define LORA_CS 18
#define LORA_MOSI 27
#define LORA_SCK 5
#define LORA_RST 14
#define LORA_IRQ 26
// LoRa Band (change it if you are outside Europe according to your country)
#define LORA_BAND 866E6
// DHT11
#define DHTTYPE DHT11
#define DHT_PIN 23
// TEMT6000 Light sensor
#define LIGHT_PIN 13
// Moisture sensor
#define MOISTURE_SENSOR_PIN 34
#define MOISTURE_THRESHOLD 700
DHT dht(DHT_PIN, DHTTYPE);
Adafruit_SSD1306 display(128, 32, &Wire, OLED_RST);
void resetDisplay() {
digitalWrite(OLED_RST, LOW);
delay(25);
digitalWrite(OLED_RST, HIGH);
}
void initializeDisplay() {
Serial.println("Initializing display...");
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
Serial.println("Failed to initialize the dispaly");
for (;;);
}
Serial.println("Display initialized");
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("Welcome to LORA");
display.setTextSize(1);
display.println("Lora sender");
display.display();
}
void initLoRa() {
Serial.println("Initializing LoRa....");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
// Start LoRa using the frequency
int result = LoRa.begin(LORA_BAND);
if (result != 1) {
display.setCursor(0,10);
display.println("Failed to start LoRa network!");
for (;;);
}
Serial.println("LoRa initialized");
display.setCursor(0,15);
display.println("LoRa network OK!");
display.display();
delay(2000);
}
void setup() {
Serial.begin(9600);
Serial.println("Setup LoRa Sender....");
resetDisplay();
initializeDisplay();
initLoRa();
dht.begin();
pinMode(LIGHT_PIN, INPUT);
}
void loop() {
// Read temperature
float t = dht.readTemperature();
float h = dht.readHumidity();
Serial.print("Temp:");
Serial.println(t);
Serial.print("Humidity:");
Serial.println(h);
// Read the Light intensity
float intensity = analogRead(LIGHT_PIN);
Serial.print("Intensity:");
Serial.println(intensity);
float moisture_level = analogRead(MOISTURE_SENSOR_PIN);
LoRa.beginPacket();
LoRa.print(t);
LoRa.print("|");
LoRa.print(h);
LoRa.print("|");
LoRa.print(intensity);
LoRa.print("|");
LoRa.print(moisture_level);
LoRa.print("|");
LoRa.endPacket();
display.clearDisplay();
display.setCursor(0,1);
display.print("Temp: ");
display.println(t);
display.print("Hum: ");
display.println(h);
display.print("Light: ");
display.println(intensity);
display.print("Moisture: ");
display.println(moisture_level);
display.display();
delay(2000);
}
Code language: C++ (cpp)
If you are new to LoRa technology and want to know step by step how to use it, please read how to get started with LoRa LPWAN. We focus our attention on the loop()
method. Firstly, the code reads the temperature and the humidity:
// Read temperature
float t = dht.readTemperature();
float h = dht.readHumidity();
Code language: C++ (cpp)
To use the DHT11 sensor it is necessary to include the library:
// DHT11
#include <Adafruit_Sensor.h>
#include <DHT.h>
Code language: C++ (cpp)
and then:
// Moisture sensor
#define MOISTURE_SENSOR_PIN 34
#define MOISTURE_THRESHOLD 700
DHT dht(DHT_PIN, DHTTYPE);
Code language: C++ (cpp)
Finally, the code reads two analog signals:
float intensity = analogRead(LIGHT_PIN);
float moisture_level = analogRead(MOISTURE_SENSOR_PIN);
The last step is sending the sensor readings using LoRa:
LoRa.beginPacket();
LoRa.print(t);
LoRa.print("|");
LoRa.print(h);
LoRa.print("|");
LoRa.print(intensity);
LoRa.print("|");
LoRa.print(moisture_level);
LoRa.print("|");
LoRa.endPacket();
Code language: C++ (cpp)
Notice that we are using a single packet to send all the data using the | as a separator.
This is the LoRa sender at work:

How to receive sensor readings using LoRa
In this second step, we will develop the LoRa receiver sketch. The receiver has two main tasks:
- receive the sensor reading throguth LoRa network
- exposes this information using API
Therefore, the receiver in connected to LoRa network while it is connected to the WiFi so that a local client can invoke the API.
LoRa receiver sketch
This the sketch developed using PlatformIO. Anyway, you can use another IDE:
#include <Arduino.h>
// LoRa include
#include <SPI.h>
#include <LoRa.h>
// display include
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Web server and WiFi
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
// Define OLED PIN
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// LoRa pins
#define LORA_MISO 19
#define LORA_CS 18
#define LORA_MOSI 27
#define LORA_SCK 5
#define LORA_RST 14
#define LORA_IRQ 26
// LoRa Band (change it if you are outside Europe according to your country)
#define LORA_BAND 866E6
// WiFi config
const char *SSID = "your_ssid";
const char *PWD = "your_password";
String currentTemperature;
String currentHumidity;
String currentLightIntensity;
String currentMoistureLevel;
Adafruit_SSD1306 display(128, 32, &Wire, OLED_RST);
// Web server running on port 80
AsyncWebServer server(80);
void connectToWiFi() {
Serial.print("Connecting to ");
Serial.println(SSID);
WiFi.begin(SSID, PWD);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
// we can even make the ESP32 to sleep
}
Serial.print("Connected. IP: ");
Serial.println(WiFi.localIP());
}
void resetDisplay() {
digitalWrite(OLED_RST, LOW);
delay(25);
digitalWrite(OLED_RST, HIGH);
}
void initializeDisplay() {
Serial.println("Initializing display...");
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
Serial.println("Failed to initialize the dispaly");
for (;;);
}
Serial.println("Display initialized");
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("Welcome to LORA");
display.setTextSize(1);
display.println("Lora receiver");
display.display();
}
void initLoRa() {
Serial.println("Initializing LoRa....");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
// Start LoRa using the frequency
int result = LoRa.begin(LORA_BAND);
if (result != 1) {
display.setCursor(0,10);
display.println("Failed to start LoRa network!");
for (;;);
}
// LoRa.onReceive(onReceive);
// LoRa.receive();
Serial.println("LoRa initialized");
display.setCursor(0,15);
display.println("LoRa network OK!");
display.display();
delay(2000);
}
// setup API resources
void setup_routing() {
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentTemperature.c_str());
});
server.on("/humidity", HTTP_GET, [] (AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentHumidity.c_str());
});
server.on("/light", HTTP_GET, [] (AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentLightIntensity.c_str());
});
server.on("/moisture", HTTP_GET, [] (AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentMoistureLevel.c_str());
});
// start server
server.begin();
}
void setup() {
Serial.begin(9600);
Serial.println("Setup LoRa Sender....");
connectToWiFi();
setup_routing();
resetDisplay();
initializeDisplay();
initLoRa();
}
void loop() {
//try to parse packet
int packetSize = LoRa.parsePacket();
if (packetSize) {
//received a packet
//Serial.print("Received packet ");
//read packet
while (LoRa.available()) {
currentTemperature = LoRa.readStringUntil('|');
//Serial.print(currentTemperature);
currentHumidity = LoRa.readStringUntil('|');
//Serial.print(currentHumidity);
currentLightIntensity = LoRa.readStringUntil('|');
//Serial.print(currentLightIntensity);
currentMoistureLevel = LoRa.readStringUntil('|');
}
display.clearDisplay();
display.setCursor(0,1);
display.print("Temp: ");
display.println(currentTemperature);
display.print("Hum: ");
display.println(currentHumidity);
display.print("Light: ");
display.println(currentLightIntensity);
display.print("Moisture: ");
display.println(currentMoistureLevel);
display.display();
}
}
Code language: C++ (cpp)
As we did before, we focus our attention on the loop()
method where interesting things happen.
In this method, the code checks if a new LoRa packet is incoming and extract the data from it:
int packetSize = LoRa.parsePacket();
if (packetSize) {
//received a packet
//read packet
while (LoRa.available()) {
currentTemperature = LoRa.readStringUntil('|');
//Serial.print(currentTemperature);
currentHumidity = LoRa.readStringUntil('|');
//Serial.print(currentHumidity);
currentLightIntensity = LoRa.readStringUntil('|');
//Serial.print(currentLightIntensity);
currentMoistureLevel = LoRa.readStringUntil('|');
}
..
}
Code language: JavaScript (javascript)
Notice that the code uses the LoRa.readStringUntil() that behaves like a string tokenizer. In the sender, we have used the | to separete the sensor readings. In the rest part of the code, these sensor readings are visualized in the TTGO LoRa32 display.
Exposing API from LoRa LPWAN
The last step is exposing a set of API to access to the information acquired using LoRa. To do it, we use a async web server on port 80:
// Web server and WiFi
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
Code language: C++ (cpp)
and then:
// Web server running on port 80
AsyncWebServer server(80);
Code language: C++ (cpp)
where we define the port used by the web server. Finally, it is necessary to map the API:
// setup API resources
void setup_routing() {
server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentTemperature.c_str());
});
server.on("/humidity", HTTP_GET, [] (AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentHumidity.c_str());
});
server.on("/light", HTTP_GET, [] (AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentLightIntensity.c_str());
});
server.on("/moisture", HTTP_GET, [] (AsyncWebServerRequest *req) {
req->send_P(200, "text/plain", currentMoistureLevel.c_str());
});
// start server
server.begin();
}
Code language: C++ (cpp)
In this way, the API defined are:
- /temperature to read the current temperature
- /humidity to read the current humidity
- /light to read the light intensity
- /moisture to read the soil moisture level
Using this API we can access to sensor readings acquired by the TTGO LoRa32 placed near the plant. Through the LoRa LPWAN, the sensor readings are sent to the receiver that in turn exposes them through API. You can read how to create API with ESP32 to know more about API in IoT.
Wrapping up
In the end of this tutoria, we have learned how to use LoRa LPWAN to send sensor readings. We have developed a TTGO LoRa32 sender that acquires data using several sensors (DHT11, TEMT6000, moisture sensor) and send it using LoRa network. Morever we have developed a LoRa receiver that receives data and exposes a set of API to access it.
Hello,
how far do you manage to send