LoRaWAN: Long-Range Sensor Networks
How I build a small LoRaWAN network for battery sensors, from nodes and gateways to ChirpStack, compact payloads, antenna placement, and field testing.
Share

The first time I tried LoRaWAN, I was also drawn in by the promise of “several kilometers.” Then I put a node behind two walls, tucked its antenna beside the battery, and sent JSON every 30 seconds. The result looked nothing like the headline range. After a few walks around the test site waiting to see whether packets arrived, I realized the difficult part was not making the radio transmit. It was gateway placement, airtime discipline, sleep behavior, and designing the application to survive a missed packet.
This is the setup I would use for a small network of roughly 5–50 temperature, humidity, water-level, or machine-state sensors across a farm, warehouse, or campus. The target is a few readings per hour, long battery life, and no SIM in every node. It is not a good fit for cameras, audio, real-time control, or devices that must receive commands continuously.
Separate LoRa from LoRaWAN first
LoRa is the radio layer based on chirp spread spectrum modulation (CSS is a radio wave modulation technique). LoRaWAN is the networking protocol above it: joining, encryption, frame counters, receive windows, data rates, and the path from a gateway to a network server.
Two LoRa boards sending strings directly to each other can be a useful radio test, but that is not yet LoRaWAN. A LoRaWAN node also does not select one permanent gateway, and nodes do not relay packets for one another. The topology is star-of-stars: every gateway that hears an uplink forwards a copy to the server, which deduplicates them into one application event.

A node is not tied to one gateway. Every gateway that hears an uplink forwards it to the network server, which deduplicates packets before passing data to the application.
sensor nodes --LoRa--> one or more gateways
|
Ethernet / 4G / Wi-Fi
|
ChirpStack network server
|
MQTT
|
database / dashboard / alert
A gateway is closer to a radio-to-IP bridge than the place where sensor logic should run. Decoding, storage, and alerts belong at the application layer. This separation lets you add a gateway for better coverage without reflashing every sensor.
A small MVP that teaches the right lessons
| Block | Practical starting point |
|---|---|
| Node | Low-power MCU plus an SX1262 radio, or an STM32WL module |
| Example sensor | BME280, float switch, door contact, or a properly conditioned 4–20 mA input |
| Power | Two lithium AA cells, or protected Li-SOCl₂ where appropriate |
| Gateway | Multi-channel LoRaWAN gateway with an SX1302/SX1303 concentrator |
| Network server | ChirpStack running locally with Docker |
| Data path | MQTT into Node-RED, Home Assistant, or a dedicated database |
Do not use a single-channel LoRa board as a pretend gateway and then judge the reliability of the network. A real LoRaWAN gateway listens on multiple channels and spreading factors concurrently. In a small pilot, one properly placed multi-channel gateway is worth more than many nodes transmitting at excessive power.
I normally start with Class A devices. After each uplink, the node opens two short receive windows and then sleeps; this is the lowest-power class and suits sensors well. Class C listens almost continuously and makes more sense for mains-powered equipment. Class B adds scheduled receive slots, but it introduces complexity that most first sensor deployments do not need.
Pick the regional plan before powering the radio
A frequency plan is not something to choose based on whichever antenna is in the parts drawer. It defines channels, data rates, transmit power, dwell time, and regulatory limits. The node, gateway, and network server must use the same regional parameters.
The LoRa Alliance Regional Parameters list Viet Nam under AS923-2, which shifts the original AS923 plan down by 1.8 MHz. Before transmitting, I still verify the current national rules, the plan supported by the gateway or network provider, and the exact regional variant compiled into the firmware. A node that never joins may simply be using AS923-2 while the gateway is configured for AS923-1.
Do not copy an EU868 or US915 tutorial and only rename a constant. Regional differences affect channel masks and payload capacity at each data rate, not just the center frequency.
Node firmware: measure quickly, send briefly, sleep deeply
The first node lifecycle should be boring, which is a compliment:
wake -> read sensor -> validate -> encode binary -> uplink -> open RX1/RX2 -> save state -> sleep
I use OTAA instead of ABP so session keys are derived during a join. AppKey and other root keys do not belong in Git, debug logs, or a credential shared by an entire batch. Once a node has joined, it should retain its session context and frame counters in suitable persistent storage; joining after every wake wastes both energy and airtime.

Deep-sleep current is only one part of the budget. I measure sensor startup, join, uplink, RX1/RX2, and the path where the node cannot find a gateway.
On the bench, I measure the full cycle rather than only quoting deep-sleep current: sensor startup, radio join, uplink, both receive windows, and the failure path when no gateway is available. A node may sleep at a few microamps and still drain its battery quickly if it performs a new join every few minutes.
ADR works well for a fixed node. The network server observes multiple uplinks and can request a more efficient data rate and transmit power when the link has enough margin. For moving nodes or links that change quickly, I am more cautious and validate the behavior rather than enabling ADR everywhere by habit.
A nine-byte payload beats radio JSON
LoRaWAN is a low-bandwidth network. I do not send { "temperature": 27.35 } over the air; the server can produce JSON after decoding. A compact environmental payload might be:
| Byte | Value | Type |
|---|---|---|
| 0 | Protocol version | uint8 |
| 1–2 | Sequence | uint16 |
| 3–4 | Temperature × 100 | int16 |
| 5 | Humidity × 2 | uint8 |
| 6–7 | Battery voltage in mV | uint16 |
| 8 | Error/alarm flags | bit field |
uint8_t payload[9];
payload[0] = 1;
payload[1] = sequence >> 8;
payload[2] = sequence & 0xff;
payload[3] = temperatureX100 >> 8;
payload[4] = temperatureX100 & 0xff;
payload[5] = humidityX2;
payload[6] = batteryMv >> 8;
payload[7] = batteryMv & 0xff;
payload[8] = flags;
The version lets an old decoder reject a format it does not understand. The sequence reveals missed or repeated packets. Byte order, scale, units, and error values must live beside the decoder source; do not transmit a C struct layout and hope the server agrees with the compiler.
Do not confirm every uplink
Losing one temperature sample is usually harmless because another will arrive a few minutes later. I use unconfirmed uplinks for periodic telemetry, with sequence numbers and server-side timestamps. Confirmed uplinks are reserved for the few events where knowing that the network received the frame is genuinely useful, and retries still need a limit.
Downlinks are more scarce than uplinks: a gateway transmits instead of listening, while a Class A node only listens after it has sent something. I therefore do not design LoRaWAN as an interactive terminal. A new threshold or sample interval can wait in a queue and be applied after the next uplink; an emergency command that needs an immediate response belongs on another transport.
A practical strategy for slow sensors is:
- Sample locally more often, but send
min/avg/maxevery 5–15 minutes. - Send early on a state change, such as a water float switching from full to empty.
- Add a slower heartbeat so a quiet but unchanged node is still observable.
- If an uplink fails, retain the newest summary rather than growing an endless flash queue.
Gateway placement matters more than transmit power
Long range appears only when the link budget and Fresnel clearance cooperate. I place the gateway high and in the clear, away from metal roofs, water tanks, electrical cabinets, and dense power cabling. Long coax runs lose RF energy, so mounting the gateway near the antenna and bringing Ethernet/PoE to it is often better than running a long antenna cable.

A high, clear gateway position usually helps more than extra node power. A coverage survey records packet delivery rate, SNR, and data rate at the actual installation points.
For outdoor gateways, I check the weatherproof enclosure, downward-facing cable glands, drip loops, and the grounding or surge-protection plan appropriate for the site. I never enable a transmitter without the correct antenna connected.
For a coverage survey, I carry a test node that sends a sequence-numbered payload at a responsible interval. At each planned sensor location I record:
- Packet delivery rate, not only the RSSI of the best-looking packet.
- SNR, data rate/spreading factor, and which gateways received it.
- Results with warehouse doors closed, in wet weather, or with normal vehicles present.
- Link margin: a link that only just works today is not a comfortable deployment.
Low RSSI is not automatically a failure if SNR remains decodable, but packet delivery rate answers whether the system is useful. I prefer another gateway or a better antenna position before forcing every node onto a high spreading factor, because longer airtime reduces capacity for the whole network.
Checklist before scaling beyond the pilot
- Node, gateway, and server use the correct regional plan; in Viet Nam, verify AS923-2 and current rules.
- OTAA credentials are unique per device and absent from the repository.
- Session state and frame counters survive reset, or the rejoin strategy is explicit.
- The payload has a version, sequence, units, and fixed decoder test vectors.
- Unconfirmed uplink is the default; confirmed traffic and downlinks have a budget.
- Current is measured across a complete cycle, including a missing-gateway failure.
- Antennas are tested with the enclosure closed and in the final installation position.
- Packet delivery rate is observed for several days before coverage is accepted.
- Low battery, silent nodes, and gateway backhaul loss all create alerts.
- One golden node remains available for regression tests after firmware or infrastructure changes.
For me, LoRaWAN earns its place when every node has only a few important bytes, sits beyond sensible Wi-Fi coverage, and needs to run for a long time on a battery. A good deployment is not the one with the most impressive single range test. It is the one that still receives enough data on a rainy day, tells me which node went silent, and does not consume batteries because the firmware talks too much.
References
- ChirpStack — open-source LoRaWAN Network Server
- RadioLib — LoRaWAN support and starter notes
- MCCI Arduino LoRaWAN — OTAA BME280 sensor example
- Zephyr LoRaWAN Class A sample
- LoRaWAN network architecture — The Things Network
- LoRaWAN limitations and airtime guidance — The Things Network
- LoRa Alliance Regional Parameters — AS923 variants
Share
Keep exploring
Read next
Related articles
A smart thermostat that learns how your room warms up
Notes on building an ESP32 smart thermostat that learns a room's heating rate, starts at the right time, and keeps safety control local.
mmWave Radar: Motion, Gesture, and Presence Detection
Practical notes on separating motion, gesture, and presence detection, choosing the right mmWave radar module, and building an ESP32 prototype that is easy to tune.
Privacy-First Video Doorbell: Local Processing Design
How I design a video doorbell around RTSP, Frigate, and Home Assistant so person detection and clip storage stay inside the house instead of depending on a camera cloud.