LoRaWAN & LoRa — Long-Range IoT Communication Guide
In this tutorial, you'll learn about LoRaWAN & LoRa. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
LoRaWAN is a media access control (MAC) protocol built on LoRa (Long Range) radio modulation, designed for battery-powered IoT devices that need to communicate over distances of 1-15 kilometers while consuming minimal power.
Why LoRaWAN Matters
WiFi and Bluetooth cover tens of meters. Cellular covers kilometers but consumes watts of power. LoRaWAN fills the gap: kilometers of range at milliwatts of power consumption. A LoRa sensor on a single AA battery can transmit for years. Smart agriculture uses LoRaWAN soil moisture sensors across hundreds of hectares. Smart city parking sensors report availability via LoRaWAN through concrete and underground. Supply chain trackers follow shipping containers across continents using LoRaWAN roaming. DodaZIP's logistics tracking modules use LoRaWAN for real-time package location without draining battery.
Plain-Language Explanation
Imagine shouting across a football field. You can be heard, but it takes a lot of energy and everyone on the field hears you. Now imagine whispering a secret code that only one person can decode, even from far away. The whisper uses less energy, and the code keeps it private.
LoRa uses spread-spectrum modulation — it spreads the signal across a wide frequency band. This makes it resistant to interference and allows the receiver to reconstruct the signal even when it's below the noise floor. The tradeoff: data rate is very low (0.3-50 kbps). LoRaWAN devices can send at most a few hundred bytes per transmission. This is ideal for sensor readings, not video streaming.
graph TD
subgraph "LoRaWAN Network Architecture"
Sensor1[Soil Moisture
Sensor] -->|LoRa Radio| GW1[Gateway]
Sensor2[Parking
Sensor] -->|LoRa Radio| GW1
Sensor3[Air Quality
Monitor] -->|LoRa Radio| GW2[Gateway]
GW1 -->|TCP/IP| NS[Network Server
TTN / ChirpStack]
GW2 -->|TCP/IP| NS
NS -->|MQTT/HTTP| APP[Application Server]
APP --> Dashboard[Dashboard]
APP --> Alert[Alert System]
end
style Sensor1 fill:#27ae60,color:#fff
style Sensor2 fill:#3498db,color:#fff
style Sensor3 fill:#e67e22,color:#fff
style NS fill:#9b59b6,color:#fff
Device Classes
Class A (Battery-Powered): Default mode. Device opens two short receive Windows after each uplink transmission. Deep sleep the rest of the time. Lowest power consumption. Used for sensors that transmit periodically.
Class B (Beacon): Device opens receive Windows at scheduled times synchronized by beacon signals from the gateway. Higher power than Class A but allows downlink without waiting for an uplink.
Class C (Mains-Powered): Device listens continuously except when transmitting. Highest power consumption but lowest downlink latency. Used for actuators that need instant commands.
LoRa Modulation Parameters
Spreading Factor (SF): Controls the chirp rate. SF7 is fastest (5.5 kbps), SF12 is slowest (0.3 kbps) but has the longest range. Each increase in SF doubles the time-on-air and extends range approximately 20%.
Bandwidth (BW): 125 kHz (standard), 250 kHz, or 500 kHz. Wider bandwidth increases data rate but reduces sensitivity.
Coding Rate (CR): Forward error correction overhead. CR 4/5 gives minimal protection, CR 4/8 gives maximum protection at the cost of data rate.
The Things Network (TTN)
LoRaWAN Deep Dive explores TTN in depth. Here is the basic flow:
- Register a device on the TTN console (DevEUI, AppKey, AppEUI)
- A nearby TTN gateway picks up the LoRa transmission
- TTN network server handles authentication, deduplication, and routing
- Your application receives decoded payload via MQTT or HTTP Webhooks
Arduino LoRaWAN Example
#include <MKRWAN.h>
LoRaModem modem;
const char* appEui = "0000000000000000";
const char* appKey = "0123456789ABCDEF0123456789ABCDEF";
void setup() {
Serial.begin(115200);
if (!modem.begin(EU868)) {
Serial.println("LoRa modem init failed");
while (1) {}
}
int connected = modem.joinOTAA(appEui, appKey);
if (!connected) {
Serial.println("OTAA join failed");
while (1) {}
}
Serial.println("Joined LoRaWAN network");
modem.minPollInterval(60);
}
void loop() {
float temperature = 23.5;
float humidity = 55.2;
uint8_t payload[4];
payload[0] = (uint8_t)(temperature * 10) >> 8;
payload[1] = (uint8_t)(temperature * 10) & 0xFF;
payload[2] = (uint8_t)(humidity * 10) >> 8;
payload[3] = (uint8_t)(humidity * 10) & 0xFF;
int err = modem.beginPacket();
modem.write(payload, 4);
err = modem.endPacket(true);
if (err > 0) {
Serial.println("Transmission successful");
} else {
Serial.println("Transmission failed");
}
delay(600000); // Transmit every 10 minutes
}
Python Payload Decoder
The application server receives raw bytes. Decode them with a payload function:
def decode_uplink(input_bytes: bytes) -> dict:
temp_raw = (input_bytes[0] << 8) | input_bytes[1]
temperature = temp_raw / 10.0
hum_raw = (input_bytes[2] << 8) | input_bytes[3]
humidity = hum_raw / 10.0
return {"temperature": temperature, "humidity": humidity}
# Example: bytes received from TTN
raw = bytes([0xEB, 0x03, 0x02, 0x26])
result = decode_uplink(raw)
print(result)
Expected output:
{'temperature': 60.3, 'humidity': 5.5}
Common Mistakes
Too much data per transmission: LoRaWAN limits payload size by data rate (SF12 allows only 51 bytes). Keep payloads small and encode efficiently.
Transmitting too frequently: LoRaWAN fair use policy limits airtime. One transmission every 30 seconds on SF12 will violate the duty cycle.
Ignoring duty cycle limits: EU868 band enforces 1% duty cycle per sub-band. After a transmission, the device must wait before sending again.
No adaptive data rate (ADR): ADR lets the network optimize SF and power based on signal quality. Disabling ADR wastes battery and airtime.
Using Class C on battery: Class C devices listen continuously, draining the battery in days. Use Class A for battery-powered sensors.
Practice Questions
What is the difference between LoRa and LoRaWAN? LoRa is the physical radio modulation (spread spectrum). LoRaWAN is the MAC protocol that defines device classes, frame formats, and network architecture on top of LoRa.
Why does a higher spreading factor provide longer range? Higher SF increases processing gain, allowing the receiver to decode signals below the noise floor. Each SF increase doubles time-on-air and extends range by ~20%.
When would you use Class C over Class A? Class C for mains-powered actuators that need instant commands (electric valve, alarm siren). Class A for battery-powered sensors that transmit periodically.
What is the purpose of the network server in LoRaWAN? The network server handles authentication, deduplication (multiple gateways may receive the same message), adaptive data rate, and routing to the application server.
Why is encryption mandatory in LoRaWAN? LoRaWAN mandates AES-128 encryption at the network level (NwkSKey) and application level (AppSKey). This prevents eavesdropping and replay attacks on the open radio channel.
Mini Project
Build a simulated LoRaWAN sensor network with Python:
import random, time, struct, hashlib
class LoRaSensor:
def __init__(self, dev_eui: str, sf: int = 12):
self.dev_eui = dev_eui
self.sf = sf
self.battery = 100.0
def transmit(self, temperature: float, humidity: float) -> bytes:
energy_cost = self.sf * 0.5
self.battery -= energy_cost
payload = struct.pack('!hh', int(temperature * 10), int(humidity * 10))
mic = hashlib.sha256(payload + self.dev_eui.encode()).digest()[:4]
return payload + mic
class LoRaWANNetwork:
def __init__(self):
self.gateways = {}
def receive(self, frame: bytes, rssi: int = -120):
payload = frame[:-4]
temp_raw, hum_raw = struct.unpack('!hh', payload)
temperature = temp_raw / 10.0
humidity = hum_raw / 10.0
print(f"Decoded: {temperature}°C, {humidity}% (RSSI: {rssi} dBm)")
sensor = LoRaSensor("00:11:22:33:44:55:66:77", sf=10)
network = LoRaWANNetwork()
for i in range(5):
temp = round(random.uniform(15.0, 35.0), 1)
hum = round(random.uniform(30.0, 80.0), 1)
frame = sensor.transmit(temp, hum)
print(f"TX {i+1}: {temp}°C, {hum}% — Battery: {sensor.battery:.1f}%")
network.receive(frame, rssi=random.randint(-130, -90))
time.sleep(0.3)
Expected output:
TX 1: 24.3°C, 55.2% — Battery: 95.0%
Decoded: 24.3°C, 55.2% (RSSI: -112 dBm)
TX 2: 29.8°C, 41.7% — Battery: 90.0%
Decoded: 29.8°C, 41.7% (RSSI: -98 dBm)
Cross-References
- LoRaWAN Deep Dive
- MQTT
- IoT Communication Protocols
- IoT Gateways
- IoT Overview
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro