@alteriom/painlessmesh 1.8.2 → 1.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +74 -11
  3. package/RELEASE_GUIDE.md +57 -16
  4. package/docs/ARDUINO_LIBRARY_MANAGER_SUBMISSION.md +331 -0
  5. package/docs/features/DIAGNOSTICS_API.md +534 -0
  6. package/docs/getting-started/arduino-manual-install.md +313 -0
  7. package/docs/implementation/BRIDGE_ARCHITECTURE_IMPLEMENTATION.md +340 -0
  8. package/docs/implementation/BRIDGE_HEALTH_MONITORING_IMPLEMENTATION.md +213 -0
  9. package/docs/implementation/BRIDGE_STATUS_FEATURE.md +635 -0
  10. package/docs/implementation/DIAGNOSTICS_API_IMPLEMENTATION.md +232 -0
  11. package/docs/implementation/IMPLEMENTATION_COMPLETE.md +228 -0
  12. package/docs/implementation/IMPLEMENTATION_NTP_TIME_SYNC.md +325 -0
  13. package/docs/implementation/IMPLEMENTATION_SUMMARY.md +316 -0
  14. package/docs/implementation/MESSAGE_QUEUE_IMPLEMENTATION.md +405 -0
  15. package/docs/implementation/MULTI_BRIDGE_IMPLEMENTATION.md +520 -0
  16. package/docs/implementation/NTP_TIME_SYNC_FEATURE.md +392 -0
  17. package/docs/internal/CUSTOM_AGENT_ANALYSIS.md +391 -0
  18. package/docs/internal/ISSUE_65_VERIFICATION.md +947 -0
  19. package/docs/internal/ISSUE_66_CLOSURE.md +249 -0
  20. package/docs/internal/ISSUE_66_STATUS.md +316 -0
  21. package/docs/internal/PR_SUMMARY.md +315 -0
  22. package/docs/internal/REVIEW_SUMMARY.md +332 -0
  23. package/docs/releases/PUBLISH_v1.8.0_INSTRUCTIONS.md +163 -0
  24. package/docs/releases/QUICK_START_RELEASES.md +113 -0
  25. package/docs/releases/RELEASE_CHECKLIST_v1.8.0.md +331 -0
  26. package/docs/releases/RELEASE_CHECKLIST_v1.8.2.md +309 -0
  27. package/docs/releases/RELEASE_NOTES_v1.8.0.md +685 -0
  28. package/docs/releases/RELEASE_NOTES_v1.8.1.md +221 -0
  29. package/docs/releases/RELEASE_NOTES_v1.8.2.md +421 -0
  30. package/docs/releases/RELEASE_NOTES_v1.8.3.md +292 -0
  31. package/docs/releases/RELEASE_NOTES_v1.8.4.md +277 -0
  32. package/docs/troubleshooting/ARDUINO_IDE_VERSION_FIX_SUMMARY.md +229 -0
  33. package/docs/troubleshooting/ARDUINO_LIBRARY_NAME_FIX.md +197 -0
  34. package/docs/troubleshooting/NPM_PUBLISHING_ISSUE_SUMMARY.md +110 -0
  35. package/docs/troubleshooting/station-reconnection-issues.md +172 -0
  36. package/examples/bridge_failover/README.md +17 -1
  37. package/examples/priority/README.md +274 -0
  38. package/examples/priority/priority_basic_example.ino +115 -0
  39. package/examples/priority/priority_with_queue.ino +249 -0
  40. package/examples/routing_demo/README.md +172 -0
  41. package/examples/routing_demo/routing_demo.ino +102 -0
  42. package/library.json +1 -1
  43. package/library.properties +3 -3
  44. package/package.json +1 -1
  45. package/src/arduino/wifi.hpp +62 -16
  46. package/src/painlessMesh.h +15 -0
  47. package/src/painlessMeshSTA.cpp +7 -1
  48. package/src/painlessmesh/buffer.hpp +218 -37
  49. package/src/painlessmesh/connection.hpp +21 -1
  50. package/src/painlessmesh/mesh.hpp +253 -19
  51. package/src/painlessmesh/router.hpp +31 -0
@@ -0,0 +1,172 @@
1
+ # Multi-Hop Routing Demo
2
+
3
+ This example demonstrates the new multi-hop routing capabilities implemented in painlessMesh.
4
+
5
+ ## Features Demonstrated
6
+
7
+ ### 1. Hop Count Calculation (`getHopCount()`)
8
+ - Calculates the actual number of hops to reach any node in the mesh
9
+ - Returns 0 for self, 1 for direct connections, actual count for multi-hop paths
10
+ - Returns -1 if the node is unreachable
11
+
12
+ ### 2. Routing Table (`getRoutingTable()`)
13
+ - Returns a complete routing table mapping each destination to its next hop
14
+ - For direct connections, destination equals next hop
15
+ - For multi-hop paths, shows which neighbor to route through
16
+
17
+ ### 3. Path Discovery (`getPathToNode()`)
18
+ - Finds the complete path from this node to any target node
19
+ - Returns a vector of node IDs representing the shortest path
20
+ - Useful for visualizing mesh topology and debugging connectivity
21
+
22
+ ## How It Works
23
+
24
+ The implementation uses **Breadth-First Search (BFS)** to:
25
+ - Find shortest paths in the mesh topology
26
+ - Build accurate routing tables
27
+ - Calculate hop counts efficiently
28
+
29
+ ### Algorithm Complexity
30
+ - **Time**: O(V + E) where V = nodes, E = edges
31
+ - **Space**: O(V) for visited set and queue
32
+ - **Suitable for**: ESP8266 (80KB RAM) and ESP32 (320KB RAM)
33
+
34
+ ## Running the Example
35
+
36
+ ### Hardware Requirements
37
+ - 2 or more ESP32 or ESP8266 boards
38
+ - No additional hardware needed
39
+
40
+ ### Software Requirements
41
+ - painlessMesh library (with multi-hop routing support)
42
+ - TaskScheduler library
43
+ - ArduinoJson library
44
+
45
+ ### Setup Instructions
46
+
47
+ 1. Upload this sketch to multiple ESP devices
48
+ 2. Each device will automatically join the mesh
49
+ 3. Open Serial Monitor (115200 baud) on any device
50
+ 4. Observe routing information every 10 seconds
51
+
52
+ ### Expected Output
53
+
54
+ ```
55
+ Routing Demo Started
56
+ Node ID: 123456789
57
+
58
+ === Routing Information ===
59
+ Mesh contains 3 nodes (plus this node)
60
+
61
+ Hop Counts:
62
+ Node 234567890: 1 hop
63
+ Node 345678901: 2 hops
64
+ Node 456789012: 3 hops
65
+
66
+ Routing Table (Destination -> Next Hop):
67
+ To 234567890 -> via 234567890 (direct connection)
68
+ To 345678901 -> via 234567890
69
+ To 456789012 -> via 234567890
70
+
71
+ Path to node 456789012:
72
+ 123456789 -> 234567890 -> 345678901 -> 456789012
73
+ (Total: 3 hops)
74
+ ========================
75
+ ```
76
+
77
+ ## Use Cases
78
+
79
+ ### Network Diagnostics
80
+ - Monitor mesh topology changes in real-time
81
+ - Identify connectivity issues
82
+ - Measure network depth
83
+
84
+ ### Load Balancing
85
+ - Choose optimal paths for data transmission
86
+ - Distribute traffic across multiple routes
87
+ - Avoid overloading single-hop nodes
88
+
89
+ ### Network Visualization
90
+ - Build visual representations of mesh topology
91
+ - Display on OLED/LCD screens
92
+ - Create web-based network maps
93
+
94
+ ## Performance Considerations
95
+
96
+ ### Memory Usage
97
+ - Routing table size: ~8 bytes per node
98
+ - BFS temporary storage: ~16 bytes per node
99
+ - Example mesh of 50 nodes: ~1.2KB total
100
+
101
+ ### CPU Usage
102
+ - Routing calculation: ~1-5ms for typical meshes
103
+ - Triggered only on topology changes
104
+ - No continuous overhead
105
+
106
+ ## Advanced Usage
107
+
108
+ ### Custom Routing Logic
109
+
110
+ ```cpp
111
+ // Find nodes within N hops
112
+ std::vector<uint32_t> findNodesWithinRange(int maxHops) {
113
+ std::vector<uint32_t> nearby;
114
+ auto nodeList = mesh.getNodeList(false);
115
+
116
+ for (auto nodeId : nodeList) {
117
+ int hops = mesh.getHopCount(nodeId);
118
+ if (hops > 0 && hops <= maxHops) {
119
+ nearby.push_back(nodeId);
120
+ }
121
+ }
122
+
123
+ return nearby;
124
+ }
125
+ ```
126
+
127
+ ### Route Optimization
128
+
129
+ ```cpp
130
+ // Select best route based on hop count
131
+ uint32_t selectBestRoute(std::vector<uint32_t> candidates) {
132
+ uint32_t best = 0;
133
+ int minHops = 999;
134
+
135
+ for (auto candidate : candidates) {
136
+ int hops = mesh.getHopCount(candidate);
137
+ if (hops > 0 && hops < minHops) {
138
+ minHops = hops;
139
+ best = candidate;
140
+ }
141
+ }
142
+
143
+ return best;
144
+ }
145
+ ```
146
+
147
+ ## Troubleshooting
148
+
149
+ ### "All nodes show 2 hops"
150
+ - Old firmware without multi-hop support
151
+ - Update to latest painlessMesh version
152
+
153
+ ### "Routing table is empty"
154
+ - No other nodes in the mesh
155
+ - Check mesh credentials (SSID/password)
156
+ - Verify mesh is initialized
157
+
158
+ ### "Path discovery returns empty vector"
159
+ - Target node is unreachable or left the mesh
160
+ - Check connectivity with `getHopCount()` first
161
+
162
+ ## Related Examples
163
+
164
+ - `namedMesh.ino` - Basic mesh setup
165
+ - `diagnosticsExample.ino` - Advanced diagnostics
166
+ - `bridge_failover.ino` - Multi-bridge routing
167
+
168
+ ## References
169
+
170
+ - [painlessMesh Documentation](https://alteriom.github.io/painlessMesh/)
171
+ - [Multi-Hop Routing Issue #XXX](https://github.com/Alteriom/painlessMesh/issues/XXX)
172
+ - BFS Algorithm: [Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search)
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Multi-Hop Routing Demo
3
+ *
4
+ * This example demonstrates the new multi-hop routing capabilities of painlessMesh.
5
+ * It shows how to:
6
+ * - Calculate hop count to any node in the mesh
7
+ * - Get the complete routing table
8
+ * - Find the path from this node to any other node
9
+ *
10
+ * The functions work automatically with any mesh topology - linear chains,
11
+ * star configurations, or complex multi-hop meshes.
12
+ */
13
+
14
+ #include "painlessMesh.h"
15
+
16
+ #define MESH_PREFIX "routingDemo"
17
+ #define MESH_PASSWORD "meshPassword"
18
+ #define MESH_PORT 5555
19
+
20
+ Scheduler userScheduler;
21
+ painlessMesh mesh;
22
+
23
+ // Task to periodically display routing information
24
+ Task taskShowRouting(10000, TASK_FOREVER, []() {
25
+ Serial.println("\n=== Routing Information ===");
26
+
27
+ // Get list of all nodes in the mesh
28
+ auto nodeList = mesh.getNodeList(false); // false = don't include self
29
+ Serial.printf("Mesh contains %d nodes (plus this node)\n", nodeList.size());
30
+
31
+ // Display hop count to each node
32
+ Serial.println("\nHop Counts:");
33
+ for (auto nodeId : nodeList) {
34
+ int hops = mesh.getHopCount(nodeId);
35
+ Serial.printf(" Node %u: %d hop%s\n", nodeId, hops, hops == 1 ? "" : "s");
36
+ }
37
+
38
+ // Display complete routing table
39
+ Serial.println("\nRouting Table (Destination -> Next Hop):");
40
+ auto routingTable = mesh.getRoutingTable();
41
+ for (auto& entry : routingTable) {
42
+ Serial.printf(" To %u -> via %u", entry.first, entry.second);
43
+ if (entry.first == entry.second) {
44
+ Serial.print(" (direct connection)");
45
+ }
46
+ Serial.println();
47
+ }
48
+
49
+ // Example: Show path to first node in list
50
+ if (!nodeList.empty()) {
51
+ uint32_t targetNode = nodeList.front();
52
+ auto path = mesh.getPathToNode(targetNode);
53
+
54
+ if (!path.empty()) {
55
+ Serial.printf("\nPath to node %u:\n ", targetNode);
56
+ for (size_t i = 0; i < path.size(); i++) {
57
+ Serial.printf("%u", path[i]);
58
+ if (i < path.size() - 1) {
59
+ Serial.print(" -> ");
60
+ }
61
+ }
62
+ Serial.printf("\n (Total: %d hop%s)\n", path.size() - 1,
63
+ path.size() - 1 == 1 ? "" : "s");
64
+ } else {
65
+ Serial.printf("\nNode %u is unreachable\n", targetNode);
66
+ }
67
+ }
68
+
69
+ Serial.println("========================\n");
70
+ });
71
+
72
+ void setup() {
73
+ Serial.begin(115200);
74
+
75
+ // Initialize mesh
76
+ mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
77
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
78
+
79
+ // Setup callbacks
80
+ mesh.onNewConnection([](uint32_t nodeId) {
81
+ Serial.printf("\nNew connection: Node %u joined the mesh\n", nodeId);
82
+ });
83
+
84
+ mesh.onDroppedConnection([](uint32_t nodeId) {
85
+ Serial.printf("\nConnection dropped: Node %u left the mesh\n", nodeId);
86
+ });
87
+
88
+ mesh.onChangedConnections([]() {
89
+ Serial.println("\nMesh topology changed - routing tables updated");
90
+ });
91
+
92
+ // Add routing display task
93
+ userScheduler.addTask(taskShowRouting);
94
+ taskShowRouting.enable();
95
+
96
+ Serial.println("\nRouting Demo Started");
97
+ Serial.printf("Node ID: %u\n", mesh.getNodeId());
98
+ }
99
+
100
+ void loop() {
101
+ mesh.update();
102
+ }
package/library.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/Alteriom/painlessMesh"
8
8
  },
9
- "version": "1.8.2",
9
+ "version": "1.8.4",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
- name=AlteriomPainlessMesh
2
- version=1.8.2
1
+ name=Alteriom PainlessMesh
2
+ version=1.8.4
3
3
  author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
4
4
  maintainer=Alteriom
5
5
  sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
@@ -7,5 +7,5 @@ paragraph=painlessMesh is a user-friendly library for creating mesh networks wit
7
7
  category=Communication
8
8
  url=https://github.com/Alteriom/painlessMesh
9
9
  architectures=esp8266,esp32
10
- includes=painlessMesh.h
10
+ includes=painlessMesh.h,AlteriomPainlessMesh.h
11
11
  depends=ArduinoJson, TaskScheduler
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteriom/painlessmesh",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
4
4
  "description": "painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices. This Alteriom fork includes additional packages for sensor data (SensorPackage), device commands (CommandPackage), and status monitoring (StatusPackage). It handles routing and network management automatically, so you can focus on your application. The library uses JSON-based messaging and syncs time across all nodes, making it ideal for coordinated behaviour like synchronized light displays or sensor networks reporting to a central node.",
5
5
  "keywords": [
6
6
  "arduino",
@@ -367,16 +367,33 @@ class Mesh : public painlessmesh::Mesh<Connection> {
367
367
  this->droppedConnectionCallbacks.push_back(
368
368
  [this](uint32_t nodeId, bool station) {
369
369
  if (station) {
370
- if (WiFi.status() == WL_CONNECTED) WiFi.disconnect();
371
- // TODO: Can we do this when we get signalled that wifi disconnect
372
- // is complete
373
- this->stationScan.yieldConnectToAP();
374
- // Re-enable it if it was disabled
375
- this->stationScan.task.enableIfNot();
370
+ if (WiFi.status() == WL_CONNECTED) {
371
+ WiFi.disconnect();
372
+ // Schedule reconnection after disconnect completes
373
+ // The WiFi event handler will signal when disconnect is complete
374
+ _pendingStationReconnect = true;
375
+ } else {
376
+ // Already disconnected, reconnect immediately
377
+ handleStationDisconnectComplete();
378
+ }
376
379
  }
377
380
  });
378
381
  }
379
382
 
383
+ /**
384
+ * Handle station disconnect completion
385
+ * Called after WiFi disconnect event is fully processed
386
+ * This ensures proper sequencing: disconnect -> event -> reconnect
387
+ */
388
+ void handleStationDisconnectComplete() {
389
+ if (_pendingStationReconnect) {
390
+ _pendingStationReconnect = false;
391
+ this->stationScan.yieldConnectToAP();
392
+ // Re-enable scanning if it was disabled
393
+ this->stationScan.task.enableIfNot();
394
+ }
395
+ }
396
+
380
397
  void tcpServerInit() {
381
398
  using namespace logger;
382
399
  Log(GENERAL, "tcpServerInit():\n");
@@ -387,26 +404,35 @@ class Mesh : public painlessmesh::Mesh<Connection> {
387
404
  return;
388
405
  }
389
406
 
407
+ /**
408
+ * Establish TCP connection to mesh network
409
+ *
410
+ * This method is called by WiFi event handlers when station gets IP address.
411
+ * It creates a TCP client connection to the mesh network gateway.
412
+ *
413
+ * Architecture Note: This is intentionally kept in the Mesh class rather than
414
+ * extracted to a separate StationConnection class because:
415
+ * - It's tightly coupled with WiFi event lifecycle
416
+ * - Needs access to mesh state and callbacks
417
+ * - Moving it would increase complexity without clear benefits
418
+ * - The existing design keeps connection logic cohesive with WiFi management
419
+ */
390
420
  void tcpConnect() {
391
421
  using namespace logger;
392
- // TODO: move to Connection or StationConnection?
393
422
  Log(GENERAL, "tcpConnect():\n");
394
423
  if (stationScan.manual && stationScan.port == 0)
395
424
  return; // We have been configured not to connect to the mesh
396
425
 
397
- // TODO: We could pass this to tcpConnect instead of loading it here
398
426
  if (WiFi.status() == WL_CONNECTED && WiFi.localIP()) {
427
+ // Determine target IP and port for connection
428
+ IPAddress targetIP = stationScan.manualIP ? stationScan.manualIP : WiFi.gatewayIP();
429
+ uint16_t targetPort = stationScan.port;
430
+
399
431
  AsyncClient *pConn = new AsyncClient();
400
-
401
- IPAddress ip = WiFi.gatewayIP();
402
- if (stationScan.manualIP) {
403
- ip = stationScan.manualIP;
404
- }
405
-
406
432
  painlessmesh::tcp::connect<Connection, painlessmesh::Mesh<Connection>>(
407
- (*pConn), ip, stationScan.port, (*this));
433
+ (*pConn), targetIP, targetPort, (*this));
408
434
  } else {
409
- Log(ERROR, "tcpConnect(): err Something un expected in tcpConnect()\n");
435
+ Log(ERROR, "tcpConnect(): err Something unexpected in tcpConnect()\n");
410
436
  }
411
437
  }
412
438
 
@@ -688,6 +714,19 @@ class Mesh : public painlessmesh::Mesh<Connection> {
688
714
  }
689
715
  );
690
716
 
717
+ // Send immediate broadcast so nodes can discover this bridge right away
718
+ // This ensures bridge is discoverable before the first periodic broadcast
719
+ this->addTask([this]() {
720
+ Log(STARTUP, "Sending initial bridge status broadcast\n");
721
+ this->sendBridgeStatus();
722
+ });
723
+
724
+ // Also broadcast when new nodes connect so they can discover the bridge immediately
725
+ this->newConnectionCallbacks.push_back([this](uint32_t nodeId) {
726
+ Log(CONNECTION, "New node %u connected, sending bridge status\n", nodeId);
727
+ this->sendBridgeStatus();
728
+ });
729
+
691
730
  Log(STARTUP, "Bridge status broadcast enabled (interval: %d ms)\n",
692
731
  this->bridgeStatusIntervalMs);
693
732
  }
