Hey makers! Electro here. My WiFi router covers maybe 40 meters before the signal turns to soup. My garden shed, 80 meters away with a brick wall and half a garden in between, might as well be on the moon as far as my home network is concerned. That's the exact problem LoRa was built to solve — and it's a big part of why LoRa-based projects (Meshtastic being the poster child) have exploded in the maker community. No WiFi, no cellular plan, no monthly fee, and a real-world range measured in kilometers instead of meters.
This guide walks through building a simple two-node LoRa link with ESP32 boards — one sender, one receiver — the foundation every LoRa sensor network is built on. Once you have this working, scaling to a full mesh of soil sensors, mailbox alerts, or off-grid messaging nodes is mostly a matter of repeating the pattern.
🔄 Updated for 2026: complete sender/receiver code, real-world range testing method, calibration tips, and a new FAQ covering LoRa regulations and Home Assistant integration.
📑 Table of Contents
- What LoRa Actually Is (And Why It's Everywhere Right Now)
- Components & Cost Breakdown
- Circuit Wiring Diagram
- Understanding LoRa Parameters
- Complete Code: Sender Node
- Complete Code: Receiver Node
- Testing Real-World Range
- Calibration Tips
- Troubleshooting
- FAQ
- Taking It Further
1. What LoRa Actually Is (And Why It's Everywhere Right Now)
LoRa (short for "Long Range") is a radio modulation technique, not a full networking protocol like WiFi — it trades bandwidth for range and power efficiency. Where WiFi might move megabits per second across a room, LoRa moves a few hundred bytes per second across kilometers. That sounds like a downgrade until you realize how little data most sensor projects actually need: a temperature reading, a "the mailbox is open" alert, or a GPS coordinate doesn't need megabit speeds — it needs to arrive reliably from far away on battery power.
⚡ Why the sudden popularity: The open-source Meshtastic firmware turned cheap LoRa boards into off-grid mesh messaging devices that hikers, preppers, and event organizers use when there's no cell signal at all. That project's popularity pulled a huge wave of new makers into LoRa, and dropping module prices ($5-8 for a basic transceiver) made "build your own long-range sensor" a genuinely accessible weekend project.
2. Components & Cost Breakdown
(You'll need two of the ESP32 + LoRa combos below — one for each end of the link.)
| Component | Model | Purpose | Price (x2) |
|---|---|---|---|
| Microcontroller | ESP32-WROOM-32 DevKit | Processing + power | $12 |
| LoRa transceiver | SX1278 / RA-02 (433MHz) or SX1276 (915MHz) | Long-range radio | $16 |
| Antenna | 433MHz or 915MHz spring/whip antenna | Signal range | $4 |
| Power (per node) | 18650 battery + holder | Portable operation | $10 |
| Misc | Jumper wires, breadboard | Assembly | $4 |
💰 Total Cost: ~$46 for a complete two-node link (vs. commercial LoRa gateway kits starting around $80-150 for similar range).
⚠️ Frequency matters: LoRa modules are region-specific — 433MHz is common in Europe/Asia, 915MHz in North America, 868MHz in parts of Europe. Check your local ISM band regulations before buying; using the wrong frequency module won't just perform poorly, it may be illegal to transmit on in your region.
3. Circuit Wiring Diagram
The SX1278/SX1276 modules communicate over SPI — same bus you'd use for an e-ink panel or SD card, but with fewer pins.
LoRa Module to ESP32 (build this wiring on both nodes):
- NSS (CS) → GPIO5
- RESET → GPIO14
- DIO0 → GPIO2
- MOSI → GPIO23
- MISO → GPIO19
- SCK → GPIO18
- GND → GND
- VCC → 3.3V (not 5V — most LoRa modules are 3.3V-only and will be damaged by 5V)
⚠️ Important: Solder the antenna before powering on the module. Running a LoRa transceiver at transmit power without an antenna can damage the radio's output stage — a genuinely common way to kill a $6 module on day one.
4. Understanding LoRa Parameters
Three settings control the fundamental trade-off between range, speed, and battery life:
- Spreading Factor (SF7-SF12): Higher SF = longer range and better penetration, but slower data rate and more airtime (battery + duty-cycle regulations). SF7 is fast and short-range; SF12 is slow but punches through serious obstacles.
- Bandwidth (125kHz default): Narrower bandwidth extends range at the cost of data rate. Most projects can leave this at 125kHz and adjust SF instead.
- Transmit power: Higher power extends range but drains battery and is legally capped in many regions. Start conservative.
The practical starting point: SF9, 125kHz bandwidth, 17dBm — a reasonable default for a sensor node covering a few hundred meters to a couple of kilometers in open terrain with useful battery life.
5. Complete Code: Sender Node
#include <SPI.h>
#include <LoRa.h>
#define SS_PIN 5
#define RST_PIN 14
#define DIO0_PIN 2
int counter = 0;
void setup() {
Serial.begin(115200);
LoRa.setPins(SS_PIN, RST_PIN, DIO0_PIN);
if (!LoRa.begin(433E6)) { // use 915E6 for North America
Serial.println("LoRa init failed. Check wiring.");
while (1);
}
LoRa.setSpreadingFactor(9);
LoRa.setSignalBandwidth(125E3);
LoRa.setTxPower(17);
Serial.println("LoRa sender ready");
}
void loop() {
Serial.print("Sending packet: ");
Serial.println(counter);
LoRa.beginPacket();
LoRa.print("Sensor reading #");
LoRa.print(counter);
LoRa.endPacket();
counter++;
delay(5000); // send every 5 seconds
}
6. Complete Code: Receiver Node
#include <SPI.h>
#include <LoRa.h>
#define SS_PIN 5
#define RST_PIN 14
#define DIO0_PIN 2
void setup() {
Serial.begin(115200);
LoRa.setPins(SS_PIN, RST_PIN, DIO0_PIN);
if (!LoRa.begin(433E6)) { // must match the sender's frequency exactly
Serial.println("LoRa init failed. Check wiring.");
while (1);
}
LoRa.setSpreadingFactor(9);
LoRa.setSignalBandwidth(125E3);
Serial.println("LoRa receiver ready");
}
void loop() {
int packetSize = LoRa.parsePacket();
if (packetSize) {
String received = "";
while (LoRa.available()) {
received += (char)LoRa.read();
}
Serial.print("Received: ");
Serial.print(received);
Serial.print(" | RSSI: ");
Serial.print(LoRa.packetRssi());
Serial.print(" dBm | SNR: ");
Serial.println(LoRa.packetSnr());
}
}
Note on the library: this uses Sandeep Mistry's LoRa library (install via Arduino Library Manager, search "LoRa"). Both nodes must use identical frequency, spreading factor, and bandwidth — a mismatch on any of these three silently prevents reception, with no error message telling you why.
7. Testing Real-World Range
- Keep the receiver stationary at home, connected to a laptop for the serial monitor.
- Walk the sender outward in a straight line, checking every 50-100 m (watch RSSI/SNR — RSSI around -120dBm is close to the practical noise floor).
- Note where packets start dropping; repeat in another direction — range is rarely symmetric once buildings or trees enter the picture.
- If range disappoints, the highest-impact change is usually antenna quality and height — before touching SF or power.
8. Calibration Tips (Pro Tips by Electro)
- Higher SF isn't always better. SF12 has the longest theoretical range, but each packet takes far longer to transmit — for a sensor sending every few seconds, SF12 can cause more missed packets (airtime collisions) than a well-chosen SF9.
- Antenna height beats everything. A vertical antenna one meter off the ground outperforms the same antenna lying on a table by a wide margin.
- Respect duty cycle limits. Many regions cap airtime per hour on ISM bands — don't design a sensor that transmits continuously without checking local rules first.
9. Troubleshooting
"LoRa init failed" on startup
- Double-check VCC is on 3.3V, not 5V — the single most common cause of a dead module.
- Reseat all SPI connections; breadboard jumpers are a frequent source of intermittent contact.
Receiver never gets packets
- Confirm both nodes use the exact same frequency (433E6 vs 915E6 is an easy typo).
- Verify SF and bandwidth match exactly — one mismatched parameter silently breaks reception.
- Check the antenna is properly connected at both ends.
Range much shorter than expected
- Test open line-of-sight first to establish a baseline.
- Raise SF by one or two steps (SF9 → SF10).
- Move the antenna higher — a meter or two often has an outsized effect.
Packets arrive corrupted
- You're likely near the edge of reliable range — check RSSI/SNR; SNR near or below 0 means errors are expected.
❓ FAQ
Is LoRa legal without a license?
Yes — as long as you stay within your region's ISM band (433/868 MHz in Europe, 915 MHz in North America) and respect power and duty-cycle limits. No license is required for compliant low-power use.
What real-world range can I expect?
Urban: 1-3 km. Suburban: 2-5 km. Open line-of-sight with raised antennas: 5-15+ km. Antenna height matters more than transmit power.
Can I feed the receiver into Home Assistant?
Absolutely — forward received packets from the ESP32 via MQTT (or serial to a Raspberry Pi) and create sensors in Home Assistant. It's the natural next step after this build.
433 vs 868 vs 915 MHz — which do I buy?
Match your region's ISM band. Lower frequencies (433 MHz) penetrate obstacles slightly better; 868/915 MHz allow higher duty cycles in their regions.
10. Taking It Further
Once the basic link is solid, scale it into a proper sensor network: multiple senders (soil moisture, door sensor, mailbox switch) reporting to one central receiver connected to a Raspberry Pi or your home network, logging to a dashboard. For serious off-grid networking, look into Meshtastic — it adds multi-hop mesh routing, encryption, and a phone app on top of this exact same hardware.
🎯 Final Thoughts
LoRa is one of those technologies that feels almost like cheating once you've used it — a reliable signal across distances that would need repeaters or mesh WiFi, using less power than a phone charger, on hardware that costs less than a lunch out. If your projects have ever been limited by "but it needs to reach the garden/garage/back field," this is very likely the missing piece.
If you build this, I'd love to hear your real-world range in the comments below — real numbers from real terrain beat datasheet claims every time!