For my MSc thesis I built HEARD — a wearable device system for hiker group safety. The core challenge was getting multiple ESP32s to exchange GPS positions over LoRa at low latency, with no internet connection, in the mountains.
The problem with off-grid coordination
Standard hiking apps rely on a cell connection. The moment you leave coverage — which happens fast in alpine terrain — you lose the ability to know where your group is. We wanted a solution that was self-contained: no phones, no towers, just the devices talking to each other.
LoRa can reach several kilometres line-of-sight at the cost of low bandwidth (~250 bytes/s). That constraint shaped every design decision.
System architecture
The network has three device roles:
- Core — the group leader's unit, stores the route and orchestrates the mesh
- Node — carried by adults, transmits position and receives alerts
- Pico — minimal child device, emergency beacon only
All firmware runs on FreeRTOS with dedicated tasks for GPS parsing, LoRa TX/RX, and the safety watchdog. The task model made it straightforward to reason about timing: GPS updates every second, LoRa broadcasts every 5s.
The LoRa packet format
With ~250 bytes/s bandwidth shared among N devices, every byte matters. A position packet is 20 bytes: device ID (1), timestamp (4), lat/lon (8), altitude (3), flags (4).
typedef struct __attribute__((packed)) {
uint8_t device_id;
uint32_t unix_ts;
int32_t lat; // degrees * 1e6
int32_t lon; // degrees * 1e6
uint24_t alt_m;
uint8_t flags; // battery, emergency, ack
uint16_t checksum;
} LoRaPacket;
Off-route detection
The Core stores the planned route as a polyline of GPS waypoints. At each position update it computes the cross-track distance from the nearest segment. If a node drifts more than 50m for over 30s, an alert propagates through the mesh.
What I'd do differently
The mesh routing is naive — broadcast flood with a hop counter. For larger groups a proper protocol like Meshtastic's would be worth adding. I'd also move the route diffing to a Python pre-processing step to shrink the waypoint list before flashing.