Skip to content

Latest commit

 

History

989 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HERMES Documentation

Overview

HERMES(Heterogeneous Application-Enabled Routing Middleware for Edge-IoT Systems) is a library built for ESP8266, ESP32, and Raspberry Pi based devices and is composed of two principal components. At its base, HERMES implements a multi-hop network on top of Wi-Fi. The library autonomously builds and manages a self-constructed tree topology, handling all aspects of routing within the network.

On top of this core network lies the second component, the middleware layer, which allows the application layer to define custom metrics and policies that tailor network behaviour to specific objectives. This enables IoT applications to customise the network layer according to their goals and requirements.

For example, in edge computing applications, information about which nodes have higher processing capacity or are currently available for computation resides at the application layer. This information directly affects how data should be handled, since packets are required to be processed by nodes with these characteristics. HERMES enables this application-level knowledge to be taken into account during network formation and packet forwarding, for example by routing packets through intermediate processing nodes before they reach their final destination.

SystemArchitecture

Table of Contents

  1. How to Run the Project
  2. Code Structure
  3. Routing Protocol Documentation
  4. Node Lifecycle (State Machine)
  5. Logging
  6. CLI
  7. Visualization Program
  8. Unit Testing

How to Run the Project

The project was developed and tested using CLion IDE version 2024.1.6 together with PlatformIO version 6.1.18. However, the use of CLion is not mandatory. The project can also be built and uploaded using other IDEs, such as Visual Studio Code, by installing the PlatformIO plugin. Once the code is opened in the IDE and the PlatformIO environment is correctly configured, simply upload the firmware to the target microcontroller. No additional steps are required and the project should run as expected.

The Network class is the main interface exposed by HERMES and is used to integrate a device into the network.
The following example illustrates the basic steps required to initialise a node and integrate it into the network.

#include "network.h"

Network network;

void setup() {
    network.setAsRoot(true);    // Set to true if this node is the root
    network.init();             // Initialize network parameters
    network.begin();            // Join and integrate the node into the network
}

void loop() {
    network.run();              // Must be called continuously
}

All methods available in the Network class are detailed in the table below.

