MeshCore on Heltec and nRF52: off-grid, encrypted, solar-powered
Table of contents
The internet works because there is a path between every pair of endpoints. The internet fails because that path crosses corporate networks, regulated spectrum, and political borders. Each of those fails on a regular basis. The 2025 outage of a single Austrian mobile operator knocked emergency lines offline for hours. Storms take fibre out for days. Power cuts take the ISP gear out as long as the cut lasts.
A LoRa mesh is the small experiment in what stays up when none of that does. No tower. No ISP. No exchange. No permission. Two of these radios in line of sight talk over a kilometre on a coin cell. Forty of them across a town form a self-routing network that needs nothing else to function.
This post is about MeshCore, the protocol I settled on for that experiment: the hardware, the budgets that decide what is possible, the encryption properties, and the projects it opens up past emergency comms. Telemetry from sensors that live nowhere near WiFi. A side channel to my server from a distance. Home Assistant feeds from things the smart-home stack would never reach on its own.
Why MeshCore instead of Meshtastic#
Meshtastic is the well-known LoRa mesh project. Same hardware, large community, phone apps, and the thing most people start with. It also has a specific opinion about what a mesh is for: chat, GPS positions, maybe telemetry. The protocol is tuned for that and the firmware is opinionated.
MeshCore is smaller in scope, deliberately. That smaller scope is what makes it better at the parts I care about.
| Meshtastic | MeshCore | |
|---|---|---|
| Packet size, text message | hundreds of bytes | dozens of bytes |
| Routing | flood-based | explicit, neighbour table |
| Airtime cost on a busy mesh | high | low |
| E2E encryption, direct messages | opt-in | default |
| Phone app | full-featured | minimal, works |
| Fit on 8 MB flash / 320 KB RAM | tight | room for app code |
| User base | large | small |
Routing is the part that matters most off-grid. Nodes keep a small neighbour table and route through it instead of flooding every packet to everyone. That is gentler on the airtime budget and harder for a hostile node to spam.
The trade-off is the user base. Want the largest active LoRa mesh? That is Meshtastic. Want a small private one where you understand every byte that goes on the air? MeshCore.
The encryption and the range#
Two properties of LoRa matter most for resilience: it goes far on very little, and the modulation is hard to jam casually. MeshCore adds two more on top: messages between any two known peers are end-to-end encrypted, and the routing is not flood-based, so a hostile node cannot easily spam the network.
Range, in a clean radio environment:
| Environment | Realistic range |
|---|---|
| Rooftop to rooftop, simple antennas, SF12 | ~10 km |
| With a relay or two between | a small town, one hop apart |
| Hill to hill, line of sight | hundreds of km (hobbyist logs) |
| Urban, concrete walls, unlucky floors | a few hundred metres |
Even the worst case beats WiFi.
Encryption matters because the air is shared. Anyone in the band can listen. MeshCore uses Curve25519 keypairs per node. A direct message is encrypted to the recipient's public key, and other nodes forward the encrypted bytes without being able to read them. Group channels use a shared key, which is less defensible but still keeps content opaque to passive sniffers outside the group.
There is no anonymity in the air, only confidentiality. Anyone listening sees that node A sent a packet routed via node B to node C. The packet itself is opaque. That fits the threat model: a corporate or state listener with a basic SDR who wants to know what people are saying, not a state actor doing traffic analysis on a small mesh.
The hardware#
Two board families on my mesh.
Heltec WiFi LoRa v3 is an ESP32-S3 plus an SX1262 radio plus an OLED. The v4 bumps the radio and the display brackets. Around 20 EUR each, USB-C for flashing, run MeshCore happily. The ESP32 gives you WiFi and Bluetooth alongside LoRa, which is nice for bridging the mesh to a phone or a local network. The cost is current draw: an ESP32 is a hungry chip even at idle, and it dominates the power budget if you want the node to last on battery.
nRF52840 boards (an Adafruit Feather and a couple of Promicro-shaped ones) are the low-power half. A Cortex-M4 with built-in BLE, a generous sleep mode, and a power profile far below the ESP32. Radio off and CPU asleep, the nRF52840 draws single-digit microamps. Wake it for a packet, send it, sleep it again, and a CR123 cell lasts months.
The two families mix on one mesh because LoRa is LoRa. The packets travel over the same SX126x radios at the same data rate. ESP32 board vs nRF52 board is mostly a power-budget question.
The radio budget#
The range numbers are the easy part. The duty cycle is the constraint. At 869 MHz in the EU SRD band, the legal duty cycle for the most common sub-band is 1%. In any sixty-minute window you can transmit for at most thirty-six seconds. On the higher-power sub-band the duty cycle drops to 0.1%, three point six seconds per hour.
That is fine for a few dozen nodes exchanging short text messages. It is NOT fine for a chatty firmware broadcasting position every minute. The first time I instrumented airtime on a node I expected the limit at maybe 5%. I was over 12% in steady state because the firmware was being too helpful.
The fix: cut the unnecessary broadcasts. MeshCore lets you tune broadcast intervals down to multi-minute spans, and skip them entirely on nodes that do not need position. Both helped. The neighbour-discovery cycle on a quiet mesh costs almost nothing once it is established.
The battery and solar budget#
On the nRF52840 side the math works without trying hard.
nRF52840 power profile
sleep ~3-8 uA
TX 25-80 mA for 200-600 ms per packet
RX 5-12 mA while listening
duty cycle wake every 30 s, listen 200 ms, send every 5 min
----------------------------------------------------------
average ~100 uA
2000 mAh battery -> most of a year before rechargeOn the ESP32 side the math fights.
ESP32 (Heltec) power profile
deep sleep tens of uA if done right
+ LDO and USB-serial chip draw you cannot turn off on-board
+ every WiFi/BLE wake costs more than a LoRa-only wake
----------------------------------------------------------
average ~5-10 mA (two orders of magnitude worse)
2000 mAh battery -> a couple of weeks instead of a yearThat single gap determined the mesh shape: ESP32 nodes near power or with bigger batteries, nRF52 nodes for the leaf positions that need to live on their own for months.
Solar was the part I was naive about going in. The textbook calc says pick a panel, multiply by sun-hours per day, get a daily energy budget. EU latitudes give 3 to 4 effective peak-sun-hours in summer, dropping to 0.5 to 1 in winter. A 1W panel makes 4 watt-hours on a good summer day, 0.5 watt-hours on a bad winter one. The long-run average is not the problem. Consecutive overcast days at the winter solstice are. Three in a row produce essentially zero charge.
Three things I had to learn:
- Size for the worst-case streak, not the average draw. Roughly four weeks of average draw for my latitude.
- The charge controller matters more than the panel. A naive linear charger throws away most of the panel's output when the battery is not at the right voltage. A proper MPPT controller (TI BQ25570 for tiny systems) makes the same panel produce two to three times the usable energy.
- Cabling and connector voltage drop costs more than you expect. A 50 cm cheap cable between panel and controller drops 0.3V at 100 mA. On a 3.3V system that is ten percent of the available power, gone before the controller sees it.
The node running right now in solar mode is an nRF52840 with a 4000 mAh 18650, a 2W panel, a BQ25570 MPPT module, in a waterproof enclosure on a south-facing fence. Eight months continuous, including one really bad week in February. State of charge that morning was 28%.
Off-grid because the grid is not always there#
The main reason the mesh exists. Not theatre. Concrete cases where the public network failed and this one did not.
- Mobile operator outage. Single-operator outages happen several times a year and locally take down everything that operator carries, including emergency lines. A LoRa mesh in your pocket does not care.
- Storm-driven fibre damage. A wind event takes out the local exchange, repair times measured in days. The mesh keeps relaying messages between everyone in radio reach.
- Power cuts. Home WiFi is gone. The cell tower's backup batteries hold for hours, maybe a day. A solar-charged LoRa node holds indefinitely.
- Travel through coverage holes. Walking, biking, hiking, sailing. The mesh covers a corridor as long as there are nodes along it. Three hilltop relays make a regional corridor doable.
The mesh does not replace normal comms. It is the radio equivalent of a generator: idle most of the time, useful the day the normal stack is down.
What it extends to#
Once the mesh exists as a transport, using it for more than messages is a small step.
Telemetry from places without WiFi. The nRF52 nodes are small and low-power enough to live on a fence post, in a shed at the back of the garden, on a hilltop relay. They report temperature, humidity, soil moisture, door states, water levels. The reports land on the mesh, get picked up by a gateway node, hop into MQTT, end up wherever I want them.
Home Assistant integration. This is the part I use daily. A Heltec node sits next to the Home Assistant server, paired over USB serial. A small bridge daemon reads packets off the serial, decodes them, posts them as MQTT messages on topics like mesh/node-04/temperature. Home Assistant subscribes through its MQTT integration. A sensor on a fence post 300 metres away with no WiFi anywhere near it now shows up as a normal entity in the dashboard, the history graphs, the automations. As far as Home Assistant cares, a LoRa node is a slower, longer-range Zigbee node.
Side channel to the server. The project I am on now. The relay VPS that fronts archworks.co is publicly reachable, but only while DNS and IP routing work, which is exactly what fails during an outage. A Heltec node in the mesh pinging a Heltec node co-located with my server, which has a serial bridge into a control daemon, gives me a way to send a command (reboot a VM, check service status, push a metric) when the normal path is down. Slow, limited bandwidth, reliable in conditions where nothing else is.
Remote control of gear I cannot run cables to. Garden irrigation valves. A weather station. A camera trigger. Anything that needs an occasional "do this now" command but is too far for WiFi and too remote to bother with a cellular SIM.
The unifying property: the mesh is a transport for small, infrequent, important messages over long distances on small power. Anything that fits that envelope becomes plausible to build once the mesh is up.
Where this is going#
Four nodes today. The plan is six by the end of the year, at least three on solar. After that the projects stack up:
- A hilltop relay with a directional antenna to extend reach into the next valley.
- A few more sensor leaves around the garden and the shed.
- The Home Assistant gateway done properly (right now it is a Python script in a tmux session).
- The server side channel finished, with the command surface limited to things that are safe over a low-bandwidth one-way link.
None of this is theoretical. The radios are cheap, the firmware is open, the math is small. The frustration is the usual RF-project frustration plus the battery and solar work, which is mostly a sizing problem.
The mesh keeps working when the corporate and regulated parts of the stack do not. The cost of having it ready was six dev boards and a Sunday afternoon.