DEV Community

Cover image for LighthouseReckoning: A Lightweight LoRa Mesh Network for Arduino, ESP32 and RP2040
Fynn Schulz
Fynn Schulz

Posted on Edited on

LighthouseReckoning: A Lightweight LoRa Mesh Network for Arduino, ESP32 and RP2040

LoRa Gives You Range. It Doesn’t Give You a Network.

LoRa is great at moving small amounts of data over long distances.

Getting a packet from one microcontroller to another? Straightforward.

Getting that packet from a sensor that cannot directly reach your base station, through several other nodes, reliably and without turning your application into a networking stack?

That is a different problem.

Once a LoRa project grows beyond two devices, you quickly start dealing with routing, retries, duplicate packets, loops, disappearing nodes, and unreliable radio links. On a desktop computer, that would be networking infrastructure. On a microcontroller, it often ends up mixed directly into the application.

That was the problem I wanted to solve with LighthouseReckoning.

It is a lightweight LoRa mesh networking library for Arduino-compatible microcontrollers, currently tested on ESP32 and RP2040 with SX126x radios.

The goal is to let application code stay simple:

lhr.sendData(payload, sizeof(payload));
lhr.update();
Enter fullscreen mode Exit fullscreen mode

while the library handles getting that data toward its destination.

The problem: one radio link is easy, a network is not

Imagine a sensor that needs to send data back to a base station.

If it is within radio range, the network is simple:

Sensor → Home
Enter fullscreen mode Exit fullscreen mode

But now imagine the sensor is too far away.

You add another node:

Sensor → Relay → Home
Enter fullscreen mode Exit fullscreen mode

Then another:

Sensor → Relay → Relay → Home
Enter fullscreen mode Exit fullscreen mode

At that point, several questions appear.

Which neighbor should a node send to?

What happens when a relay disappears?

How long should packets be retried?

How do you prevent a packet from circulating forever?

How does a node know whether the next hop actually received the packet?

And how do you solve all of this without requiring every microcontroller to maintain a complete map of the network?

LighthouseReckoning uses a deliberately simple answer: every node only needs to know how to get closer to Home.

Why "Lighthouse Reckoning"?

The name describes the architecture surprisingly well.

A lighthouse does not search for ships. It stays where it is and provides a fixed point that others can navigate toward.

In LighthouseReckoning, one node plays that role: the Home node.

It is the destination of the network.

Every other node learns its distance to Home in hops and uses nearby nodes to move data closer to it.

A node might not be able to reach Home directly, but another node might.

That creates a chain:

Sensor → Relay → Relay → Home
Enter fullscreen mode Exit fullscreen mode

Every node is both a participant in the network and a possible landmark for nodes further away.

The "Reckoning" part comes from navigation. Nodes continuously make routing decisions using the information currently available to them. Neighbors appear, disappear, and change their distance to Home. The routing layer adapts to those changes.

No manually configured routes are required.

Distance-vector routing toward Home

LighthouseReckoning does not require every node to know the complete network topology.

Instead, each node tracks its own distance to Home.

For example:

Home (0 hops)
 ├── Relay A (1 hop)
 │     ├── Sensor C (2 hops)
 │     └── Sensor D (2 hops)
 └── Relay B (1 hop)
       └── Sensor E (2 hops)
Enter fullscreen mode Exit fullscreen mode

Nodes periodically exchange routing information with their neighbors.

If Sensor C knows that Relay A is one hop from Home, Sensor C can calculate:

Sensor C → Relay A → Home
Enter fullscreen mode Exit fullscreen mode

and assign itself a distance of two hops.

The node then selects a suitable neighbor that provides the shortest path toward Home.

The important part is what it doesn't need to know.

Sensor C does not need a full map of:

  • Relay B
  • Sensor D
  • Sensor E
  • every possible path through the network

It only needs enough information to choose the next hop that moves the packet toward Home.

If Relay A disappears, Sensor C can re-evaluate its available neighbors and select another route.

Reliability should be local

One of the design decisions in LighthouseReckoning is using hop-by-hop confirmation rather than waiting for a single end-to-end acknowledgment.

Consider this path:

Sensor C → Relay A → Relay B → Home
Enter fullscreen mode Exit fullscreen mode

An end-to-end approach would require the original sender to somehow know whether the packet successfully completed the entire journey.

That means the original sender may need to wait while the packet travels through multiple nodes.

Instead, LighthouseReckoning handles reliability locally.

Sensor C ──ACK── Relay A ──ACK── Relay B ──ACK── Home
Enter fullscreen mode Exit fullscreen mode

Sensor C only needs to know that Relay A received the packet.

After that, Relay A becomes responsible for delivering it to the next hop.

Relay B does the same.

This keeps retry state local to each radio link.

If one hop fails, the node responsible for that hop can retry without forcing the original sender to track the entire route.