Function Description
Network Basics
setAsRoot(bool isRoot) Configures the node as a root or non-root node. Must be called before begin().
init() Initializes node parameters such as IP configuration, Wi-Fi interfaces, and transport layer setup.
begin() Integrates the node into the HERMES network and starts network operation.
run() Runs the network logic and must be called repeatedly inside the main loop.
stop() Terminates the node’s connection to the network.
Callbacks
onDataReceived(callback) Registers a callback to handle incoming application-layer messages.
onPeriodicAppTask(callback) Registers a periodic application-level task.
onNetworkJoin(callback) Triggered when the node successfully joins the network.
onChildConnect(callback) Triggered when a new child node connects.
Network Information
getHopDistanceToNode(uint8_t*nodeIP) Returns the number of hops required to reach a specific node.
getHopDistanceToRoot() Returns the number of hops required to reach the root node.
getNumberOfChildren() Returns the number of direct child nodes.
getNodeMAC(uint8_t *MAC) Fills the provided array with the node’s MAC address.
getNodeIP(uint8_t *IP) Fills the provided array with the current node’s IP address.
getParentIP(uint8_t *IP) Fills the provided array with the IP address of the node’s parent.
getRootIP(uint8_t *IP) Fills the provided array with the IP address of the root node.
Message Delivery
sendMessageToRoot(char* messageBuffer,size_t bufferSize,const char* messagePayload) Sends a message to the root node. The message is prepared in messageBuffer with the provided payload.
sendMessageToParent(char* messageBuffer,size_t bufferSize,const char* messagePayload) Sends a message to the parent node.
sendMessageToChildren(char* messageBuffer,size_t bufferSize,const char* messagePayload) Sends a message to all direct child nodes.
sendMessageToNode(char* messageBuffer,size_t bufferSize,const char* messagePayload, uint8_t* nodeIP) Sends a message to a specific node.
broadcastMessage(char* messageBuffer,size_t bufferSize,const char* messagePayload) Sends a message to all nodes in the network.
encodeDataMessage(char* encodeBuffer,size_t bufferSize,const char* messagePayload, uint8_t *destinationIP) Encodes a data message with the provided payload addressed to the specified recipient.
Middleware
getActiveMiddlewareStrategy() Returns the currently active middleware strategy.
middlewareSelectStrategy(StrategyType strategyType) Sets the middleware strategy based on the specified strategyType.
middlewarePrintInfo() Prints information about the active middleware strategy.
Middleware: Inject Strategy
initMiddlewareStrategyInject( void *metricStruct, size_t metricStructSize, void (*setValueFunction)(void*,void*), void (*encodeMetricFunction)(char*,size_t,void *), void(*decodeMetricFunction)(char*,void *), int(*compareMetricsFunction)(void*,void*), void (*printMetricStruct)(TableEntry*) ) Initializes the Inject Strategy with required structures and parameters.
injectMetric(void*metric) Injects a custom metric used to influence path selection.
influenceRoutingStrategyInject(char* messageEncodeBuffer,size_t encodeBufferSize,char* dataMessagePayload, uint8_t *destinationIP) Applies the Inject strategy’s routing influence when preparing a message for a specific destination.
isDataMessageEncapsulated(char* dataMessage) Checks whether a data message is encapsulated inside another data message.
parseDataMessage(char*dataMessage,uint8_t* senderIP,uint8_t*destinationIP,char*payload,size_t payloadSize) Extracts sender, destination, and payload information from an encapsulated data message.
Middleware: Publish and Subscribe Strategy
initMiddlewareStrategyPubSub( void (*decodeTopicFunction)(char*,int8_t *)) Initializes the Publish/Subscribe Strategy with required parameters.
influenceRoutingStrategyPubSub(char* messageEncodeBuffer,size_t encodeBufferSize,char* dataMessagePayload) Influences routing according to the Pub/Sub Strategy.
subscribeToTopic(int8_t topic) Subscribed to a specif topic.
unsubscribeToTopic(int8_t topic) Removes the subscription for a specific topic.
advertiseTopic(int8_t topic) Makes a node a publisher for a specific topic.
unadvertiseTopic(int8_t topic) Stops advertising a topic, ceasing publication for that topic.
subscribeAndPublishTopics(int8_t *subscribeList, int subCount, int8_t *publishList, int pubCount) Registers multiple topics for subscription and publication simultaneously.
Middleware: Topology Strategy
initMiddlewareStrategyTopology( void *topologyMetricValues, size_t topologyMetricStructSize, void (*setValueFunction)(void*,void*),void (*encodeTopologyMetricFunction)(char*,size_t,void *), void (*decodeTopologyMetricFunction)(char*,void *), void (*printMetricFunction)(TableEntry*), uint8_t * (*selectParentFunction)(uint8_t *, uint8_t (*)[4], uint8_t) ) Initializes the Topology Strategy with required structures and parameters.
setParentMetric(void*metric) Sets a custom metric for the node.
getParentMetric(uint8_t *nodeIP) Retrieves the metric associated with a given node.
getParentNode(uint8_t *nodeIP,uint8_t *parentIP) Retrieves the IP address of a specified node's parent.

Code Structure

The library is organised in a modular way to clearly separate responsibilities and simplify extension and maintenance.
The main entry point for users of the library is the network class, defined in network.h and implemented in network.cpp. This class represents the core interface through which an application integrates a node into the HERMES network.
Users interact exclusively with this class to initialise the network, manage node behaviour, and exchange data.

The internal structure of the library is organised as follows:

examples/                     # Usage examples and demos
src/
├── core/                     # Internal network implementation
│   ├── circular_buffer/      # Circular buffer implementation
│   ├── cli/                  # Command-line interface for debugging and control
│   ├── ip_tools/             # IP address utilities
│   ├── lifecycle/            # Node lifecycle management
│   ├── logger/               # Logging utilities
│   ├── middleware/           # Middleware Layer implementation
│   ├── network_monitoring/   # Network state and performance monitoring
│   ├── routing/              # Routing logic
│   ├── state_machine/        # State machine implementation
│   ├── table/                # Custom table implementation
│   ├── time_hal/             # Time abstraction layer
│   ├── transport_hal/        # Transport abstraction layer
│   └── wifi_hal/             # Wi-Fi hardware abstraction layer
├── network.cpp               # Network class implementation
└── network.h                 # Network class interface

The core directory contains the internal building blocks of the system.
Each submodule is responsible for a specific aspect of the network, such as routing, lifecycle management, middleware logic, transport abstraction, and hardware-specific functionality.
These components are orchestrated internally by the network class, which abstracts the underlying complexity from the application layer.

Routing Protocol Documentation

Routing Table

Each node maintains a routing table that includes all nodes in the network (including itself), the next hop IP address to reach each node, and the hop distance to that node. When a packet arrives at a node, it is forwarded to the next hop IP specified in the corresponding routing table entry.

Note: All IPs in the routing tables are AP IPs

Children Table

