How to use LoRa to send and receive sensor readings

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.

How to use LoRa to send sensor reading. TTGO LoRa32 sender and receiver.

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

DHT11TTGO LoRa32
VccVcc (3.3V)
GNDGND
SignalGPIO23

TEMT6000

TEMT6000TTGO LoRa32
VccVcc (3.3V)
GNDGND
Analogic signalGPIO13

Moisture sensor

Moisture sensor (YL-38 + YL-69)TTGO LoRa32
VccVcc (3.3V)
GNDGND
Analog signalGPIO34

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); }

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();

To use the DHT11 sensor it is necessary to include the library:

// DHT11 #include <Adafruit_Sensor.h> #include <DHT.h>

and then:

// Moisture sensor #define MOISTURE_SENSOR_PIN 34 #define MOISTURE_THRESHOLD 700 DHT dht(DHT_PIN, DHTTYPE);

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();

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 send sensor data using LoRa

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(); } }

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('|'); } .. }

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"

and then:

// Web server running on port 80 AsyncWebServer server(80);

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(); }

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.