@@ -1153,6 +1192,8 @@ class Mesh : public painlessmesh::Mesh<Connection> {
1153
1192
  "eventSTADisconnectedHandler: "
1154
1193
  "ARDUINO_EVENT_WIFI_STA_DISCONNECTED\n");
1155
1194
  this->droppedConnectionCallbacks.execute(0, true);
1195
+ // Handle station disconnect completion after callbacks
1196
+ this->handleStationDisconnectComplete();
1156
1197
  this->semaphoreGive();
1157
1198
  }
1158
1199
  },
@@ -1189,6 +1230,8 @@ class Mesh : public painlessmesh::Mesh<Connection> {
1189
1230
  [&](const WiFiEventStationModeDisconnected &event) {
1190
1231
  Log(CONNECTION, "Event: Station Mode Disconnected\n");
1191
1232
  this->droppedConnectionCallbacks.execute(0, true);
1233
+ // Handle station disconnect completion after callbacks
1234
+ this->handleStationDisconnectComplete();
1192
1235
  });
1193
1236
 
1194
1237
  eventSTAGotIPHandler =
@@ -1215,6 +1258,9 @@ class Mesh : public painlessmesh::Mesh<Connection> {
1215
1258
  #endif // ESP8266
1216
1259
  AsyncServer *_tcpListener;
1217
1260
  std::shared_ptr<Task> bridgeStatusTask;
1261
+
1262
+ // Station disconnect handling state
1263
+ bool _pendingStationReconnect = false;
1218
1264
 
1219
1265
  // Bridge failover state and configuration
1220
1266
  enum ElectionState {
@@ -1,6 +1,21 @@
1
1
  #ifndef _EASY_MESH_H_
2
2
  #define _EASY_MESH_H_
3
3
 
4
+ /**
5
+ * @file painlessMesh.h
6
+ * @brief Main header file for Alteriom painlessMesh library
7
+ *
8
+ * @version 1.8.4
9
+ * @date 2025-11-12
10
+ *
11
+ * painlessMesh is a user-friendly library for creating mesh networks with
12
+ * ESP8266 and ESP32 devices. This Alteriom fork includes additional packages
13
+ * for sensor data, device commands, and status monitoring.
14
+ *
15
+ * For the latest version and updates, visit:
16
+ * https://github.com/Alteriom/painlessMesh
17
+ */
18
+
4
19
  #include "painlessTaskOptions.h"
5
20
 
6
21
  #include <Arduino.h>
@@ -189,7 +189,13 @@ void ICACHE_FLASH_ATTR StationScan::connectToAP() {
189
189
  mesh->closeConnectionSTA();
190
190
  task.enableDelayed(10 * SCAN_INTERVAL);
191
191
  return;
192
- } else if (aps.empty() || !ssid.equals(aps.begin()->ssid)) {
192
+ } else {
193
+ // For manual router connections, reconnect directly using WiFi.begin()
194
+ // Don't rely on scan results since router may be on different channel
195
+ Log(CONNECTION,
196
+ "connectToAP(): Manual connection - attempting to reconnect to %s\n",
197
+ ssid.c_str());
198
+ WiFi.begin(ssid.c_str(), password.c_str());
193
199
  task.enableDelayed(SCAN_INTERVAL);
194
200
  return;
195
201
  }