Each node also maintains a table that maps the AP IP address of each of its child nodes to their corresponding STA IP address. This is necessary because when the next hop for message forwarding is one of the node’s children, the STA IP must be used. Since the child is connected as a station within the parent’s network, it receives messages through its STA interface.

Messages

Managing a Network

Parent Node Selection Process

When a new node joins the network, it selects its parent based on two main criteria. First, it chooses the parent with the lowest hop count to the root node. If multiple potential parents share the same hop count, the node then selects the one with the fewest children, promoting a more balanced and evenly distributed network structure.

Root Node

The root node is chosen manually by the user.

Parent Node Failure Handling Procedure

LostParent

Child Node Disconnection Handling Procedure

Node Lifecycle (State Machine)

StateMachine

Logging

To facilitate debugging across different architectures (ESP devices, native environments, etc.), a unified logging module was developed. Since printing functions vary between architectures (e.g., Serial.printf for ESPs vs. printf for native platforms), this module abstracts logging to ensure consistency.

Log Categories

The logging system is divided into different log categories, each corresponding to a specific aspect of the system:

  • NETWORK – Logs related to network events (e.g., new child node detected, successful network join, AP IP established).
  • MESSAGES – Logs for sent and received messages.
  • STATE_MACHINE – Logs tracking state transitions in the node's state machine.
  • MONITORING_SERVER – Logs related to communication with the visualization server.

By default, all logging modules are disabled. To activate a specific logging module, call passing the desired module as an argument:

enableModule(LogModules module);

Log Levels

In addition to categories, logs are classified by severity levels:

  • DEBUG – Detailed logs for development and debugging (e.g., variable values).
  • INFO – General informative logs (e.g., network state, IP addresses, number of children).
  • ERROR – Critical errors that should not occur (e.g., invalid memory accesses).

To set the current log level to debug, use the following line:

currentLogLevel = DEBUG;

Example Usage

To log an informational network event, use the LOG:

LOG(NETWORK, INFO, "My STA IP: %s; Gateway: %s\n", getMySTAIP().toString().c_str(), getGatewayIP().toString().c_str());

CLI

For debugging, development, and monitoring purposes, a Command Line Interface (CLI) has been implemented. To enter the CLI, simply press "Enter" in the serial monitor of the node you wish to monitor. Upon entering, a menu will be displayed with various options, such as visualizing the node’s routing and children tables or sending messages to other nodes within the network.

Note: When in CLI mode, the node becomes "locked" in this mode and will not respond to or receive any network messages.

Tip: In PlatformIO, to view the words you type in the serial monitor, press [CTRL] + [T] followed by [CTRL] + [E].

Visualization Program

A network visualization program was implemented in Python to provide real-time monitoring of the network topology. The program communicates with the root node via the serial monitor, reading and writing data to exchange information.

When the visualization program is active, nodes report all network changes to the root, including new nodes joining, nodes leaving, and parent changes. The root then relays this information to the visualization program, allowing for a dynamic, real-time representation of the network structure.

Network Topology Example

Visualization program GitHub: Network Visualization Program

Unit Testing

Several unit tests were developed to validate the libraries before testing the code on physical devices. These tests are currently executed on a computer, meaning there are no unit tests running directly on the boards. The following tests were implemented:

  • test_circular_buffer – Verifies the correct functionality of the circular buffer, including inserting, deleting, and handling priority elements in the queue.
  • test_lifecycle – Tests the parent selection function under various scenarios.
  • test_logger – Ensures the logger module operates correctly on the computer, testing the variable argument handling (similar to printf).
  • test_messages – Validates the encoding and decoding of different types of messages.
  • test_routing_table – Evaluates multiple functionalities of the routing table. It verifies IP comparison logic, ensures correct printing of the routing and children tables, and tests adding, removing, and updating node entries. The test also checks table cleanup, correct pathfinding to different nodes (child, parent, or other network nodes), and handling invalid nodes. Additionally, it validates the initialization of the routing table on a new node through full routing updates and ensures partial updates modify the table correctly.
  • test_state_machine – Ensures the state machine transitions correctly between states, considering the event queue.
  • test_table – Tests the table implementation with different structure types used as keys and values (e.g., an IP address, structs).
  • test_table_prealloc – Tests the table implementation using preallocated variables (e.g., global variables), which is useful for embedded systems.

These unit tests also serve as examples of how to use each implemented library.

About

HERMES (Heterogeneous Application-Enabled Routing Middleware for Edge-IoT Systems) is a networking library for ESP8266, ESP32, and Raspberry Pi devices that enables self-organising multi-hop Wi-Fi networks. It combines routing with a middleware layer that allows applications to influence network behavior through custom metrics and policies.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages