LoRaWAN for Low-Power IoT

IoTEmbeddedArchitecturePerformance
Share on LinkedIn

A soil moisture sensor in a vineyard needs to send 4 bytes every hour for five years on a pair of AA batteries. Wi-Fi would last weeks. Cellular would cost $5/month in SIM fees. LoRaWAN sends those 4 bytes over 3 km to a gateway, then to the cloud, drawing microamps between transmissions. It's not fast — 300 bps to 5.5 kbps depending on configuration — and it's not for video or firmware updates. It's for the millions of sensors that need to whisper, not shout.

LoRaWAN architecture

End Device (sensor)  ──LoRa RF──►  Gateway  ──IP──►  Network Server  ──►  Application Server
   │                                  │                    │
   Battery powered               Mains powered         Manages keys,
   868/915 MHz                   Ethernet/backhaul     routing, ADR
   0.3-50 kbps                   Multiple channels

Devices never talk directly to the application server. The network server handles all MAC-layer concerns.

Device classes

Class RX windows Power Use case
A 2 short windows after TX Lowest Sensors, meters
B Scheduled + post-TX Medium Actuators with predictable downlink
C Always listening Highest Real-time control, street lighting

99% of battery-powered sensors are Class A.

Spreading factors and range

LoRa modulation uses spreading factors (SF7-SF12) that trade data rate for range:

SF Data rate (EU868) Sensitivity Airtime (10 bytes) Range (urban)
SF7 5.5 kbps -126 dBm ~50 ms ~2 km
SF9 1.2 kbps -131 dBm ~180 ms ~5 km
SF12 0.3 kbps -137 dBm ~1.5 s ~10 km

Higher SF = longer range but more airtime = more battery drain and less network capacity. Use Adaptive Data Rate (ADR) to let the network server optimize SF per device based on signal quality.

End device code (Arduino/ESP32)

#include <LoRaWAN.h>

void setup() {
    LoRaWAN.begin(EU868);
    LoRaWAN.setAppEUI(APP_EUI);
    LoRaWAN.setDevEUI(DEV_EUI);
    LoRaWAN.setAppKey(APP_KEY);
    LoRaWAN.join(OTAA);  // Over-the-Air Activation
}

void loop() {
    float moisture = read_soil_sensor();
    uint8_t payload[2];
    payload[0] = (uint16_t)(moisture * 100) >> 8;
    payload[1] = (uint16_t)(moisture * 100) & 0xFF;

    LoRaWAN.send(1, payload, 2);  // port 1, 2 bytes
    LoRaWAN.sleep();  // deep sleep until next reading
    // Wake every 15 minutes via RTC timer
    enter_deep_sleep(15 * 60);
}

OTAA (Over-the-Air Activation) is preferred over ABP (Activation By Personalization) — session keys are negotiated, not pre-provisioned.

Payload encoding

Keep payloads compact. Use a binary format, not JSON:

# Decoder (Application Server — The Things Stack)
def decode_uplink(payload):
    if len(payload) >= 2:
        moisture = ((payload[0] << 8) | payload[1]) / 100.0
        return {"soil_moisture_percent": moisture}
    if len(payload) >= 4:
        temp = struct.unpack(">h", payload[0:2])[0] / 100.0
        humidity = payload[2]
        battery = payload[3]
        return {"temperature_c": temp, "humidity_percent": humidity, "battery_percent": battery}

Define a Cayenne LPP or custom binary schema per device type. Document it alongside the firmware.

Gateway placement

Rules of thumb:

Use RF planning tools (Radio Mobile, CellMapper for LoRa) to model coverage before deploying hardware.

Duty cycle regulations

EU868 enforces 1% duty cycle per sub-band (device can transmit max 1% of the time). At SF7 with a 10-byte payload, that's roughly 140 messages per day. Plan your reporting interval accordingly:

Max messages/day ≈ (86400 seconds × 0.01) / airtime_per_message
SF7, 10 bytes: ~140/day → every 10 minutes is safe
SF12, 10 bytes: ~14/day → every 2 hours maximum

US915 has different regulations (no duty cycle limit but frequency hopping required).

Size LoRaWAN payload under 51 bytes for lowest data rate SF12 — larger payloads force faster SF and reduce range dramatically.

ADR and spreading factor selection

Higher SF = longer range, lower data rate, more airtime:

SF Range (suburban) Airtime (10B payload) Use case
SF7 ~2 km ~60 ms Frequent updates, mains powered
SF10 ~5 km ~370 ms Battery, hourly readings
SF12 ~15 km ~1.5 s Remote sensors, daily readings

Adaptive Data Rate (ADR) lets network server reduce SF when signal is strong — saves battery and increases network capacity.

Join procedure and security

OTAA (Over-The-Air Activation) preferred over ABP:

Device → Join-Request → Network Server → Join-Accept
Session keys derived from AppKey — never transmitted

ABP hardcodes keys in firmware — device clone attack copies keys to rogue device. OTAA rotates session keys on each join.

Downlink limitations

LoRaWAN is uplink-heavy — downlink slots are scarce:

Don't design command-and-control that requires frequent downlinks on Class A battery devices.

Pair with IoT OTA updates rollback for firmware delivery over constrained LoRaWAN downlinks.

Common production mistakes

Teams get lorawan low power wan wrong in predictable ways:

IoT deployments of lorawan low power wan fail in the field when firmware assumes stable Wi-Fi, OTA rollback is untested, and device certificates expire without automated renewal.

Resources

Frequently asked questions

What is LoRaWAN and how is it different from Wi-Fi or cellular?

LoRaWAN is a low-power wide-area network protocol that sends small payloads (10-250 bytes) over kilometers using unlicensed ISM bands (868 MHz EU, 915 MHz US). Devices sleep between transmissions and run on batteries for years. Wi-Fi and cellular offer higher bandwidth but consume 10-100x more power and require infrastructure LoRaWAN doesn't.

What are LoRaWAN device classes A, B, and C?

Class A devices receive only during two short windows after each uplink — lowest power, most common. Class B adds scheduled receive slots synchronized by gateway beacons — moderate power. Class C keeps the receiver always on — highest power, used for actuators that must respond immediately.

How long do LoRaWAN sensor batteries last?

5-10 years on 2x AA batteries for Class A devices sending one reading every 15 minutes, depending on spreading factor, payload size, and battery chemistry. Higher spreading factors (longer range) increase airtime and reduce battery life. SF7 at close range maximizes battery; SF12 at maximum range minimizes it.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →