A lightweight decentralized, long-range, low-power, mesh communication protocol for IoT devices
Nodiflux is a protocol to enable decentralized. long-range, low-power communication between entities over a set of IoT node. This reposiory presents a complete implementation of the protocol using NodeMCU ESP32 modules as node. The goal of the protocol is to allow for short message delivery between nodes on the network without a central entity for routing management.
This section presents the high-level design principles behind the protocol. The protocol occupies the third layer (network layer) of the OSI model. The nodiflux is, therefore, responsible for structuring and managing a multi-node network. Specifically, the nodiflux protocol manages addressing, routing, and network traffic control. The current implementation of the protocol uses ESP-NOW v0.1 for data link and physical layers of the network.
The nodiflux protocol supports three types of packets. Specifically, discovery (DIS), data (DAT), and acknowledgement(ACK) packets are to be exchanged between nodes. All three types of the packets share common headers presented below:
| Field | Size(bytes) | Description |
|---|---|---|
| PacketType | 1 | A field to specify the type of a packet: DIS (0x01), ACK (0x02), and DAT (0x03). |
| src | 6 | This field contains the MAC address of the sender. |
| pkt_id | 16 | A field that contains a unique identification number of a packet. The ID is automatically generated by combining the MAC address of the sender and a random number. |
| chs | 1 | This field contains a checksum of all fields of a given packet. The checksum is calculated as the XOR of all bytes of a packet and is used to verify data integrity. |
Discovery packets do not posses any additional fields, as they are kept small for ensuring as little resource usage during the node discovery process, as possible. On the other hand, data and acknowledge packets entail additional fields, that are presented in the following table:
| Packet Type | Field | Size (bytes) | Description |
|---|---|---|---|
| DataPacket | dest | 6 | This field contains the MAC address of the receiver. |
| DataPacket | ttl | 1 | A field that holds the TTL value of the packet — the maximum number of hops before the packet is dropped. |
| DataPacket | msg | DATA_MESSAGE_SIZE | This field contains the actual binary message to be transmitted over the network. The DATA_MESSAGE_SIZE can be specified in the config.hpp file. |
| AcknowledgePacket | ack_pkt_id | 16 | This field contains the pkt_id of the DataPacket that has been received. |
| AcknowledgePacket | dest | 6 | This field contains the MAC address of the receiver. |
| AcknowledgePacket | ttl | 1 | A field that holds the TTL value of the packet — the maximum number of hops before the packet is dropped. |
The node discovery process is conducted by each of the nodes on the network on a periodic basis. Specifically, a node sends packets every DIS_BROADCAST_SPEED milliseconds. Once a node recieves a DIS packet it create an entry in a thread-safe singleton object—NodeRegistry. The MAC-address of the sender node and a timestamp are saved in the NodeRegistry and a new peer connection is established to the node. Furthermore, if a DIS packet is received and the node is already in the NodeRegistry only its timestamp gets updated.
An additional kernel task was set up to prune old (inactive) nodes. Particularly, the pruning task is conducted every NODE_REGISTRY_PRUNING_INTERVAL milliseconds, whereby the nodes whoes timestamps in the NodeRegistry are older than NODE_DISCARD_THRESHOLD milliseconds are discarded.
Data packets that cannot be delivered over a regular P2P connection are subjects to routing on the nodiflux network. In an absence of a P2P connection a network node falls back to its NodeRegistry and retrieves the most recent node from it. The packet is then updated by reducing the TTL counter and updating the checksum, and forwarded to the looked up node from the NodeRegistry.
Acknowledge packets are routed in a similar fashion. Each router on the network checks the ack_pkt_id field of an ACK packet, while routing it to the other node. If the ack_pkt_id is to be found in the RetryJournal, the entry with this packet id is removed to avoid sending the same message to the node which already received the packet and sent an ACK back.
Each of the nodes on the Nodiflux network is equipped with a thread-safe singleton object responsible for retrying the packet delivery—RetryJournal. Before a DAT packet is sent out an entry to the RetryJournal is established, that consists of a unique pointer to the packet to be sent, a timestamp, and a retries counter. The packet is send one more time every RETRY_TIME_THRESHOLD seconds until the retries counter reaches the RETRY_CNT_THRESHOLD. The entry is deleted from the retry journal once the threshold is reached or an ACK packet is received.
A BLE endpoint can be activated in the config.hpp file. This creates a BLE server with a single service. The service consists of two characteristics:
- Rx characteristics for writing all of the received client messages (the
WRITEproperty is used) - Tx characteristics for notifying the client about new messages (the
NOTIFYproperty is used)
A client application can be attached to the BLE characteristics to interact with the protocol running on a specific node. This allows for sending messages without an explicit interaction with the node. All of the data sent over this API must be prefixed with 6 bytes of a MAC-address of a destination node. All of the incoming messages pushed to a Tx characteristic are also prefixed with 6 bytes of a source node.
An alternative way to interact with the Nodiflux protocol is provided by setting up a WebSocket on an async web server hosted on each of the nodes. It is turned on by default and needs some extra engineering work to be disabled completely (might be updated in the future versions of the Nodiflux protocol). Specifically, each of the nodes on the network creates its own Access Point (AP) with a specific SSID and password. Once connected to this AP a connection to a WebSocket running on the ws://192.168.4.1/ws can be established. Analogously, to the BLE communication all of the messages must be prefixed by a 6 byte MAC-address of the destination node and all incoming messages are as well prefixed by the MAC-address of the sender node.
The current implementation of the protocol has only been tested using ESP-32S NodeMCU nodes. In theory, any device supporting ESP-NOW protocol and having enough RAM and flash memory can be used as a node in the Nodiflux network.
In order to further develop or flash devices with the Nodiflux protocol following software is required:
- GCC++17
- GNU Make
- PlatformIO Core (CLI)
- Doxygen (optional, needed for documentation generation)
The config.hpp file shall be used to fine-tune the hyper-parameters of the protocol (e.g. DIS_BROADCAST_SPEED or NODE_REGISTRY_PRUNING_INTERVAL). This file must be modified before flashing the nodes.
This file must be created in the ./include/ folder and must contain the following:
#ifndef ENV
#define ENV
// WARNING: Please make sure to keep those secret
#define WSS_SSID_AP "ESP-NODE-A"
#define WSS_PASSWORD_AP "12345678"
#endif // !ENV
This files is used to define the name and the password for the Access Point (AP) where the WebSocket would be hosted, so that interaction with a mobile application can be established.
The whole project can be compiled using the Makefile command make prun (or a direct PlatformIO CLI command pio run -e nodemcu-32s).
A node of the Nodiflux can be flashed by attaching it to the computer and running the command make pup (or a direct PlatformIO CLI command pio run -e nodemcu-32s -t upload)
Some parts of the protocol were inspired by the following paper:
Arregui Almeida, D., Chafla Altamirano, J., Román Cañizares, M., Játiva, P. P., Guaña-Moya, J., & Sánchez, I. (2025). Gateway-Free LoRa Mesh on ESP32: Design, Self-Healing Mechanisms, and Empirical Performance. Sensors, 25(19), 6036. https://doi.org/10.3390/s25196036
