# AlteriomPainlessMesh User Guide > **Complete guide to building mesh networks with ESP8266/ESP32 devices** This comprehensive guide covers everything you need to build production-ready mesh networks using the AlteriomPainlessMesh library. ## Table of Contents 1. [Introduction](#introduction) 2. [Getting Started](#getting-started) 3. [Core Concepts](#core-concepts) 4. [API Reference](#api-reference) 5. [Alteriom Extensions](#alteriom-extensions) 6. [Bridge to Internet](#bridge-to-internet) 7. [Advanced Features](#advanced-features) 8. [Troubleshooting](#troubleshooting) 9. [Examples](#examples) --- ## Introduction ### What is AlteriomPainlessMesh? **AlteriomPainlessMesh** is a user-friendly library for creating self-organizing mesh networks with ESP8266 and ESP32 devices. This **Alteriom fork** extends the original painlessMesh library with specialized packages for IoT sensor networks, device control, and status monitoring. ### Key Features **Core Features:** - **Automatic Mesh Formation** - Nodes discover and connect automatically - **Self-Healing Network** - Adapts when nodes join/leave - **Time Synchronization** - Coordinated actions across all nodes - **Smart Routing** - Broadcast, point-to-point, and neighbor messaging - **Plugin System** - Type-safe custom message packages - **ESP32 & ESP8266 Support** - Full support for both platforms - **Memory Efficient** - Optimized for resource-constrained devices **Alteriom Extensions:** - **Broadcast OTA Distribution** - 98% network traffic reduction for large meshes - **MQTT Bridge** - Professional monitoring with Grafana/InfluxDB/Prometheus - **Structured Packages** - SensorPackage, CommandPackage, StatusPackage - **Bridge Failover** - Automatic high-availability for critical systems - **Message Queue** - Zero data loss during Internet outages ### Why Use painlessMesh? **True Ad-Hoc Networking** - No planning, central controller, or router required - Any system of 1 or more nodes will self-organize - Maximum mesh size limited only by available memory **JSON-Based Messaging** - Human-readable and easy to understand - Simple integration with web applications - Straightforward debugging and development **Production Ready** - Tested in industrial deployments - Enterprise-grade stability and performance - Comprehensive error handling and recovery --- ## Getting Started ### What You'll Need - **Hardware**: 2 or more ESP8266 or ESP32 development boards - **Software**: Arduino IDE or PlatformIO - **Accessories**: USB cables for programming ### Installation #### Arduino Library Manager Search for **"Alteriom PainlessMesh"** in **Tools → Manage Libraries...** and install it with the dependencies it offers; also install AsyncTCP (ESP32) or ESPAsyncTCP (ESP8266). The index follows a GitHub release within a day. **Manual installation** (a release ZIP, or a clone to follow a branch): 1. Go to [Releases](https://github.com/Alteriom/painlessMesh/releases/latest) 2. Download the latest release ZIP file 3. In Arduino IDE: **Sketch** → **Include Library** → **Add .ZIP Library...** 4. Select the downloaded ZIP file 5. Restart Arduino IDE **Or via Git Clone:** ```bash cd ~/Arduino/libraries/ git clone https://github.com/Alteriom/painlessMesh.git AlteriomPainlessMesh # Restart Arduino IDE ``` #### PlatformIO Add to your `platformio.ini` (the library is `sparck75/AlteriomPainlessMesh` on the PlatformIO registry): ```ini lib_deps = sparck75/AlteriomPainlessMesh@^2.0.0 ``` To follow the integration branch instead of a release, use the repository URL: `https://github.com/Alteriom/painlessMesh.git#Feat/next-release`. #### Dependencies painlessMesh requires these libraries (auto-installed with PlatformIO): - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) v7.x - [TaskScheduler](https://github.com/arkhipenko/TaskScheduler) v4.x - [ESPAsyncTCP](https://github.com/ESP32Async/ESPAsyncTCP) (ESP8266) v2.x - [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) (ESP32) v3.4.7 or later - [PubSubClient](https://github.com/knolleary/pubsubclient) is only needed by the `mqttBridge` example Arduino cores: ESP32 core 2.0.x or 3.x (the ESP32-C5 and ESP32-C6 need 3.x); ESP8266 core 3.x. ### Your First Mesh Network #### Step 1: Basic Example Code Copy this code to your Arduino IDE: ```cpp #include "painlessMesh.h" #define MESH_PREFIX "MyMeshNetwork" #define MESH_PASSWORD "somethingSneaky" #define MESH_PORT 5555 Scheduler userScheduler; painlessMesh mesh; // Task to send messages Task taskSendMessage(TASK_SECOND * 1, TASK_FOREVER, &sendMessage); void sendMessage() { String msg = "Hello from node "; msg += mesh.getNodeId(); mesh.sendBroadcast(msg); taskSendMessage.setInterval(random(TASK_SECOND * 1, TASK_SECOND * 5)); } void receivedCallback(uint32_t from, String &msg) { Serial.printf("Received from %u: %s\n", from, msg.c_str()); } void newConnectionCallback(uint32_t nodeId) { Serial.printf("New Connection, nodeId = %u\n", nodeId); } void changedConnectionCallback() { Serial.printf("Changed connections\n"); } void nodeTimeAdjustedCallback(int32_t offset) { Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset); } void setup() { Serial.begin(115200); mesh.setDebugMsgTypes(ERROR | STARTUP); mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); mesh.onReceive(&receivedCallback); mesh.onNewConnection(&newConnectionCallback); mesh.onChangedConnections(&changedConnectionCallback); mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback); userScheduler.addTask(taskSendMessage); taskSendMessage.enable(); } void loop() { mesh.update(); } ``` #### Step 2: Upload and Test 1. Upload the code to your first ESP device 2. Open Serial Monitor (115200 baud) 3. Upload the same code to your second device 4. Watch them automatically discover each other! #### Expected Output ``` New Connection, nodeId = 123456789 Received from 123456789: Hello from node 123456789 Changed connections Adjusted time 1234567. Offset = 12 ``` --- ## Core Concepts ### Mesh Networking Basics **What is a Mesh Network?** A mesh network is a network topology where each node can relay data for other nodes. Unlike traditional star or tree topologies with a central hub, mesh networks are decentralized and self-organizing. **How painlessMesh Works:** 1. **Node Discovery**: Nodes scan for other nodes broadcasting the same MESH_PREFIX 2. **Automatic Connection**: Nodes connect to the strongest available signals 3. **Route Building**: Each node maintains a routing table of the network topology 4. **Message Routing**: Messages are automatically routed through intermediate nodes 5. **Self-Healing**: When nodes disconnect, routes automatically reconfigure ### Node Identification **Node ID:** - Each ESP device has a unique 32-bit chip ID - Retrieved automatically using `system_get_chip_id()` - Used for targeted messaging: `mesh.sendSingle(nodeId, msg)` **Getting Your Node ID:** ```cpp uint32_t myNodeId = mesh.getNodeId(); Serial.printf("My Node ID: %u\n", myNodeId); ``` ### Message Types **Broadcast Messages:** - Sent to all nodes in the mesh - Propagate through the entire network - Use for sensor data, status updates, alerts ```cpp mesh.sendBroadcast("Hello everyone!"); ``` **Single (Targeted) Messages:** - Sent to a specific node by ID - Automatically routed through the mesh - Use for commands, replies, private data ```cpp uint32_t targetNode = 123456789; mesh.sendSingle(targetNode, "Hello specific node!"); ``` ### Time Synchronization **Mesh Time:** - All nodes maintain a synchronized timebase - Uses SNTP-based protocol for synchronization - Returns microseconds since mesh startup - Rolls over every 71 minutes ```cpp uint32_t meshTime = mesh.getNodeTime(); Serial.printf("Mesh time: %u µs\n", meshTime); ``` **Use Cases:** - Coordinated LED displays - Synchronized sensor readings - Event scheduling across nodes - Timestamp correlation ### Network Topology **Getting Node List:** ```cpp std::list nodes = mesh.getNodeList(); Serial.printf("Connected nodes: %d\n", nodes.size()); for (auto nodeId : nodes) { Serial.printf(" - Node: %u\n", nodeId); } ``` **Topology as JSON:** ```cpp String topology = mesh.subConnectionJson(); Serial.println(topology); ``` **Checking Connections:** ```cpp uint32_t nodeId = 123456789; if (mesh.isConnected(nodeId)) { Serial.println("Node is connected"); } ``` **Note:** Connection status can change rapidly in dynamic mesh networks. This method reflects the current state at the moment of the call. --- ## API Reference ### painlessMesh Class #### Initialization **`init()`** - Initialize the mesh network ```cpp void init(String ssid, String password, uint16_t port = 5555, WiFiMode_t connectMode = WIFI_AP_STA, uint8_t channel = 1, uint8_t hidden = 0, uint8_t maxconn = MAX_CONN, String stationSSID = "", String stationPassword = ""); void init(String ssid, String password, Scheduler* scheduler, uint16_t port = 5555, WiFiMode_t connectMode = WIFI_AP_STA, uint8_t channel = 1, uint8_t hidden = 0, uint8_t maxconn = MAX_CONN, String stationSSID = "", String stationPassword = ""); ``` **Parameters:** - `ssid` - Network name (same for all nodes) - `password` - Network password - `scheduler` - Your TaskScheduler instance; without one the library creates its own - `port` - TCP port for mesh communication (default: 5555) - `connectMode` - `WIFI_AP_STA` (default), `WIFI_STA` for a leaf that accepts no children, or `WIFI_AP` - `channel` - WiFi channel (1-13), or 0 to scan for the mesh and join it on whatever channel it is on - `hidden` - Hide the mesh SSID - `maxconn` - Children this node's AP accepts (0 makes it a leaf; see the ESP8266 note in the README) - `stationSSID`, `stationPassword` - Also connect the station to a router (a bridge); `initAsBridge()` is the usual way **Example:** ```cpp Scheduler userScheduler; mesh.init("MyMesh", "password123", &userScheduler, 5555); ``` **`stop()`** - Stop the node ```cpp void stop(); ``` Disconnects from all nodes and stops sending/receiving messages. **`update()`** - Main mesh update loop ```cpp void update(); ``` Call this in your `loop()` function. Handles all mesh maintenance tasks. ```cpp void loop() { mesh.update(); // Required } ``` #### Message Sending **`sendBroadcast()`** - Send to all nodes ```cpp bool sendBroadcast(String &msg, bool includeSelf = false); ``` **Parameters:** - `msg` - Message to broadcast - `includeSelf` - Send to yourself (default: false) **Returns:** `true` if successful **`sendSingle()`** - Send to specific node ```cpp bool sendSingle(uint32_t dest, String &msg); ``` **Parameters:** - `dest` - Destination node ID - `msg` - Message to send **Returns:** `true` if successful **Delivery confirmation (v2.0.0+)** - Both send functions accept an optional delivery callback for reliable communication: ```cpp bool sendSingle(uint32_t dest, String &msg, painlessmesh::ack::deliveryCallback_t ackCallback, uint32_t ackTimeoutMs = 5000); bool sendBroadcast(String &msg, bool includeSelf, painlessmesh::ack::deliveryCallback_t ackCallback, uint32_t ackTimeoutMs = 5000); ``` When a callback is given, the receiving node automatically acknowledges the message and the callback fires with `delivered = true` and the round-trip latency, or `delivered = false` after the timeout. For broadcasts the callback fires once per node in the mesh. See the `reliableSensorLogging` and `commandControl` examples: ```cpp mesh.sendSingle(gatewayId, msg, [](uint32_t nodeId, bool delivered, uint32_t latencyMs) { if (delivered) Serial.printf("Node %u confirmed in %u ms\n", nodeId, latencyMs); else Serial.printf("Delivery to %u timed out\n", nodeId); }); ``` Use `mesh.pendingAcks()` to see how many messages are still unconfirmed and `mesh.checkAcks()` to poll timeouts manually (they are also processed inside `mesh.update()`). At most `PAINLESSMESH_MAX_PENDING_ACKS` (default 32) messages can await confirmation at once — beyond that the send returns `false` and nothing is sent. To combine a priority level with a delivery callback in one call, pass a `painlessmesh::SendOptions` struct instead of using the separate overloads: ```cpp painlessmesh::SendOptions options; options.priority = painlessmesh::protocol::PRIORITY_HIGH; options.ackCallback = [](uint32_t nodeId, bool delivered, uint32_t latencyMs) { // ... }; mesh.sendSingle(targetNode, msg, options); mesh.sendBroadcast(msg, options); // includeSelf as optional third argument ``` Since v2.0.0 the priority level is carried on the wire, so intermediate nodes forward a prioritized message at the sender's priority instead of dropping it to normal after the first hop. #### Callbacks **`onReceive()`** - Message received callback ```cpp void onReceive(receivedCallback_t callback); ``` **Callback signature:** ```cpp void receivedCallback(uint32_t from, String &msg) { Serial.printf("From %u: %s\n", from, msg.c_str()); } ``` **`onNewConnection()`** - New node connected ```cpp void onNewConnection(newConnectionCallback_t callback); ``` **Callback signature:** ```cpp void newConnectionCallback(uint32_t nodeId) { Serial.printf("New node: %u\n", nodeId); } ``` **`onChangedConnections()`** - Topology changed ```cpp void onChangedConnections(changedConnectionsCallback_t callback); ``` **Callback signature:** ```cpp void changedConnectionCallback() { Serial.println("Network topology changed"); } ``` **`onNodeTimeAdjusted()`** - Time synchronized ```cpp void onNodeTimeAdjusted(nodeTimeAdjustedCallback_t callback); ``` **Callback signature:** ```cpp void nodeTimeAdjustedCallback(int32_t offset) { Serial.printf("Time adjusted: %d µs\n", offset); } ``` **`onNodeDelayReceived()`** - Delay measurement response ```cpp void onNodeDelayReceived(nodeDelayCallback_t callback); ``` **Callback signature:** ```cpp void onDelayReceived(uint32_t nodeId, int32_t delay) { Serial.printf("Delay to %u: %d µs\n", nodeId, delay); } ``` #### Network Information **`getNodeId()`** - Get this node's ID ```cpp uint32_t getNodeId(); ``` **`getNodeList()`** - Get list of all known nodes ```cpp std::list getNodeList(); ``` **`getNodeTime()`** - Get mesh timebase (microseconds) ```cpp uint32_t getNodeTime(); ``` **`isConnected()`** - Check if node is connected ```cpp bool isConnected(uint32_t nodeId); ``` **`subConnectionJson()`** - Get topology as JSON ```cpp String subConnectionJson(); ``` #### Network Measurement **`startDelayMeas()`** - Measure network delay ```cpp bool startDelayMeas(uint32_t nodeId); ``` Sends a packet to measure round-trip delay. Result delivered via `onNodeDelayReceived()` callback. #### Debug Configuration **`setDebugMsgTypes()`** - Configure debug output ```cpp void setDebugMsgTypes(uint16_t types); ``` **Debug types:** - `ERROR` - Error messages - `STARTUP` - Startup information - `CONNECTION` - Connection events - `SYNC` - Time synchronization - `COMMUNICATION` - Message routing - `GENERAL` - General information - `MSG_TYPES` - Message type details - `REMOTE` - Remote node information **Example:** ```cpp mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION); ``` #### TCP Retry Configuration **`setTcpRetryConfig()`** / **`getTcpRetryConfig()`** - Tune how a node retries a failed TCP connection before falling back to a full WiFi reconnect. ```cpp void setTcpRetryConfig(const painlessmesh::tcp::TcpRetryConfig& config); painlessmesh::tcp::TcpRetryConfig getTcpRetryConfig() const; ``` | Field | Default | Meaning | |---|---|---| | `maxRetries` | 5 | TCP connect attempts after the first before giving up | | `retryDelayMs` | 1000 | Base delay between retries; scaled 1x, 2x, 4x, 8x, 8x | | `stabilizationDelayMs` | 500 | Wait after IP acquisition before the first attempt | | `exhaustionReconnectDelayMs` | 10000 | Wait before the WiFi reconnect after exhaustion | | `failureBlockDurationMs` | 60000 | How long a failed peer is skipped during AP selection | The defaults reproduce the previous hardcoded behaviour exactly, so existing sketches see no change. Call this **before** `mesh.init()`. **Example** (real-time profile — fail fast and re-scan rather than stall): ```cpp painlessmesh::tcp::TcpRetryConfig cfg; // starts at the defaults cfg.maxRetries = 1; cfg.retryDelayMs = 200; cfg.exhaustionReconnectDelayMs = 1000; mesh.setTcpRetryConfig(cfg); mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); ``` `maxRetries` is capped at 10 and `retryDelayMs` is held between 50 ms and 60000 ms; use `getTcpRetryConfig()` to read back what actually took effect. See `examples/tcpRetryConfig/` for real-time, high-reliability and battery-saver profiles, and the API docs for the full tuning guidance. > **Note:** these production defaults prevent connection churn and rapid > reconnect loops. Tuning them down can reintroduce mesh instability. --- ## Alteriom Extensions ### Overview Alteriom extensions provide production-ready, type-safe packages for common IoT scenarios: - **Environmental Monitoring** - Temperature, humidity, pressure sensors - **Device Control** - Commands for actuators, displays, relays - **Health Monitoring** - Device status, diagnostics, telemetry - **Type Safety** - Compile-time validation - **Production Ready** - Tested and optimized implementations ### SensorPackage (Type 200) For broadcasting environmental sensor data. **Fields:** - `temperature` (double) - Temperature in °C - `humidity` (double) - Relative humidity (%) - `pressure` (double) - Atmospheric pressure (hPa) - `sensorId` (uint32_t) - Sensor identifier - `timestamp` (uint32_t) - Measurement time (mesh time) - `batteryLevel` (uint8_t) - Battery percentage (0-100) **Example:** ```cpp #include "examples/alteriom/alteriom_sensor_package.hpp" using namespace alteriom; SensorPackage sensor; sensor.from = mesh.getNodeId(); sensor.temperature = 23.5; sensor.humidity = 65.0; sensor.pressure = 1013.25; sensor.sensorId = 1001; sensor.timestamp = mesh.getNodeTime(); sensor.batteryLevel = 85; String json = sensor.toJsonString(); mesh.sendBroadcast(json); ``` **Receiving:** ```cpp void receivedCallback(uint32_t from, String &msg) { JsonDocument doc; deserializeJson(doc, msg); if (doc["type"] == 200) { SensorPackage sensor(doc.as()); Serial.printf("Sensor %u: %.1f°C, %.1f%% RH, %.1f hPa\n", sensor.sensorId, sensor.temperature, sensor.humidity, sensor.pressure); } } ``` **Use Cases:** - Weather stations - Environmental monitoring - HVAC feedback - Greenhouse automation - Industrial sensors ### CommandPackage (Type 400) For sending control commands to specific devices. **Fields:** - `command` (uint8_t) - Command type - `targetDevice` (uint32_t) - Device identifier - `parameters` (String) - JSON parameters - `commandId` (uint32_t) - Unique command ID **Example:** ```cpp CommandPackage cmd; cmd.dest = targetNodeId; cmd.from = mesh.getNodeId(); cmd.command = 1; // LED_CONTROL cmd.targetDevice = 100; cmd.parameters = "{\"brightness\":75,\"color\":\"blue\"}"; cmd.commandId = generateCommandId(); String json = cmd.toJsonString(); mesh.sendSingle(targetNodeId, json); ``` **Handling Commands:** ```cpp void receivedCallback(uint32_t from, String &msg) { JsonDocument doc; deserializeJson(doc, msg); if (doc["type"] == 400) { CommandPackage cmd(doc.as()); if (cmd.dest != mesh.getNodeId()) return; // Parse parameters JsonDocument params; deserializeJson(params, cmd.parameters); int brightness = params["brightness"]; String color = params["color"]; // Execute command setLED(cmd.targetDevice, brightness, color); } } ``` **Use Cases:** - Remote device control - Actuator management - Display updates - System configuration - Automation triggers ### StatusPackage (Type 202) For broadcasting device health and operational status. **Fields:** - `deviceStatus` (uint8_t) - Status flags - `uptime` (uint32_t) - Uptime in seconds - `freeMemory` (uint16_t) - Free heap memory (bytes) - `wifiStrength` (uint8_t) - WiFi RSSI (dBm) - `firmwareVersion` (String) - Firmware version **Example:** ```cpp StatusPackage status; status.from = mesh.getNodeId(); status.deviceStatus = 1; // OPERATIONAL status.uptime = millis() / 1000; status.freeMemory = ESP.getFreeHeap(); status.wifiStrength = WiFi.RSSI(); status.firmwareVersion = "2.0.0"; String json = status.toJsonString(); mesh.sendBroadcast(json); ``` **Use Cases:** - System monitoring - Predictive maintenance - Network diagnostics - Performance tracking - Remote troubleshooting ### Advanced Packages **MetricsPackage (Type 204)** - Comprehensive performance metrics - CPU usage, memory health, network throughput - Response time and latency tracking - Connection quality monitoring **HealthCheckPackage (Type 605)** - Proactive problem detection - Overall health scoring (0-100) - Problem flag indicators - Memory leak detection - Predictive maintenance **Bridge Packages:** - **BridgeStatusPackage (Type 610)** - Bridge health monitoring - **BridgeElectionPackage (Type 611)** - Automatic failover election - **BridgeTakeoverPackage (Type 612)** - Bridge role announcement - **NTPTimeSyncPackage (Type 614)** - NTP time synchronization See README.md for complete package documentation. --- ## Bridge to Internet ### Overview Connect your mesh network to the Internet by creating a bridge node that connects to both the mesh and your WiFi router. ### Quick Start (Auto Channel Detection) The recommended approach uses `initAsBridge()` which automatically detects your router's channel: ```cpp #include "painlessMesh.h" #define MESH_PREFIX "MyMeshNetwork" #define MESH_PASSWORD "somethingSneaky" #define ROUTER_SSID "YourRouterSSID" #define ROUTER_PASSWORD "YourRouterPassword" Scheduler userScheduler; painlessMesh mesh; void setup() { Serial.begin(115200); // Automatically detects router channel and configures mesh mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD, ROUTER_SSID, ROUTER_PASSWORD, &userScheduler, 5555); mesh.onReceive(&receivedCallback); } void loop() { mesh.update(); } void receivedCallback(uint32_t from, String &msg) { Serial.printf("Mesh message from %u: %s\n", from, msg.c_str()); // Forward to Internet services (HTTP, MQTT, etc.) } ``` ### Bridge Failover Automatic high-availability for critical systems: **Features:** - RSSI-based election (best router signal wins; ties go to uptime, free memory, node ID) - A bridge that stops cleanly (`mesh.stop()`, or a sketch that stops the mesh before rebooting) announces it is leaving, and the candidates elect within seconds - A bridge that loses power is noticed when its last status ages out: the bridge timeout (60 s) plus up to one 30 s monitor tick - The elected bridge announces its router channel; peers move to it and treat it as home - One election at a time, a 60 s hold between role changes, and a minimum router RSSI for a lone candidate The first election check comes after a startup period (`setElectionStartupDelay()`, 60 s by default). See [examples/bridge_failover](https://github.com/Alteriom/painlessMesh/blob/main/examples/bridge_failover/README.md) for the whole protocol. **Example:** ```cpp // Enable automatic bridge failover mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD); mesh.enableBridgeFailover(true); mesh.onBridgeRoleChanged(&bridgeRoleCallback); void bridgeRoleCallback(bool isBridge, const String& reason) { if (isBridge) { Serial.printf("🎯 Promoted to bridge: %s\n", reason.c_str()); // Start MQTT connection, etc. } else { Serial.printf("Demoted from bridge: %s\n", reason.c_str()); // Stop bridge services } } ``` ### Shared Gateway Mode All nodes as Internet gateways with automatic failover: **Features:** - Universal Internet access (all nodes connect to router) - Instant failover through mesh relay - No single point of failure - Automatic gateway election - Duplicate prevention **Example:** ```cpp // Initialize all nodes as shared gateways mesh.initAsSharedGateway( MESH_PREFIX, MESH_PASSWORD, ROUTER_SSID, ROUTER_PASSWORD, &userScheduler, MESH_PORT ); // Send data with automatic failover mesh.sendToInternet( "https://api.example.com/data", sensorJson, [](bool success, uint16_t status, String error) { Serial.printf("Delivery: %s\n", success ? "OK" : error.c_str()); } ); ``` For complete bridge documentation, see [BRIDGE_TO_INTERNET.md](Bridge-To-Internet.md). --- ## Advanced Features ### Message Queue Zero data loss during Internet outages: ```cpp // Enable the queue with room for 100 messages (call after mesh.init()) mesh.enableMessageQueue(true, 100); // Queue a message for an Internet endpoint. Returns the queued message's // id, 0 on failure. The queue holds it until you drain it. String criticalAlarm = "{\"sensor\":\"O2\",\"value\":2.5,\"alarm\":true}"; uint32_t id = mesh.queueMessage(criticalAlarm, "https://api.example.com/alarm", PRIORITY_CRITICAL); // Drain it when a gateway with Internet is available: flushMessageQueue() // returns the queued messages, your sketch sends each one and removes it if (mesh.hasInternetConnection()) { for (auto& queued : mesh.flushMessageQueue()) { mesh.sendToInternet(queued.destination, queued.payload, onResult); mesh.removeQueuedMessage(queued.id); } } auto stats = mesh.getQueueStats(); // queued, dropped and flushed counts ``` **Priority Levels** (`PRIORITY_CRITICAL`, `PRIORITY_HIGH`, `PRIORITY_NORMAL`, `PRIORITY_LOW`): - `PRIORITY_CRITICAL` - Never evicted - `PRIORITY_LOW` - Evicted first when the queue is full, then `NORMAL`, then `HIGH` See [examples/priority/priority_with_queue](https://github.com/Alteriom/painlessMesh/tree/main/examples/priority/priority_with_queue). ### Broadcast OTA Efficient firmware distribution for large meshes: ```cpp // Receiver: accept firmware offered for this role, report progress mesh.initOTAReceive("sensor", [](int part, int total) { Serial.printf("OTA %d/%d\n", part, total); }); // Sender: serve the image in parts on request, then offer it to the role mesh.initOTASend( [](painlessmesh::plugin::ota::DataRequest pkg, char* buffer) { // copy part pkg.partNo of the image into buffer, return its length return (size_t)readPart(pkg.partNo, buffer); }, OTA_PART_SIZE); mesh.offerOTA("sensor", "esp32", md5, partCount, false, true /* broadcast */); ``` With the last argument true the image is broadcast once instead of unicast to each node, which is what makes a large mesh affordable. See [examples/otaSender](https://github.com/Alteriom/painlessMesh/tree/main/examples/otaSender) and [examples/otaReceiver](https://github.com/Alteriom/painlessMesh/tree/main/examples/otaReceiver) for a complete pair. ### MQTT Bridge Professional monitoring integration: The library has no MQTT client of its own; the bridge sketch uses [PubSubClient](https://github.com/knolleary/pubsubclient) beside the mesh: ```cpp WiFiClient espClient; PubSubClient mqttClient(espClient); // Publish mesh data mesh.onReceive([](uint32_t from, String &msg) { String topic = "mesh/" + String(from) + "/data"; mqttClient.publish(topic.c_str(), msg.c_str()); }); // Forward commands from mesh//command into the mesh mqttClient.setCallback([](char* topic, byte* payload, unsigned int length) { String t(topic); uint32_t nodeId = t.substring(t.indexOf('/') + 1, t.lastIndexOf('/')).toInt(); String msg; for (unsigned int i = 0; i < length; i++) msg += (char)payload[i]; mesh.sendSingle(nodeId, msg); }); mqttClient.subscribe("mesh/+/command"); ``` See the complete sketch in the [Bridge with MQTT](#bridge-with-mqtt) example below and in [examples/mqttBridge](https://github.com/Alteriom/painlessMesh/tree/main/examples/mqttBridge). ### Multi-Bridge Coordination Enterprise load balancing: ```cpp // Primary bridge (priority 10) mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD, ROUTER_SSID, ROUTER_PASSWORD, &userScheduler, MESH_PORT, 10); // Configure load balancing mesh.setBridgeSelectionStrategy(painlessMesh::ROUND_ROBIN); // Monitor bridge coordination (fires every ~30s per bridge) mesh.onBridgeCoordination( [](const plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode) { Serial.printf("Bridge %u: priority=%d, load=%d%%\n", fromNode, pkg.priority, pkg.load); } ); // Get notified when bridge state changes mesh.onBridgeCoordinationChanged( [](const plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode, TSTRING changeType) { Serial.printf("Bridge %s: %u (role=%s)\n", changeType.c_str(), fromNode, pkg.role.c_str()); } ); ``` **Strategies:** - `PRIORITY_BASED` - Use highest priority bridge - `ROUND_ROBIN` - Distribute load evenly - `BEST_SIGNAL` - Use best RSSI bridge --- ## Troubleshooting ### Common Issues #### Nodes Not Connecting **Symptoms:** Nodes don't discover each other **Solutions:** 1. Verify MESH_PREFIX and MESH_PASSWORD are identical 2. Check devices are within WiFi range (~50-100m) 3. Ensure MESH_PORT is the same on all devices 4. Try different WiFi channels (avoid congested channels) 5. Check for physical obstacles blocking signal #### Messages Not Received **Symptoms:** Messages sent but not received **Solutions:** 1. Check message size (keep under 1KB for reliability) 2. Verify callback is registered: `mesh.onReceive(&callback)` 3. Add debug output: `mesh.setDebugMsgTypes(ERROR | COMMUNICATION)` 4. Ensure `mesh.update()` is called in `loop()` 5. Check for network congestion (reduce message frequency) #### High Memory Usage **Symptoms:** Device crashes or becomes unstable **Solutions:** 1. Monitor free heap: `ESP.getFreeHeap()` 2. Reduce message frequency 3. Limit mesh size (max ~50 nodes for ESP8266) 4. Use shorter messages 5. Clear unused String objects 6. Reduce TaskScheduler task count #### Bridge Connection Issues **Symptoms:** Bridge can't maintain router connection **Solutions:** 1. Use `initAsBridge()` for automatic channel detection 2. Ensure router channel matches mesh channel 3. Check router signal strength (RSSI > -70 dBm) 4. Enable bridge debug: `mesh.setDebugMsgTypes(ERROR | CONNECTION)` 5. Try static IP configuration on router ### Debug Configuration Enable verbose debugging: ```cpp void setup() { Serial.begin(115200); // Enable all debug messages mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION | SYNC | COMMUNICATION | GENERAL); mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); } ``` ### Performance Optimization **Reduce Network Congestion:** ```cpp // Use rate limiting static unsigned long lastBroadcast = 0; const unsigned long MIN_INTERVAL = 1000; // 1 second if (millis() - lastBroadcast > MIN_INTERVAL) { mesh.sendBroadcast(msg); lastBroadcast = millis(); } ``` **Optimize Message Size:** ```cpp // Bad: Large JSON with unnecessary data String msg = "{\"temperature\":23.5,\"humidity\":65.0,\"description\":\"This is a very long description...\"}"; // Good: Compact JSON with essential data String msg = "{\"t\":23.5,\"h\":65}"; ``` ### Best Practices 1. **Avoid `delay()`** - Use TaskScheduler instead 2. **Conservative messaging** - Don't spam the network 3. **Handle failures gracefully** - Messages can be dropped 4. **Monitor memory** - Especially on ESP8266 5. **Use retries for critical messages** - Implement acknowledgments 6. **Test at scale** - Test with expected number of nodes For more troubleshooting help, see: - [Common Issues](https://github.com/Alteriom/painlessMesh/blob/main/docsify-site/troubleshooting/common-issues.md) - [FAQ](https://github.com/Alteriom/painlessMesh/blob/main/docsify-site/troubleshooting/faq.md) - [Bridge Failover troubleshooting](https://github.com/Alteriom/painlessMesh/blob/main/examples/bridge_failover/README.md#troubleshooting) --- ## Examples ### Basic Mesh Network ```cpp #include "painlessMesh.h" #define MESH_PREFIX "MyMesh" #define MESH_PASSWORD "password" #define MESH_PORT 5555 Scheduler userScheduler; painlessMesh mesh; Task taskSendMessage(TASK_SECOND * 5, TASK_FOREVER, [](){ String msg = "Hello from " + String(mesh.getNodeId()); mesh.sendBroadcast(msg); }); void receivedCallback(uint32_t from, String &msg) { Serial.printf("From %u: %s\n", from, msg.c_str()); } void setup() { Serial.begin(115200); mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); mesh.onReceive(&receivedCallback); userScheduler.addTask(taskSendMessage); taskSendMessage.enable(); } void loop() { mesh.update(); } ``` ### Sensor Network with Alteriom Packages ```cpp #include "painlessMesh.h" #include "examples/alteriom/alteriom_sensor_package.hpp" using namespace alteriom; #define MESH_PREFIX "SensorMesh" #define MESH_PASSWORD "password" #define MESH_PORT 5555 Scheduler userScheduler; painlessMesh mesh; // DHT22 sensor pins #define DHTPIN 4 #define DHTTYPE DHT22 DHT dht(DHTPIN, DHTTYPE); Task taskSendSensor(30000, TASK_FOREVER, [](){ SensorPackage sensor; sensor.from = mesh.getNodeId(); sensor.temperature = dht.readTemperature(); sensor.humidity = dht.readHumidity(); sensor.sensorId = mesh.getNodeId(); sensor.timestamp = mesh.getNodeTime(); sensor.batteryLevel = readBattery(); if (!isnan(sensor.temperature) && !isnan(sensor.humidity)) { mesh.sendBroadcast(sensor.toJsonString()); } }); void receivedCallback(uint32_t from, String &msg) { JsonDocument doc; deserializeJson(doc, msg); if (doc["type"] == 200) { SensorPackage sensor(doc.as()); Serial.printf("Sensor %u: %.1f°C, %.1f%% RH\n", sensor.sensorId, sensor.temperature, sensor.humidity); } } void setup() { Serial.begin(115200); dht.begin(); mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT); mesh.onReceive(&receivedCallback); userScheduler.addTask(taskSendSensor); taskSendSensor.enable(); } void loop() { mesh.update(); } ``` ### Bridge with MQTT ```cpp #include "painlessMesh.h" #include #define MESH_PREFIX "MyMesh" #define MESH_PASSWORD "password" #define ROUTER_SSID "YourRouter" #define ROUTER_PASSWORD "RouterPassword" Scheduler userScheduler; painlessMesh mesh; WiFiClient espClient; PubSubClient mqttClient(espClient); void receivedCallback(uint32_t from, String &msg) { // Forward mesh messages to MQTT String topic = "mesh/" + String(from) + "/data"; mqttClient.publish(topic.c_str(), msg.c_str()); } void mqttCallback(char* topic, byte* payload, unsigned int length) { // Forward MQTT messages to mesh String msg = ""; for (unsigned int i = 0; i < length; i++) { msg += (char)payload[i]; } // Extract node ID from topic: mesh//command String topicStr = String(topic); int startIdx = topicStr.indexOf("/") + 1; int endIdx = topicStr.lastIndexOf("/"); String nodeIdStr = topicStr.substring(startIdx, endIdx); uint32_t nodeId = nodeIdStr.toInt(); mesh.sendSingle(nodeId, msg); } void setup() { Serial.begin(115200); // Initialize bridge mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD, ROUTER_SSID, ROUTER_PASSWORD, &userScheduler, 5555); mesh.onReceive(&receivedCallback); // Connect to MQTT mqttClient.setServer("mqtt.example.com", 1883); mqttClient.setCallback(mqttCallback); mqttClient.connect("painlessMeshBridge"); mqttClient.subscribe("mesh/+/command"); } void loop() { mesh.update(); mqttClient.loop(); } ``` ### More Examples Complete examples are available in the repository (18 example directories, 21 sketches, every one compiled for esp32 and esp8266 on each pull request): **Getting Started:** - [Start Here](https://github.com/Alteriom/painlessMesh/blob/main/examples/startHere/startHere.ino) - Best starting point for beginners - [Basic Example](https://github.com/Alteriom/painlessMesh/blob/main/examples/basic/basic.ino) - Essential patterns and techniques **IoT & Sensors:** - [Alteriom Sensor Node](https://github.com/Alteriom/painlessMesh/blob/main/examples/alteriom/alteriom.ino) - IoT sensor packages - [Named Mesh](https://github.com/Alteriom/painlessMesh/blob/main/examples/namedMesh/namedMesh.ino) - Using node names instead of IDs **Internet Connectivity:** - [Bridge Example](https://github.com/Alteriom/painlessMesh/blob/main/examples/bridge/bridge.ino) - Basic Internet bridge - [Bridge Failover](https://github.com/Alteriom/painlessMesh/tree/main/examples/bridge_failover) - High availability bridge - [Shared Gateway](https://github.com/Alteriom/painlessMesh/tree/main/examples/sharedGateway) - All nodes as gateways - [Send to Internet](https://github.com/Alteriom/painlessMesh/tree/main/examples/sendToInternet) - Direct Internet communication - [MQTT Bridge](https://github.com/Alteriom/painlessMesh/tree/main/examples/mqttBridge) - MQTT integration **Reliable Delivery (2.0):** - [Reliable Sensor Logging](https://github.com/Alteriom/painlessMesh/tree/main/examples/reliableSensorLogging) - Buffered retries until the gateway confirms - [Command Control](https://github.com/Alteriom/painlessMesh/tree/main/examples/commandControl) - Per-node broadcast confirmation **Advanced Features:** - [Priority Queue](https://github.com/Alteriom/painlessMesh/tree/main/examples/priority) - Message priority handling - [TCP Retry Config](https://github.com/Alteriom/painlessMesh/tree/main/examples/tcpRetryConfig) - Tuning connection retries - [OTA Sender](https://github.com/Alteriom/painlessMesh/tree/main/examples/otaSender) - Firmware update distribution - [OTA Receiver](https://github.com/Alteriom/painlessMesh/tree/main/examples/otaReceiver) - Firmware update reception - [Log Server](https://github.com/Alteriom/painlessMesh/tree/main/examples/logServer) - Centralized logging - [Log Client](https://github.com/Alteriom/painlessMesh/tree/main/examples/logClient) - Log forwarding - [Web Server](https://github.com/Alteriom/painlessMesh/tree/main/examples/webServer) - Embedded web interface --- ## Additional Resources ### Documentation - **[Main README](Home.md)** - Quick reference and overview - **[BRIDGE_TO_INTERNET.md](Bridge-To-Internet.md)** - Complete bridge guide - **[API Documentation](https://alteriom.github.io/painlessMesh/)** - Online docs - **[CHANGELOG](Changelog.md)** - Version history - **[CONTRIBUTING](https://github.com/Alteriom/painlessMesh/blob/main/CONTRIBUTING.md)** - Contribution guidelines ### Community & Support - **[GitHub Issues](https://github.com/Alteriom/painlessMesh/issues)** - Bug reports and features - **[GitHub Discussions](https://github.com/Alteriom/painlessMesh/discussions)** - General discussion ### Development - **[Release Guide](Release-Guide.md)** - Release process - **[Security Policy](https://github.com/Alteriom/painlessMesh/blob/main/SECURITY.md)** - Security information - **[Code of Conduct](https://github.com/Alteriom/painlessMesh/blob/main/CODE_OF_CONDUCT.md)** - Community guidelines --- ## Version Information This guide covers **AlteriomPainlessMesh 2.0.0**. Every snippet in it is written against the 2.0.0 headers; when the API changes, the guide changes in the same pull request. For the latest updates and releases, visit: - [GitHub Releases](https://github.com/Alteriom/painlessMesh/releases) - [NPM Package](https://www.npmjs.com/package/@alteriom/painlessmesh) - [PlatformIO Registry](https://registry.platformio.org/libraries/sparck75/AlteriomPainlessMesh) --- *Made with ❤️ by the painlessMesh community*