I wanted a way to send text messages across kilometers of terrain without a cell tower, without Wi-Fi, and without paying anyone a monthly fee for the privilege. That’s basically the pitch for Meshtastic: cheap LoRa radios that form a self-healing mesh network and pass short messages, GPS positions, and telemetry from node to node. Pair that with solar power and you get something that can sit on a hilltop indefinitely, which is exactly what I set out to build. This post walks through the whole thing: the hardware, the firmware, the antenna theory I had to relearn the hard way, and what I’d change next time.
The Gear That Made It Possible
Before getting into assembly, it’s worth explaining why I picked each part, because none of these choices are arbitrary. A LoRa off-grid node has to solve three problems at once: it needs to stay powered without a wall outlet, it needs a radio that can actually punch a signal through kilometers of air, and it needs firmware that knows how to route messages intelligently when there’s no central server to rely on.
| Component | Role | Why I Chose It |
|---|---|---|
| D5 Solar LoRa Node | Base power & solar integration | It’s a full off-grid platform with built-in battery and solar management, so I wasn’t stuck tethering the thing to a wall outlet. |
| WisBlock RAK19007 | Power management & solar MPPT | Handles charge regulation between the panel and battery and exposes voltage telemetry to the microcontroller, which turned out to be a lifesaver for diagnosing power issues in the field. |
| WisBlock RAK4630 | LoRa microcontroller | An nRF52840 MCU paired with a Semtech SX1262 LoRa transceiver. It runs the Meshtastic firmware, talks to other nodes over LoRa, and handles Bluetooth to the companion app. |
| ALLNET 868MHz 14dBi Yagi antenna | Directional long-range LoRa | High gain and directional, which is exactly what you want for punching a message across kilometers in one specific direction rather than spraying power out in all directions. |
| Meshtastic firmware | Mesh networking | Open source, actively maintained, and flexible enough to reconfigure in the field without recompiling anything. |
| Meshtastic app | Node management | Quick testing, battery monitoring, and message sending from a phone. |
| ATAK | Tactical mapping | Adds real-time visualization of every node on a map. Totally optional, but it turns a pile of radios into something that looks like a command center. |
A couple of these choices deserve more explanation than a table cell allows. The MPPT stage on the RAK19007 matters more than it looks like it should. A solar panel doesn’t behave like a simple voltage source: its current-voltage curve has a single point, the maximum power point, where the product of voltage and current is highest, and that point drifts constantly with sunlight intensity and panel temperature. If you just wire a panel straight to a battery, you’re leaving real power on the table any time conditions aren’t perfect. An MPPT charge controller continuously hunts for that sweet spot and converts the panel’s output to whatever the battery actually wants, which is the difference between a node that limps through a cloudy week and one that just dies.
The antenna choice is the other one worth dwelling on. A 14dBi Yagi is a directional antenna: it doesn’t radiate power evenly in a sphere around itself, it concentrates that power into a narrow cone pointed wherever you aim it. That’s a trade, not a free lunch. You gain a lot of effective range in the direction you care about, but you lose coverage everywhere else, and the beamwidth on a 14dBi Yagi at 868MHz is narrow enough (on the order of 20 to 30 degrees) that aiming sloppily will cost you more signal than upgrading to a bigger antenna would ever gain you back.
Step 1: Assembling the Hardware
Honestly, the first thing I learned is that wiring is never as simple as the instructions make it look.
I started by connecting the RAK19007 power module to the D5 solar base. This module sits between the solar panel and the battery, doing the MPPT job described above while also protecting the pack from overcharging and reporting battery and solar voltage back to the microcontroller so the firmware can make sensible power decisions. For the battery itself, I went with three 18650 Li-ion cells in series, which gives a nominal pack voltage of 11.1V (3.7V per cell) and a full-charge ceiling around 12.6V. Series cells like this need a balance-aware charge controller, because if one cell in the string charges or discharges faster than its neighbors, you can end up over-stressing it even while the pack as a whole reads a healthy voltage. That’s part of what the RAK19007 is doing under the hood, and it’s why I didn’t just wire three loose 18650s together with tape and hope.
Next, I mounted the RAK4630 WisBlock module onto the D5 base. This is where the actual radio work happens: the nRF52840 runs the Meshtastic firmware and the SX1262 transceiver handles the LoRa physical layer, doing the chirp spread spectrum modulation that gives LoRa its long range and noise resistance in the first place (more on that below). Finally, I screwed on the ALLNET 868MHz Yagi. This thing is large and unmistakably directional, and I quickly learned that where you point it matters as much as anything else in the build. Because the gain is concentrated into a narrow beam, a few degrees of misalignment can drop a node that was previously showing full bars off the map entirely. Free space path loss increases with distance, but pointing error costs you signal on top of that, and the two effects stack.
Mounting height mattered too, and not just because “higher is better” in some vague sense. At 868MHz, propagation is largely line of sight, and even when you can technically see the other end of a link, you generally need to clear the first Fresnel zone, an elliptical volume of space around the direct line between antennas, or you’ll get diffraction losses even with a clear visual line of sight. I put the node on a tripod about 4 meters up for testing, which was enough to get it above nearby brush and fencing. For a permanent installation you’d want a roof, a pole, or a tree, with the general rule being that every bit of extra elevation buys you clearance against both terrain obstructions and the Fresnel zone.
Step 2: Flashing the Firmware
Flashing the firmware was surprisingly painless once I got the Python tooling sorted out. I grabbed the latest Meshtastic RAK4630 firmware build from GitHub and used the official CLI to flash it over USB:
pip install meshtastic
meshtastic --flash meshtastic_rak4630.binOnce flashed, there were a handful of settings worth understanding rather than just copying:
- Device Name: SolarYagi01, purely so I could recognize it in the app once I had a handful of nodes running.
- Frequency: 868 MHz, which is the ISM band allocation used across the EU for LoRa. This is not a free-for-all band: most of the sub-bands within it carry duty cycle restrictions, commonly around 1%, meaning your radio is legally only allowed to transmit for a small fraction of any given hour. Meshtastic’s firmware respects these limits by default, which is part of why you shouldn’t just crank settings to “always transmit” even if the hardware would technically let you.
- TX Power: 14 dBm, which works out to roughly 25 milliwatts of conducted power. This lines up with the legal ceiling for a lot of 868MHz sub-band allocations in Europe. It’s worth flagging a subtlety here: regulations typically cap effective radiated power (ERP or EIRP), not just the power coming out of the radio chip. Bolt a 14dBi gain antenna onto a radio transmitting at the legal limit and your effective radiated power can end up well above what’s allowed, so in a real deployment you’d want to check local rules and potentially back off the conducted TX power to compensate for antenna gain, rather than assuming “legal power plus legal antenna equals legal system.”
- Spreading Factor: SF7, the fastest and shortest-range option Meshtastic offers by default. Spreading factor controls how many chirps LoRa uses to encode each symbol; higher spreading factors trade throughput for sensitivity, roughly adding about 2.5dB of link budget per step up while also roughly doubling the airtime per message. SF7 is the right starting point when you want responsive messaging and you’re not yet trying to push a link to its absolute range limit.
- Encryption: AES128, which is Meshtastic’s default and keeps message contents private from anyone else listening on the same frequency. It’s worth knowing this is a pre-shared key scheme rather than something with public-key key exchange baked in, so key distribution across your nodes is still something you have to manage deliberately.
After setup, I opened the Meshtastic app and watched the node come alive: it reported battery voltage, RSSI to nearby peers, and showed up on the network map like a tiny solar-powered star. That first successful check-in is a genuinely satisfying moment, because it confirms the whole power and radio chain is actually working end to end rather than just looking right on paper.
Step 3: Planning the Mesh
I’m a visual thinker, so before final deployment I mapped out how I expected my nodes to relate to each other. It looked something like this:
graph TD
A["SolarYagi01"] -->|868MHz| B["NodeB - Hill"]
B --> C["NodeC - Farm"]
C --> D["NodeD - Forest Outpost"]
A --> D
B --> E["NodeE - Relay"]
The reason this diagram matters is that Meshtastic doesn’t route messages the way your home Wi-Fi router does. There’s no central access point deciding paths. Instead it uses a managed flood routing scheme: when a node has a message to send, it broadcasts it, and any node that hears it and hasn’t already seen that message will rebroadcast it, up to a configured hop limit. This is deliberately redundant. If one link between two nodes goes down, whether from a tree growing into the Fresnel zone or a battery dying, messages can still find another path through the mesh as long as some alternate route exists. That redundancy is the entire point of drawing the graph before deployment: it let me spot which nodes only had a single weak link to the rest of the network (a single point of failure) and adjust antenna orientation or add a relay node to give those links a backup path. Flood routing isn’t free, though. Every rebroadcast consumes airtime, and since the 868MHz band has duty cycle limits, a mesh that’s too dense or too chatty can start stepping on its own messages. Planning link topology ahead of time is as much about avoiding unnecessary rebroadcasts as it is about guaranteeing coverage.
Step 4: ATAK Integration
This is where it got genuinely fun. I installed ATAK (the Android Team Awareness Kit, originally developed for military and emergency response use) on my Android device and connected it to Meshtastic through the community-built plugin. Under the hood, the plugin translates Meshtastic’s node and message data into Cursor on Target (CoT), the XML-based situational awareness format that ATAK natively understands. The practical result is that every node’s position, battery level, signal strength, and any in-transit messages show up as live icons on a real map, updating as the mesh updates.
Watching the network light up in real time gave me a sense of scale I hadn’t expected from reading spec sheets. I could see which nodes were reliably relaying traffic for everyone else and which ones were barely hanging on with a marginal link. It’s one thing to know in the abstract that flood routing distributes load unevenly across a mesh; it’s another thing to watch one particular relay node’s icon flash constantly while others sit mostly idle. It made obvious, in a way the mermaid diagram alone couldn’t, exactly where I needed to improve antenna alignment.
Step 5: Fine-Tuning LoRa
Getting good performance out of LoRa means understanding that its parameters aren’t independent dials, they’re a set of tradeoffs against a shared airtime and sensitivity budget. Here’s what I landed on after testing:
| Parameter | My Setup | Notes |
|---|---|---|
| Bandwidth | 125 kHz | Meshtastic’s standard choice; wider bandwidth buys throughput but raises the noise floor the receiver has to work against, which lowers sensitivity. 125 kHz is a solid middle ground. |
| Coding Rate | 4/5 | Forward error correction: for every 4 bits of data, 1 parity bit is added, giving the receiver a way to recover from small burst errors at the cost of roughly 20% extra airtime. |
| Spreading Factor | SF7 | Fastest option and my default; SF9 through SF12 trade speed for a longer link budget when a particular hop needs the extra range. |
| TX Power | 14 dBm | The practical ceiling for this band without exceeding legal ERP once antenna gain is factored in. |
| Antenna Height | 4 m | Buys clearance against both ground-level obstructions and the first Fresnel zone. |
The single biggest lesson from this stage was that antenna placement mattered more than any of the radio parameters in that table. That’s a consequence of how LoRa’s link budget actually works: your total budget is transmit power plus antenna gain at both ends, minus free space path loss, minus any pointing loss from misalignment. Free space path loss is fixed by distance and frequency and you can’t do much about it. Transmit power is capped by regulation. That leaves antenna gain and pointing accuracy as the two levers you actually have meaningful control over, and pointing accuracy is free. A tiny adjustment in the Yagi’s angle changed RSSI readings by 10-15 dB in my testing, which is a huge swing in link margin, easily enough to move a link from “barely holding on” to “clean and reliable,” even though the LoRa demodulator can technically decode signals well below the noise floor (LoRa’s processing gain lets it pull symbols out of signal-to-noise ratios as low as around negative 20 dB at high spreading factors). RSSI alone doesn’t tell the whole story either; signal-to-noise ratio matters just as much, since a strong RSSI reading next to an equally strong noise floor won’t decode any better than a weak signal in a quiet environment.
Step 6: Field Testing
After assembly, I took the node out to a hill and tested communication with three other nodes scattered across the area. The results were better than I expected going in:
- Messages traveled more than 60 km without much trouble.
- Nodes reported battery voltage and solar charge in real time over the mesh telemetry channel.
- The mesh topology I’d sketched out beforehand held up: it correctly predicted which links were strong and which needed antenna adjustment.
A link that long isn’t just a matter of raising TX power; it’s mostly a story about elevation and geometry. At UHF frequencies like 868MHz, the radio horizon is governed by roughly the same math as visual line of sight, adjusted slightly for atmospheric refraction, and a rough rule of thumb is that your horizon distance in kilometers is about 3.57 times the square root of your antenna height in meters. From ground level you’re not seeing 60 km of terrain no matter how good your radio is; from a hilltop with a clear line to another elevated node, and with a 14dBi Yagi concentrating power along that specific path, it becomes achievable in a single hop. It was genuinely exciting to watch messages hop from node to node across kilometers of terrain without a single cell tower or Wi-Fi access point anywhere in the chain.
Looking Ahead: Future Addons
There’s a long list of things I still want to try with this setup:
- More nodes for a denser mesh, which improves redundancy but needs to be balanced against the duty cycle and airtime constraints discussed above so the network doesn’t start flooding itself.
- Telemetry broadcasts of solar and battery stats across the whole mesh, so I can monitor the health of every node from a single dashboard instead of checking each one individually.
- LoRaWAN integration for IoT sensors. It’s worth noting LoRaWAN is a genuinely different architecture from Meshtastic’s mesh: it’s a star topology where sensor nodes talk directly to a gateway rather than hopping through peers, which makes it a good complement for battery-sipping sensors rather than a replacement for the mesh itself.
- Portable deployment kits with tripods and foldable solar panels, so I can stand up a temporary relay node anywhere I need one without redesigning the whole system from scratch.
