If you have ever tried to send sensor data from a field 2 km away using WiFi, you already know the pain. WiFi drops off after about 50 metres outdoors. Bluetooth is even shorter. For remote sensing across farms, campuses, or industrial sites, you need something fundamentally different.
That something is LoRa (Long Range), a radio modulation technique purpose-built for sending small packets over enormous distances using minimal power. In this tutorial, we will build a complete two-node IoT network using the Ra-01H LoRa module (based on the Semtech SX1276 chip) and the ESP32 microcontroller. By the end, you will have a working transmitter and receiver capable of communicating over 2-10 km in open terrain.
What is LoRa and Why Does It Matter?
LoRa stands for Long Range. It is a physical layer (PHY) modulation scheme developed by Semtech that uses Chirp Spread Spectrum (CSS) to achieve extraordinary range with very low power consumption. Unlike WiFi or Bluetooth, LoRa was never designed for streaming video or transferring files. It is built for sending tiny packets of sensor data reliably over kilometres.
Here is how the three technologies compare:
| Parameter | WiFi (2.4 GHz) | Bluetooth (BLE) | LoRa (868/915 MHz) |
|---|---|---|---|
| Range (outdoor) | 50-100 m | 10-30 m | 2-15 km |
| Data Rate | 54-600 Mbps | 1-2 Mbps | 0.3-50 kbps |
| Power Consumption | High (100-350 mA) | Low (10-15 mA) | Very Low (10-40 mA TX) |
| Frequency | 2.4 / 5 GHz | 2.4 GHz | Sub-GHz (433/868/915 MHz) |
| Best For | Internet, streaming | Wearables, short-range | Remote sensors, agriculture |
| Licence Required | No | No | No (ISM band) |
The key trade-off is simple: LoRa sacrifices data rate for range and power efficiency. You cannot stream a camera feed over LoRa, but you can send a temperature reading every 5 minutes from a sensor node running on a single 18650 battery for over a year.
Why Sub-GHz Frequencies Win for Range
LoRa operates in the sub-GHz ISM bands (433 MHz, 868 MHz, or 915 MHz depending on your region). Lower frequencies have better propagation characteristics than the 2.4 GHz used by WiFi and Bluetooth. They diffract around obstacles more effectively and penetrate vegetation and walls better. For India, the commonly used bands are 433 MHz and 868 MHz (which falls under the ISM allocation).
LoRa vs LoRaWAN: Know the Difference
This is a common source of confusion. They are not the same thing.
LoRa is the radio modulation layer. It is the raw physical ability to send and receive packets between two radios. Think of it as the "walkie-talkie" mode. Two devices with LoRa modules can communicate point-to-point with no infrastructure needed.
LoRaWAN is a full network protocol built on top of LoRa. It adds gateways, network servers, encryption, device management, and over-the-air activation. Think of it as the "cellular network" mode. You need gateways connected to the internet and a LoRaWAN network server (like The Things Network or ChirpStack).
When to Use Each
| Use Case | Choose |
|---|---|
| Two nodes talking to each other directly | LoRa (point-to-point) |
| Less than 10 sensor nodes in a known area | LoRa (star topology) |
| Dozens or hundreds of nodes across a city | LoRaWAN |
| Need cloud integration and OTA management | LoRaWAN |
| Quick prototype or proof of concept | LoRa (point-to-point) |
| Battery life is the absolute top priority | LoRaWAN (Class A) |
For this tutorial, we are using raw LoRa point-to-point communication. It is simpler to set up, requires no gateway infrastructure, and is perfect for most hobby and small-scale projects.
Hardware You Will Need
Here is the complete bill of materials for a two-node setup (one transmitter, one receiver):
| Component | Quantity | Purpose |
|---|---|---|
| Ra-01H LoRa Module (SX1276, 868 MHz) | 2 | Radio transceiver |
| ESP32 Dev Board (e.g., ESP32-WROOM-32) | 2 | Microcontroller |
| 868 MHz SMA Antenna | 2 | Required for transmission |
| Breadboard (830-point) | 2 | Prototyping |
| Jumper Wires (M-M) | ~20 | Connections |
| DHT22 or BME280 Sensor (optional) | 1 | Temperature/humidity data |
| USB Cable (Micro-USB or Type-C) | 2 | Power and programming |
Important: Never power on a LoRa module without an antenna connected. Transmitting without an antenna can damage the RF output stage of the SX1276 chip permanently.
About the Ra-01H Module
The Ra-01H from Ai-Thinker is based on the Semtech SX1276 transceiver IC. Key specifications:
- Frequency: 803-930 MHz (covers both 868 and 915 MHz bands)
- Modulation: LoRa (CSS) and FSK
- TX Power: Up to +20 dBm (100 mW)
- Sensitivity: Down to -148 dBm
- Interface: SPI
- Supply Voltage: 1.8-3.7V (3.3V typical)
- Package: 16-pin SMD with castellated pads (breadboard-friendly breakout boards available)
Wiring the Ra-01H to ESP32
The Ra-01H communicates over SPI (Serial Peripheral Interface). The ESP32 has a hardware SPI peripheral (VSPI) that we will use. Here are the connections:
| Ra-01H Pin | ESP32 GPIO | Function |
|---|---|---|
| VCC | 3.3V | Power supply (do NOT use 5V) |
| GND | GND | Ground |
| MOSI | GPIO 23 | SPI Master Out, Slave In |
| MISO | GPIO 19 | SPI Master In, Slave Out |
| SCK | GPIO 18 | SPI Clock |
| NSS (CS) | GPIO 5 | SPI Chip Select |
| RST | GPIO 14 | Module Reset |
| DIO0 | GPIO 2 | Interrupt (packet received/sent) |
Wiring notes:
- Power: The Ra-01H is a 3.3V device. The ESP32's 3.3V output can supply enough current. Never connect VCC to the 5V pin.
- SPI lines: MOSI, MISO, SCK, and NSS form the standard 4-wire SPI bus. The ESP32 VSPI defaults map perfectly to GPIOs 23, 19, 18, and 5.
- RST (Reset): Connected to GPIO 14. The library uses this to reset the module during initialization.
- DIO0 (Interrupt): Connected to GPIO 2. The SX1276 asserts this pin when a packet has been received or transmission is complete. This is critical for the
onReceivecallback to work. - Antenna: Connect an 868 MHz antenna to the module's U.FL or SMA connector. A simple quarter-wave wire antenna (about 8.2 cm for 868 MHz) works for bench testing, but a proper tuned antenna is essential for range.
Wire both ESP32 boards identically. The same hardware setup works for both transmitter and receiver, as the difference is purely in software.
Setting Up the Arduino IDE
Step 1: Install ESP32 Board Support
- Open Arduino IDE. Go to File > Preferences.
- In "Additional Board Manager URLs", add:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Go to Tools > Board > Board Manager, search "ESP32", and install esp32 by Espressif Systems.
- Select your board: Tools > Board > ESP32 Dev Module.
Step 2: Install the LoRa Library
- Go to Sketch > Include Library > Manage Libraries.
- Search for "LoRa" by Sandeep Mistry.
- Install it (version 0.8.0 or later).
This library provides a clean, high-level API for the SX1276/SX1277/SX1278/SX1279 family of chips.
Step 3: Configure the Library Pins
The library defaults to different pins than what we are using. We will explicitly set them in our code using LoRa.setPins(ss, reset, dio0).
Code: Transmitter Sketch
This sketch reads a simulated sensor value (or a real DHT22 if connected) and transmits it as a LoRa packet every 5 seconds.
#include <SPI.h>
#include <LoRa.h>
// Pin definitions for Ra-01H on ESP32
#define LORA_SS 5
#define LORA_RST 14
#define LORA_DIO0 2
// LoRa parameters
#define FREQUENCY 868E6 // 868 MHz
#define BANDWIDTH 125E3 // 125 kHz bandwidth
#define SPREAD_FACTOR 7 // SF7 (fastest, shortest range)
#define TX_POWER 17 // dBm (max 20)
int packetCounter = 0;
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("LoRa Transmitter Starting...");
// Configure LoRa pins
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
// Initialize LoRa
if (!LoRa.begin(FREQUENCY)) {
Serial.println("ERROR: LoRa init failed! Check wiring.");
while (1); // Halt
}
// Configure radio parameters
LoRa.setSignalBandwidth(BANDWIDTH);
LoRa.setSpreadingFactor(SPREAD_FACTOR);
LoRa.setTxPower(TX_POWER);
LoRa.enableCrc(); // Enable CRC for error detection
Serial.println("LoRa Transmitter Ready.");
Serial.print("Frequency: ");
Serial.print(FREQUENCY / 1E6);
Serial.println(" MHz");
}
void loop() {
// Simulate sensor data (replace with real sensor readings)
float temperature = 25.0 + random(-50, 50) / 10.0;
float humidity = 60.0 + random(-100, 100) / 10.0;
int soilMoisture = random(200, 800);
// Build packet string
String packet = "N01,"; // Node ID
packet += String(temperature, 1) + ",";
packet += String(humidity, 1) + ",";
packet += String(soilMoisture) + ",";
packet += String(packetCounter);
// Send packet
Serial.print("Sending packet #");
Serial.print(packetCounter);
Serial.print(": ");
Serial.println(packet);
LoRa.beginPacket();
LoRa.print(packet);
LoRa.endPacket();
packetCounter++;
delay(5000); // Send every 5 seconds
}
Packet format: N01,25.3,62.1,456,42 where the fields are node ID, temperature, humidity, soil moisture, and packet counter. Using CSV makes parsing on the receiver side straightforward.
Code: Receiver Sketch
This sketch listens for incoming LoRa packets, parses the sensor data, and prints it to the serial monitor along with the received signal strength (RSSI) and signal-to-noise ratio (SNR).
#include <SPI.h>
#include <LoRa.h>
// Pin definitions (same as transmitter)
#define LORA_SS 5
#define LORA_RST 14
#define LORA_DIO0 2
// LoRa parameters (MUST match transmitter)
#define FREQUENCY 868E6
#define BANDWIDTH 125E3
#define SPREAD_FACTOR 7
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("LoRa Receiver Starting...");
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(FREQUENCY)) {
Serial.println("ERROR: LoRa init failed! Check wiring.");
while (1);
}
LoRa.setSignalBandwidth(BANDWIDTH);
LoRa.setSpreadingFactor(SPREAD_FACTOR);
LoRa.enableCrc();
Serial.println("LoRa Receiver Ready. Waiting for packets...");
Serial.println("-------------------------------------------");
}
void loop() {
int packetSize = LoRa.parsePacket();
if (packetSize) {
// Read incoming packet
String incoming = "";
while (LoRa.available()) {
incoming += (char)LoRa.read();
}
// Get signal quality
int rssi = LoRa.packetRssi();
float snr = LoRa.packetSnr();
// Parse CSV data
int idx1 = incoming.indexOf(',');
int idx2 = incoming.indexOf(',', idx1 + 1);
int idx3 = incoming.indexOf(',', idx2 + 1);
int idx4 = incoming.indexOf(',', idx3 + 1);
String nodeId = incoming.substring(0, idx1);
String temp = incoming.substring(idx1 + 1, idx2);
String hum = incoming.substring(idx2 + 1, idx3);
String soil = incoming.substring(idx3 + 1, idx4);
String pktNum = incoming.substring(idx4 + 1);
// Display parsed data
Serial.println("=== Packet Received ===");
Serial.print(" Node: "); Serial.println(nodeId);
Serial.print(" Temperature: "); Serial.print(temp); Serial.println(" C");
Serial.print(" Humidity: "); Serial.print(hum); Serial.println(" %");
Serial.print(" Soil Moisture:"); Serial.println(soil);
Serial.print(" Packet #: "); Serial.println(pktNum);
Serial.print(" RSSI: "); Serial.print(rssi); Serial.println(" dBm");
Serial.print(" SNR: "); Serial.print(snr); Serial.println(" dB");
Serial.println("-----------------------");
}
}
Understanding RSSI and SNR:
- RSSI (Received Signal Strength Indicator): Measured in dBm. Typical values range from -30 dBm (very strong, nearby) to -120 dBm (very weak, maximum range). Anything above -100 dBm is generally reliable.
- SNR (Signal-to-Noise Ratio): Measured in dB. Positive values mean the signal is above the noise floor. LoRa can demodulate signals with SNR as low as -20 dB at higher spreading factors, which is remarkable.
Range Testing Tips
Getting maximum range out of your LoRa link requires attention to a few key factors.
1. Antenna Placement
This is the single most important factor. Elevate your antennas as high as possible. A LoRa module on the ground floor might reach 500 m. The same module on a rooftop can reach 5-10 km. Even raising the antenna by 2 metres on a PVC pipe makes a dramatic difference.
2. Line of Sight
LoRa works best with clear line of sight between transmitter and receiver. Buildings, dense trees, and hills attenuate the signal significantly. In urban Bengaluru, expect 1-3 km. In open farmland in rural Karnataka, 5-15 km is achievable.
3. Spreading Factor Tuning
The Spreading Factor (SF) is your primary range vs speed knob:
| Spreading Factor | Data Rate | Sensitivity | Range (approx) | Air Time (10 bytes) |
|---|---|---|---|---|
| SF7 | 5.47 kbps | -123 dBm | 2-3 km | 36 ms |
| SF8 | 3.13 kbps | -126 dBm | 3-5 km | 72 ms |
| SF9 | 1.76 kbps | -129 dBm | 4-7 km | 144 ms |
| SF10 | 0.98 kbps | -132 dBm | 6-10 km | 288 ms |
| SF11 | 0.54 kbps | -134.5 dBm | 8-13 km | 577 ms |
| SF12 | 0.29 kbps | -137 dBm | 10-15 km | 1155 ms |
Start with SF7 for development (fastest). Increase to SF9 or SF10 for production deployments where you need more range. Each step up roughly doubles the air time but gains about 2.5 dB of sensitivity.
To change the spreading factor, simply modify the SPREAD_FACTOR define in both sketches:
#define SPREAD_FACTOR 10 // Increased for longer range
4. Practical Range Test Procedure
- Set up the receiver at a fixed location with the serial monitor open.
- Walk or drive away with the transmitter, stopping every 200-500 m.
- At each stop, note the RSSI and SNR values displayed on the receiver.
- Record the GPS coordinates (use your phone) and signal quality.
- The link will become unreliable when SNR approaches -10 dB or RSSI drops below -115 dBm.
Real-World Applications in India
LoRa is especially compelling for Indian conditions where cellular coverage is spotty, WiFi infrastructure is nonexistent, and power availability is unreliable.
Farm Monitoring
India's agricultural sector is massive, and farms are often spread across large areas without internet. A LoRa network can monitor:
- Soil moisture at multiple points across a field
- Temperature and humidity for crop health
- Water pump status (on/off detection via current sensor)
- Livestock tracking (GPS + LoRa collar)
A single receiver node at the farmhouse with an ESP32 connected to WiFi can relay all data to a mobile app.
Water Tank Level Monitoring
In many Indian housing societies and villages, overhead water tanks are on rooftops or elevated structures. An ultrasonic sensor + LoRa transmitter at the tank can send level readings to a receiver in the pump house, enabling automatic pump control and preventing overflow.
Weather Stations
Deploy weather sensor nodes across a campus, village, or taluk. Each node measures temperature, humidity, pressure, rainfall, and wind speed. All data flows to a central receiver over LoRa, then to a dashboard via WiFi. Useful for microclimate monitoring in coffee plantations in Coorg or grape vineyards in Nashik.
Industrial Monitoring
Factories and warehouses can use LoRa to monitor cold storage temperatures, machine vibration levels, or power consumption across a large industrial estate where running Ethernet or WiFi is impractical.
Power Optimization for Battery-Operated Nodes
For field-deployed sensor nodes running on batteries, power efficiency is everything. Here are key strategies:
1. Use Deep Sleep
The ESP32 consumes about 150-240 mA when active but only 10 uA in deep sleep. For a sensor node that transmits every 5 minutes, the ESP32 should spend 99.9% of its time asleep.
#define SLEEP_MINUTES 5
#define uS_TO_S_FACTOR 1000000ULL
void setup() {
// Initialize, read sensor, transmit...
sendLoRaPacket();
// Enter deep sleep
esp_sleep_enable_timer_wakeup(SLEEP_MINUTES * 60 * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {
// Never reached
}
2. Power Down the LoRa Module
After transmitting, put the SX1276 into sleep mode:
LoRa.sleep(); // Module draws ~0.2 uA in sleep
3. Battery Life Estimation
With a 3000 mAh 18650 battery, deep sleep mode, and one transmission every 5 minutes:
- Active time per cycle: ~2 seconds at ~120 mA average = 0.067 mAh
- Sleep time per cycle: ~298 seconds at ~0.01 mA = 0.0008 mAh
- Energy per cycle: ~0.068 mAh
- Cycles per day: 288
- Daily consumption: ~19.6 mAh
- Estimated battery life: ~150 days (5 months)
Add a small solar panel (5V/1W) and a TP4056 charge module, and the node runs indefinitely.
Scaling Up: Multiple Nodes and Addressing
When you have multiple sensor nodes, you need a way to identify which node sent each packet. Our CSV protocol already includes a node ID field (N01, N02, etc.), but here is a more robust approach:
Software Addressing
Define unique node IDs and optionally filter on the receiver:
// On each transmitter, set a unique ID
#define NODE_ID "FARM_SOIL_03"
// On the receiver, optionally filter
void loop() {
int packetSize = LoRa.parsePacket();
if (packetSize) {
String incoming = "";
while (LoRa.available()) {
incoming += (char)LoRa.read();
}
// Process all nodes or filter specific ones
if (incoming.startsWith("FARM_")) {
processPacket(incoming);
}
}
}
Star Topology
For most small-to-medium deployments (up to 20-30 nodes), a star topology works well:
- One central receiver (gateway) placed at an elevated position
- Multiple transmitter nodes in the field
- Each node transmits on a time-division schedule to avoid collisions
To avoid collisions, stagger transmission times. A simple approach is to add a random delay:
// Add random jitter to avoid packet collisions
int jitter = random(0, 2000); // 0-2 second random delay
delay(jitter);
LoRa.beginPacket();
LoRa.print(packet);
LoRa.endPacket();
Sync Word for Network Isolation
If multiple LoRa networks operate in the same area, use sync words to isolate them:
LoRa.setSyncWord(0x34); // Only radios with matching sync word will receive
Both transmitter and receiver must use the same sync word. The default is 0x34. Change it to any byte value (avoid 0x34 which is the LoRaWAN public network sync word if you want to avoid interference).
Common Pitfalls and Troubleshooting
| Problem | Likely Cause | Fix |
|---|---|---|
LoRa init failed |
Wiring error or no antenna | Double-check SPI connections, especially NSS and RST |
| Packets sent but not received | Mismatched parameters | Ensure frequency, bandwidth, SF, and sync word match exactly |
| Very short range (<100 m) | No antenna or wrong antenna | Attach a proper antenna matched to your frequency |
| Intermittent reception | Loose breadboard connections | Use solid-core jumpers, check for cold joints |
| ESP32 crashes on transmit | Power brownout | Add a 100 uF capacitor across the Ra-01H VCC and GND |
Where to Get Verified Components
When building LoRa projects, module quality matters. Counterfeit SX1276 chips exist in the market and will give you poor range and unreliable communication. Wavtron stocks genuine Ra-01H modules with datasheets and pinout diagrams included, along with compatible ESP32 dev boards, antennas, and sensors. All components are tested before shipping, so you spend time building your project instead of debugging bad hardware.
Browse the full LoRa and ESP32 catalogue at wavtron.in.
Next Steps
Once you have the basic two-node link working, here are directions to explore:
- Add a display: Connect an SSD1306 OLED to the receiver to show live sensor data without a computer.
- Web dashboard: Use the receiver ESP32's WiFi to host a simple web server or push data to ThingSpeak/Blynk.
- Two-way communication: LoRa is half-duplex. Implement a request-response protocol where the gateway polls each node.
- Encryption: Add AES-128 encryption to your packets using the ESP32's hardware AES accelerator.
- Move to LoRaWAN: When you outgrow point-to-point, set up a RAK or Dragino gateway and join The Things Network.
LoRa opens up an entirely different class of IoT projects, the kind where WiFi and Bluetooth simply cannot reach. With an ESP32 and a pair of Ra-01H modules, you have everything needed to build reliable, long-range sensor networks for a fraction of the cost of cellular IoT solutions. Start with the sketches above, test your range, and then adapt the system to your specific use case.



