@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
@@ -2,6 +2,9 @@
2
2
  #define _PAINLESS_MESH_MESH_HPP_
3
3
 
4
4
  #include <vector>
5
+ #include <map>
6
+ #include <set>
7
+ #include <queue>
5
8
 
6
9
  #include "painlessmesh/configuration.hpp"
7
10
 
@@ -336,6 +339,23 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
336
339
  auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
337
340
  return painlessmesh::router::send<T>(single, (*this));
338
341
  }
342
+
343
+ /** Send message to a specific node with priority
344
+ *
345
+ * @param destId The nodeId of the node to send it to.
346
+ * @param msg The message to send
347
+ * @param priorityLevel Priority level: 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
348
+ *
349
+ * @return true if everything works, false if not.
350
+ */
351
+ bool sendSingle(uint32_t destId, TSTRING msg, uint8_t priorityLevel) {
352
+ Log(logger::COMMUNICATION, "sendSingle(): dest=%u msg=%s priority=%u\n", destId,
353
+ msg.c_str(), priorityLevel);
354
+ auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
355
+ auto conn = painlessmesh::router::findRoute<T>((*this), destId);
356
+ if (!conn) return false;
357
+ return painlessmesh::router::sendWithPriority<painlessmesh::protocol::Single, T>(single, conn, priorityLevel);
358
+ }
339
359
 
340
360
  /** Broadcast a message to every node on the mesh network.
341
361
  *
@@ -355,6 +375,39 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
355
375
  if (success > 0) return true;
356
376
  return false;
357
377
  }
378
+
379
+ /** Broadcast a message with priority to every node on the mesh network.
380
+ *
381
+ * @param msg The message to broadcast
382
+ * @param priorityLevel Priority level: 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
383
+ * @param includeSelf Send message to myself as well. Default is false.
384
+ *
385
+ * @return true if everything works, false if not
386
+ */
387
+ bool sendBroadcast(TSTRING msg, uint8_t priorityLevel, bool includeSelf = false) {
388
+ using namespace logger;
389
+ Log(COMMUNICATION, "sendBroadcast(): msg=%s priority=%u\n", msg.c_str(), priorityLevel);
390
+ painlessmesh::protocol::Broadcast pkg(this->nodeId, 0, msg);
391
+
392
+ // Broadcast to all connections with priority
393
+ size_t success = 0;
394
+ for (auto&& conn : this->subs) {
395
+ if (conn->nodeId != 0) {
396
+ painlessmesh::protocol::Variant variant(pkg);
397
+ TSTRING msgStr;
398
+ variant.printTo(msgStr);
399
+ auto sent = conn->addMessageWithPriority(msgStr, priorityLevel);
400
+ if (sent) ++success;
401
+ }
402
+ }
403
+
404
+ if (includeSelf) {
405
+ protocol::Variant var(pkg);
406
+ this->callbackList.execute(var.type(), var, NULL, 0);
407
+ }
408
+ if (success > 0) return true;
409
+ return false;
410
+ }
358
411
 
359
412
  /** Sends a node a packet to measure network trip delay to that node.
360
413
  *
@@ -1076,6 +1129,11 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1076
1129
  * Returns -1 if node is unreachable
1077
1130
  */