For small, low-throughput embedded networks, this significantly simplifies the reliability model.

Using the library

A basic node can be set up with RadioLib and LighthouseReckoning:

#include <RadioLib.h>
#include <LighthouseReckoning.h>

#define PIN_CS    17
#define PIN_DIO1  20
#define PIN_RST   21
#define PIN_BUSY  22

SX1262 radio = new Module(PIN_CS, PIN_DIO1, PIN_RST, PIN_BUSY);

LighthouseReckoning lhr;

void onRadioIrq() {
  lhr.handleDio1Rise();
}

void setup() {
  radio.begin();

  // Configure the radio parameters for your application
  // and ensure they comply with regional regulations.

  lhr.beginAsNode(&radio, 0xA1B2C3D4);

  attachInterrupt(
    digitalPinToInterrupt(PIN_DIO1),
    onRadioIrq,
    RISING
  );
}

void loop() {
  lhr.update();

  static unsigned long lastSendMs = 0;

  if (millis() - lastSendMs >= 60000) {
    uint8_t payload[] = {
      0x01,
      0x02,
      0x03
    };

    lhr.sendData(payload, sizeof(payload));

    lastSendMs = millis();
  }
}
Enter fullscreen mode Exit fullscreen mode

The application does not need to know which relay is currently being used.

It sends data into the routing layer, and LighthouseReckoning handles forwarding it toward Home.

The Home node is similar, using beginAsHome() and an onDataReceived() callback to receive application data.

The library is designed around non-blocking operation. update() is called repeatedly from the main loop while radio events are handled through the DIO1 interrupt path.

What the library handles

The current implementation includes:

  • Automatic multi-hop routing
  • Distance-vector routing toward a single Home node
  • Periodic and reactive neighbor information
  • Hop-by-hop confirmations
  • Local packet retries
  • Duplicate detection
  • TTL protection
  • Non-blocking operation
  • Interrupt-driven radio handling
  • Optional duty-cycle budgeting
  • Compact packet formats
  • Integration with RadioLib

The minimum packet format is 2 bytes, while DATA packets use a 16-byte header.

The goal is not to create a general-purpose internet protocol stack for microcontrollers.

The goal is to provide a small routing and reliability layer for LoRa networks where data needs to travel through multiple embedded devices.

Tested on real hardware

The project has been tested on:

  • ESP32
  • RP2040
  • SX126x radios, including SX1262

The single-DIO1-interrupt design and Channel Activity Detection have specifically been validated on SX126x hardware.

Other radios supported by RadioLib may work for basic transmission and reception, but their CAD behavior and interrupt timing have not been independently verified.

Tested in the field, not just in a simulation

Multi-hop routing is difficult to evaluate purely with diagrams.

A network can look correct in theory while behaving differently once real radios, timing, interference, retries, and changing links are involved.

LighthouseReckoning therefore includes a real 4-hop forced-chain field test.

The test includes raw radio logs and an analysis of routing behavior over an actual multi-hop path rather than a simulation.

The protocol is also used in the Adam4EvesWine sub-project under ZDIN's Central Laboratory for Water for ad-hoc water-monitoring sensor networks.

That does not mean the library is finished.

It does mean the routing model has been exercised outside of a purely theoretical environment.

What is intentionally not in v1

Security is not implemented in v1.

Packets are currently not encrypted or authenticated.

That is a deliberate scope decision rather than an assumption that security does not matter.

The first version focuses on establishing and testing the routing and multi-hop reliability layer. Cryptographic protection is planned as a later protocol feature.

If you are considering LighthouseReckoning for an environment where confidentiality or packet authenticity matters, this limitation should be taken seriously.

Citing LighthouseReckoning

Version 1.0.0 is archived on Zenodo and has a permanent DOI.

If you use the project in research or need a stable reference:

DOI

DOI: 10.5281/zenodo.22033930

The project

LighthouseReckoning is available on GitHub with the source code, protocol documentation, examples, and field-test material.

👉 LighthouseReckoning on GitHub

The project is available through the Arduino Library Manager and can also be installed with PlatformIO or directly from the repository.

If you are building LoRa networks with ESP32, RP2040, Arduino-compatible hardware, or distributed sensors, feedback, testing, and ideas are welcome.

Top comments (1)

Collapse
 
fynn_schulz_8dad41ee4e7b profile image
Fynn Schulz

Great to finally share LighthouseReckoning publicly. I built it to make multi-hop LoRa networking on Arduino-compatible hardware simple, lightweight, and reliable.

The project is currently tested with ESP32 and RP2040 + SX126x radios, with automatic routing, hop-by-hop confirmations, retries, and no internet connection required.

I'd really appreciate feedback, especially from people working with LoRa, embedded systems, mesh networking, or distributed sensor networks.

GitHub: github.com/TidalImpact/LighthouseR...