WiFi range tops out at ~100 m. LoRa reaches 2–15 km (line of sight) on a CR2032 battery — making it the go-to choice for agriculture sensors, asset tracking, smart meter reading, and any outdoor IoT deployment where WiFi or cellular is impractical.
// What you'll build: A LoRa sensor node (ESP32 + SX1278) that broadcasts soil moisture and temperature every 60 seconds, and a gateway node that receives the packets and prints them to serial — extendable to forward data via MQTT to any server.
// AD SLOT — IN-CONTENT RESPONSIVE
Hardware Required
| Part | Notes |
|---|---|
| 2× ESP32 DevKit | One transmitter, one gateway/receiver |
| 2× LoRa SX1278 (Ra-02) | 433 MHz for EU/Asia; use 915 MHz for US |
| 2× Wire antenna | ~17.3 cm for 433 MHz (quarter-wave) |
| Soil moisture sensor (optional) | Capacitive preferred over resistive |
Wiring — SX1278 to ESP32
| SX1278 Pin | ESP32 GPIO |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SCK | GPIO 18 |
| MISO | GPIO 19 |
| MOSI | GPIO 23 |
| NSS (CS) | GPIO 5 |
| RST | GPIO 14 |
| DIO0 | GPIO 2 |
Library Setup
Install the LoRa library by Sandeep Mistry from the Arduino Library Manager.
Transmitter Node Sketch
// lora_transmitter.ino#include <SPI.h>
#include <LoRa.h>
#define LORA_SS 5
#define LORA_RST 14
#define LORA_DIO0 2
#define SOIL_PIN 34 // analog soil moisture sensor
int packetCount = 0;
void setup() {
Serial.begin(115200);
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(433E6)) { // 433 MHz — change to 868E6 or 915E6 as needed
Serial.println("LoRa init failed!");
while (true);
}
LoRa.setSpreadingFactor(9); // SF9 — good balance of range vs speed
LoRa.setSignalBandwidth(125E3);
LoRa.setCodingRate4(5);
Serial.println("LoRa transmitter ready.");
}
void loop() {
int soil = map(analogRead(SOIL_PIN), 4095, 1500, 0, 100); // % moisture
float temp = (float)(analogRead(35)) * 0.1; // placeholder
String payload = String(packetCount) + "," +
String(soil) + "," +
String(temp, 1);
LoRa.beginPacket();
LoRa.print(payload);
LoRa.endPacket();
Serial.printf("Sent packet #%d: %s\n", packetCount, payload.c_str());
packetCount++;
delay(60000); // transmit every 60 seconds
}
Gateway / Receiver Sketch
// lora_gateway.ino#include <SPI.h>
#include <LoRa.h>
#include <WiFi.h>
#include <PubSubClient.h>
// Same pin definitions as transmitter
#define LORA_SS 5; #define LORA_RST 14; #define LORA_DIO0 2;
const char* SSID = "YOUR_SSID"; const char* PASSWORD = "YOUR_PASSWORD";
const char* MQTT_HOST = "192.168.1.10";
WiFiClient wifiClient; PubSubClient mqtt(wifiClient);
void setup() {
Serial.begin(115200);
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(500);
mqtt.setServer(MQTT_HOST, 1883);
LoRa.setPins(5, 14, 2);
LoRa.begin(433E6);
LoRa.setSpreadingFactor(9); // must match transmitter
Serial.println("Gateway listening...");
}
void loop() {
if (!mqtt.connected()) { mqtt.connect("lora-gateway"); }
mqtt.loop();
int packetSize = LoRa.parsePacket();
if (packetSize) {
String received = "";
while (LoRa.available()) received += (char)LoRa.read();
int rssi = LoRa.packetRssi();
Serial.printf("Received: %s RSSI: %d dBm\n", received.c_str(), rssi);
mqtt.publish("lora/sensor/data", received.c_str());
mqtt.publish("lora/sensor/rssi", String(rssi).c_str());
}
}
Spreading Factor Reference
| SF | Range | Airtime (10-byte payload) | Battery drain |
|---|---|---|---|
| SF7 | ~2 km | ~56 ms | Lowest |
| SF9 | ~5 km | ~370 ms | Medium |
| SF12 | ~15 km | ~2.5 s | Highest |
// Rule of thumb: Each SF step doubles the range but also doubles the airtime (and power use). Start with SF9 for outdoor field deployments — it is a safe balance. Use SF7 only when nodes are within the same building.