embedded-nano-mesh
A tiny mesh networking protocol for small embedded devices.
embedded-nano-mesh allows you to build a self-healing mesh network
using very cheap microcontrollers and simple serial radio modules.
It is designed to run on extremely small devices such as:
- Arduino Nano / Uno / Mini
- small STM32 boards
- ESP8266 / ESP32
- Raspberry Pi Pico
- Linux devices (Raspberry Pi, Orange Pi, PCs)
The only thing required is a serial connection to a radio module.
No WiFi stack.
No heavy networking libraries.
Just a lightweight mesh protocol.
Why this library exists
Many mesh solutions require:
- powerful hardware
- complex configuration
- large memory footprint
embedded-nano-mesh is different.
It was built with the goal of running on very small and cheap boards.
You can create a mesh network using:
Microcontroller + Serial Radio Module
Example:
+--------------+ +--------------+
| Arduino Nano | | HC-12 |
| STM32 | + | JDY-41 | = Mesh node
| ESP8266 | | LoRa module |
| Linux PC | | etc. |
+--------------+ +--------------+
Connect multiple nodes together and they will automatically form a mesh network.
What you can build
This protocol works well for small distributed systems:
- Home automation
- Agricultural sensor networks
- Remote telemetry
- Industrial data collection
- Device-to-device messaging
- Remote control systems
Because nodes forward messages to each other, the network automatically extends its range.
Key features
no_stdcompatible- Works on very low-memory devices
- Runs on Linux and microcontrollers
- Works with any serial radio module
- Self-healing mesh network
- Configurable packet lifetime (hop limit)
- Duplicate packet filtering
- Very small protocol overhead
- Easy to port to new hardware
Quick start (5 minutes)
Add the library to your Cargo.toml:
embedded-nano-mesh = "3.1.0"
Then provide a serial interface using embedded-io.
Example for Linux:
embedded-nano-mesh-linux-io = "0.0.1"
Example usage:
let mut interface = new;
let mut mesh_node = ...;
mesh_node.send_to_exact.unwrap;
loop
That’s it.
Your device is now part of a mesh network.
How routing works
The protocol intentionally uses a very simple routing model.
When a node sends a packet:
- Nearby nodes receive it
- Each node decides whether to forward it
- The packet spreads through the network
This behaves similar to ripples in water.
To prevent infinite spreading, packets contain a lifetime (hop limit).
Each hop decreases the lifetime.
Example:
lifetime = 10
The packet can travel up to 10 nodes before it disappears.
Examples:
lifetime = 1 → only nearest devices
lifetime = 5 → medium network range
lifetime = 10 → large mesh
Avoiding duplicate packets
Mesh networks often suffer from packet echo.
embedded-nano-mesh prevents this using a duplicate filter.
Each node remembers recently seen packets:
(sender_id, packet_id)
If the same packet appears again within a short time window, it is automatically ignored.
This keeps the network from being flooded by echoes.
When a duplicate of an already-known packet is detected, the default
strategy (double_period) doubles the time window during which echoes are
ignored — a simple anti-flood backoff: the busier the network, the longer
echoes are suppressed. The alternative ignore strategy leaves the period
unchanged.
The filter remembers a limited number of packets at once. When its table is
full, the default overflow strategy (evict_oldest) drops the oldest entry
and accepts the new packet, so legitimate traffic is never silently lost.
Both the table size and the time window are configurable at compile time —
see "Compile-time configuration" below.
Version compatibility
Different versions of embedded-nano-mesh use the same wire protocol, so
nodes with different versions can coexist in one network.
The table below is generated by the test
tests/test_version_compatibility.rs and reflects the actual state of
cross-version compatibility:
| Scenario | Topology | Status |
|---|---|---|
| send_to_exact | v3 -> v2 | pass |
| send_to_exact | v2 -> v3 | pass |
| broadcast | v3 -> v2 | pass |
| broadcast | v2 -> v3 | pass |
| send_to_exact through relay | v3 -> v2 -> v3 | pass |
| ping-pong through relay | v2 -> v3 -> v2 | pass |
| transaction through relay | v2 -> v3 -> v2 | fail |
Known limitation: v2 send_with_transaction does not work when the
transaction packets have to transit through a v3 relay node, because the
v3 duplicate filter drops the FinishTransaction step (it reuses the
unique id of AcceptTransaction). Ping-pong and plain packet forwarding
are unaffected.
Examples
Example projects:
Arduino Nano example:
https://github.com/boshtannik/embedded-nano-mesh-arduino-nano-example
Linux example:
https://github.com/boshtannik/embedded-nano-mesh-linux-example
CLI tool:
https://github.com/boshtannik/embedded-nano-mesh-cli-tool
Platform support
Currently tested on:
- Arduino Nano
- Linux (PC / Raspberry Pi)
Expected to work on:
- STM32
- ESP8266
- ESP32
- Raspberry Pi Pico
- Windows
- macOS
Porting to new platforms is easy.
Porting to new hardware
The protocol communicates with radios through the embedded-io trait.
To port the library to a new platform you only need to implement:
embedded_io::Read
embedded_io::Write
for your serial interface.
That’s all.
Once your serial driver implements embedded-io, the mesh protocol will
work.
Managing packet collisions
To reduce packet collisions, it is recommended to use different
listen_period values for different nodes.
Example:
device 1 → 230 ms
device 2 → 240 ms
device 3 → 250 ms
This prevents devices from synchronizing their transmissions.
Protocol limits (default configuration)
- Payload size: 32 bytes
- Max addresses: 255 (configurable, see below)
- Receive queue: 5 packets
- Transit queue: 5 packets
- Duplicate filter size: 8 packets
- Duplicate ignore period: 1000 ms
Compile-time configuration
The protocol is configurable at compile time, either via environment
variables or a .env file in the project root (see .env.example). Real
environment variables take precedence over .env.
| Variable | Meaning | Default |
|---|---|---|
ENM_RECEIVER_FILTER_DUPLICATE_IGNORE_PERIOD |
how long (ms) a packet id is remembered | 1000 |
ENM_RECEIVER_FILTER_REGISTRATION_SIZE |
how many packet ids can be remembered at once | 8 |
ENM_RECEIVER_FILTER_OVERFLOW_STRATEGY |
what to do when the table is full: evict_oldest or drop_new |
evict_oldest |
ENM_RECEIVER_FILTER_DUPLICATE_STRATEGY |
what to do on a duplicate: double_period or ignore |
double_period |
ENM_ADDRESS_BYTES |
wire size of the device address / lifetime field: 1 (u8), 2 (u16) or 4 (u32) |
1 |
When the table is full, evict_oldest (default) removes the oldest entry
and accepts the new packet, so legitimate new packets are never silently
dropped in busy networks. drop_new rejects the new packet instead.
When a duplicate of an already-known packet is detected, double_period
(default) doubles the ignore period (anti-flood backoff); ignore leaves it
unchanged.
ENM_ADDRESS_BYTES controls the size of the device address and the packet
lifetime on the wire:
1→u8: 255 addresses, max 255 hops (default)2→u16: 65535 addresses, max 65535 hops4→u32: 4 billion addresses, max 4 billion hops
Warning: any value other than 1 changes the wire format, so all nodes
in a network must be built with the same setting and cannot interoperate
with default (u8) builds.
Example:
ENM_RECEIVER_FILTER_REGISTRATION_SIZE=16 cargo build
ENM_ADDRESS_BYTES=2 cargo build
The other protocol limits (payload size, queue sizes) are fixed constants in the source code.
Security
The protocol does not implement encryption.
If encryption is required, it should be implemented in the application layer.
Status
Current version: 3.1.0
Features:
- sending messages to an exact node (
send_to_exact) - broadcasting (
broadcast) - receiving messages (
receive) - hop-limited routing (
lifetime) - duplicate filtering
- intermediate node forwarding
- fully non-blocking: never stalls the user program loop
Delivery guarantees (transactions, ping-pong, acknowledgements, retries)
are intentionally not part of the protocol core. They are the
responsibility of the application layer and can be built on top of
send_to_exact / broadcast / receive.
Community
Telegram channel:
https://t.me/embedded_nano_mesh
GitHub:
Support the project
If this project helped you, you can support development.
Bitcoin:
bc1qc50tm0ppj3hh7fecd6d0rv8tdygy8uhe2cemzt
Buy me a coffee:
https://www.buymeacoffee.com/boshtannik
License
This project is licensed under:
- MIT
- Apache 2.0
- GPLv3
You may choose the license that best fits your needs.
Contributing
Contributions are welcome.
Please include in your pull request:
- Issue reference
- Short explanation of the fix
- Description of the solution