1078
1131
  int getHopCount(uint32_t nodeId) {
1132
+ // Check if requesting hop count to self
1133
+ if (nodeId == this->getNodeId()) {
1134
+ return 0;
1135
+ }
1136
+
1079
1137
  // Check if it's a direct connection (1 hop)
1080
1138
  for (auto conn : this->subs) {
1081
1139
  if (conn->nodeId == nodeId && conn->connected()) {
@@ -1083,44 +1141,174 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1083
1141
  }
1084
1142
  }
1085
1143
 
1086
- // For indirect connections, traverse the node tree
1144
+ // For indirect connections, use BFS to find shortest path
1087
1145
  auto tree = this->asNodeTree();
1088
- // Simple BFS to find hop count
1089
- // For now, return 2 for any node in the mesh but not directly connected
1090
- auto nodeList = this->getNodeList(false);
1091
- for (auto node : nodeList) {
1092
- if (node == nodeId) {
1093
- return 2; // TODO: Implement proper hop count calculation
1146
+
1147
+ // BFS to find hop count
1148
+ std::queue<std::pair<uint32_t, uint8_t>> queue; // (nodeId, hops)
1149
+ std::set<uint32_t> visited;
1150
+
1151
+ // Start from this node
1152
+ queue.push({this->getNodeId(), 0});
1153
+ visited.insert(this->getNodeId());
1154
+
1155
+ while (!queue.empty()) {
1156
+ auto current = queue.front();
1157
+ queue.pop();
1158
+
1159
+ uint32_t currentNode = current.first;
1160
+ uint8_t hops = current.second;
1161
+
1162
+ // Found the target
1163
+ if (currentNode == nodeId) {
1164
+ return hops;
1165
+ }
1166
+
1167
+ // Get neighbors of current node
1168
+ std::list<uint32_t> neighbors;
1169
+ if (getNodeNeighbors(tree, currentNode, neighbors)) {
1170
+ // Add unvisited neighbors to queue
1171
+ for (auto neighbor : neighbors) {
1172
+ if (visited.find(neighbor) == visited.end()) {
1173
+ visited.insert(neighbor);
1174
+ queue.push({neighbor, static_cast<uint8_t>(hops + 1)});
1175
+ }
1176
+ }
1094
1177
  }
1095
1178
  }
1096
1179
 
1097
- return -1; // Node not found
1180
+ return -1; // Node not found or unreachable
1098
1181
  }
1099
1182
 
1100
1183
  /**
1101
1184
  * Get routing table as map (destination -> next hop)
1185
+ *
1186
+ * Uses BFS to build a complete routing table with next-hop information
1187
+ * for all reachable nodes in the mesh. For direct connections, the next
1188
+ * hop is the node itself. For multi-hop paths, the next hop is the first
1189
+ * node on the shortest path to the destination.
1102
1190
  */
1103
1191
  std::map<uint32_t, uint32_t> getRoutingTable() {
1104
1192
  std::map<uint32_t, uint32_t> table;
1105
- auto nodeList = this->getNodeList(false);
1193
+ auto tree = this->asNodeTree();
1106
1194
 
1107
- // For each destination, find the next hop
1108
- for (auto destNode : nodeList) {
1109
- // Check direct connections first
1110
- for (auto conn : this->subs) {
1111
- if (conn->nodeId == destNode && conn->connected()) {
1112
- table[destNode] = destNode; // Direct connection
1113
- break;
1195
+ // BFS from this node to build routing table
1196
+ std::queue<uint32_t> queue;
1197
+ std::set<uint32_t> visited;
1198
+ std::map<uint32_t, uint32_t> nextHop; // dest -> next hop from source
1199
+
1200
+ // Start from this node
1201
+ queue.push(this->getNodeId());
1202
+ visited.insert(this->getNodeId());
1203
+
1204
+ while (!queue.empty()) {
1205
+ uint32_t current = queue.front();
1206
+ queue.pop();
1207
+
1208
+ // Get neighbors of current node
1209
+ std::list<uint32_t> neighbors;
1210
+ if (getNodeNeighbors(tree, current, neighbors)) {
1211
+ for (auto neighbor : neighbors) {
1212
+ if (visited.find(neighbor) == visited.end()) {
1213
+ visited.insert(neighbor);
1214
+ queue.push(neighbor);
1215
+
1216
+ // Determine next hop for this neighbor
1217
+ if (current == this->getNodeId()) {
1218
+ // Direct connection - next hop is the neighbor itself
1219
+ nextHop[neighbor] = neighbor;
1220
+ } else {
1221
+ // Multi-hop - inherit parent's next hop
1222
+ nextHop[neighbor] = nextHop[current];
1223
+ }
1224
+
1225
+ // Add to routing table
1226
+ table[neighbor] = nextHop[neighbor];
1227
+ }
1114
1228
  }
1115
1229
  }
1116
-
1117
- // TODO: Implement proper routing table lookup for multi-hop paths
1118
- // For now, just mark direct connections
1119
1230
  }
1120
1231
 
1121
1232
  return table;
1122
1233
  }
1123
1234
 
1235
+ /**
1236
+ * Get complete path from this node to target node
1237
+ *
1238
+ * Returns a vector containing the complete path of node IDs from this node
1239
+ * to the target node, including both endpoints. Uses BFS to find the shortest
1240
+ * path. Returns an empty vector if the target is unreachable.
1241
+ *
1242
+ * \param nodeId The target node ID
1243
+ * \return Vector of node IDs representing the path (empty if unreachable)
1244
+ *
1245
+ * \code
1246
+ * auto path = mesh.getPathToNode(targetNodeId);
1247
+ * if (!path.empty()) {
1248
+ * Serial.printf("Path length: %d hops\n", path.size() - 1);
1249
+ * for (auto node : path) {
1250
+ * Serial.printf("%u -> ", node);
1251
+ * }
1252
+ * }
1253
+ * \endcode
1254
+ */
1255
+ std::vector<uint32_t> getPathToNode(uint32_t nodeId) {
1256
+ std::vector<uint32_t> path;
1257
+
1258
+ // Check if requesting path to self
1259
+ if (nodeId == this->getNodeId()) {
1260
+ path.push_back(this->getNodeId());
1261
+ return path;
1262
+ }
1263
+
1264
+ auto tree = this->asNodeTree();
1265
+
1266
+ // BFS to find path
1267
+ std::queue<uint32_t> queue;
1268
+ std::set<uint32_t> visited;
1269
+ std::map<uint32_t, uint32_t> parent; // child -> parent mapping
1270
+
1271
+ // Start from this node
1272
+ queue.push(this->getNodeId());
1273
+ visited.insert(this->getNodeId());
1274
+ parent[this->getNodeId()] = 0; // Root has no parent
1275
+
1276
+ bool found = false;
1277
+ while (!queue.empty() && !found) {
1278
+ uint32_t current = queue.front();
1279
+ queue.pop();
1280
+
1281
+ // Check if we reached the target
1282
+ if (current == nodeId) {
1283
+ found = true;
1284
+ break;
1285
+ }
1286
+
1287
+ // Get neighbors of current node
1288
+ std::list<uint32_t> neighbors;
1289
+ if (getNodeNeighbors(tree, current, neighbors)) {
1290
+ for (auto neighbor : neighbors) {
1291
+ if (visited.find(neighbor) == visited.end()) {
1292
+ visited.insert(neighbor);
1293
+ parent[neighbor] = current;
1294
+ queue.push(neighbor);
1295
+ }
1296
+ }
1297
+ }
1298
+ }
1299
+
1300
+ // Reconstruct path from parent map
1301
+ if (found) {
1302
+ uint32_t current = nodeId;
1303
+ while (current != 0) {
1304
+ path.insert(path.begin(), current);
1305
+ current = parent[current];
1306
+ }
1307
+ }
1308
+
1309
+ return path;
1310
+ }
1311
+
1124
1312
  /**
1125
1313
  * Get bridge health metrics
1126
1314
  *
@@ -1789,6 +1977,42 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
1789
1977
  router::send<protocol::TimeSync, T>(timeSync, conn, true);
1790
1978
  }
1791
1979
 
1980
+ /**
1981
+ * Helper function to get direct neighbors of a node from the mesh topology tree
1982
+ *
1983
+ * \param tree The node tree to search
1984
+ * \param nodeId The node whose neighbors we want to find
1985
+ * \param neighbors Output list of neighbor node IDs
1986
+ * \param parent The parent node ID (used during recursion, 0 means no parent)
1987
+ * \return true if the node was found in the tree
1988
+ */
1989
+ bool getNodeNeighbors(const protocol::NodeTree& tree, uint32_t nodeId,
1990
+ std::list<uint32_t>& neighbors, uint32_t parent = 0) const {
1991
+ if (tree.nodeId == nodeId) {
1992
+ // Found the target node - collect its neighbors
1993
+ // Add parent if it exists
1994
+ if (parent != 0) {
1995
+ neighbors.push_back(parent);
1996
+ }
1997
+ // Add all direct children
1998
+ for (auto& sub : tree.subs) {
1999
+ if (sub.nodeId != 0) {
2000
+ neighbors.push_back(sub.nodeId);
2001
+ }
2002
+ }
2003
+ return true;
2004
+ }
2005
+
2006
+ // Recursively search in children
2007
+ for (auto& sub : tree.subs) {
2008
+ if (getNodeNeighbors(sub, nodeId, neighbors, tree.nodeId)) {
2009
+ return true;
2010
+ }
2011
+ }
2012
+
2013
+ return false;
2014
+ }
2015
+
1792
2016
  bool closeConnectionSTA() {
1793
2017
  auto connection = this->subs.begin();
1794
2018
  while (connection != this->subs.end()) {
@@ -1979,6 +2203,16 @@ class Connection : public painlessmesh::layout::Neighbour,
1979
2203
  bool addMessage(const TSTRING &msg, bool priority = false) {
1980
2204
  return this->write(msg, priority);
1981
2205
  }
2206
+
2207
+ /**
2208
+ * Add message with explicit priority level (0-3)
2209
+ *
2210
+ * \param msg The message to send
2211
+ * \param priorityLevel Priority level: 0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW
2212
+ */
2213
+ bool addMessageWithPriority(const TSTRING &msg, uint8_t priorityLevel) {
2214
+ return this->writeWithPriority(msg, priorityLevel);
2215
+ }
1982
2216
 
1983
2217
  /**
1984
2218
  * Record message received timestamp and bytes
@@ -64,6 +64,37 @@ bool send(protocol::Variant&& variant, std::shared_ptr<U> conn,
64
64
  return conn->addMessage(msg, priority);
65
65
  }
66
66
 
67
+ // New priority-level send functions (0-3 priority levels)
68
+ template <class T, class U>
69
+ bool sendWithPriority(T& package, std::shared_ptr<U> conn, uint8_t priorityLevel) {
70
+ painlessmesh::protocol::Variant variant(package);
71
+ TSTRING msg;
72
+ variant.printTo(msg);
73
+ return conn->addMessageWithPriority(msg, priorityLevel);
74
+ }
75
+
76
+ template <class T, class U>
77
+ bool sendWithPriority(T&& package, std::shared_ptr<U> conn, uint8_t priorityLevel) {
78
+ painlessmesh::protocol::Variant variant(package);
79
+ TSTRING msg;
80
+ variant.printTo(msg);
81
+ return conn->addMessageWithPriority(msg, priorityLevel);
82
+ }
83
+
84
+ template <class U>
85
+ bool sendWithPriority(protocol::Variant& variant, std::shared_ptr<U> conn, uint8_t priorityLevel) {
86
+ TSTRING msg;
87
+ variant.printTo(msg);
88
+ return conn->addMessageWithPriority(msg, priorityLevel);
89
+ }
90
+
91
+ template <class U>
92
+ bool sendWithPriority(protocol::Variant&& variant, std::shared_ptr<U> conn, uint8_t priorityLevel) {
93
+ TSTRING msg;
94
+ variant.printTo(msg);
95
+ return conn->addMessageWithPriority(msg, priorityLevel);
96
+ }
97
+
67
98
  template <class T, class U>
68
99
  bool send(T& package, layout::Layout<U> layout) {
69
100
  painlessmesh::protocol::Variant variant(package);