@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.
- package/CHANGELOG.md +60 -0
- package/README.md +74 -11
- package/RELEASE_GUIDE.md +57 -16
- package/docs/ARDUINO_LIBRARY_MANAGER_SUBMISSION.md +331 -0
- package/docs/features/DIAGNOSTICS_API.md +534 -0
- package/docs/getting-started/arduino-manual-install.md +313 -0
- package/docs/implementation/BRIDGE_ARCHITECTURE_IMPLEMENTATION.md +340 -0
- package/docs/implementation/BRIDGE_HEALTH_MONITORING_IMPLEMENTATION.md +213 -0
- package/docs/implementation/BRIDGE_STATUS_FEATURE.md +635 -0
- package/docs/implementation/DIAGNOSTICS_API_IMPLEMENTATION.md +232 -0
- package/docs/implementation/IMPLEMENTATION_COMPLETE.md +228 -0
- package/docs/implementation/IMPLEMENTATION_NTP_TIME_SYNC.md +325 -0
- package/docs/implementation/IMPLEMENTATION_SUMMARY.md +316 -0
- package/docs/implementation/MESSAGE_QUEUE_IMPLEMENTATION.md +405 -0
- package/docs/implementation/MULTI_BRIDGE_IMPLEMENTATION.md +520 -0
- package/docs/implementation/NTP_TIME_SYNC_FEATURE.md +392 -0
- package/docs/internal/CUSTOM_AGENT_ANALYSIS.md +391 -0
- package/docs/internal/ISSUE_65_VERIFICATION.md +947 -0
- package/docs/internal/ISSUE_66_CLOSURE.md +249 -0
- package/docs/internal/ISSUE_66_STATUS.md +316 -0
- package/docs/internal/PR_SUMMARY.md +315 -0
- package/docs/internal/REVIEW_SUMMARY.md +332 -0
- package/docs/releases/PUBLISH_v1.8.0_INSTRUCTIONS.md +163 -0
- package/docs/releases/QUICK_START_RELEASES.md +113 -0
- package/docs/releases/RELEASE_CHECKLIST_v1.8.0.md +331 -0
- package/docs/releases/RELEASE_CHECKLIST_v1.8.2.md +309 -0
- package/docs/releases/RELEASE_NOTES_v1.8.0.md +685 -0
- package/docs/releases/RELEASE_NOTES_v1.8.1.md +221 -0
- package/docs/releases/RELEASE_NOTES_v1.8.2.md +421 -0
- package/docs/releases/RELEASE_NOTES_v1.8.3.md +292 -0
- package/docs/releases/RELEASE_NOTES_v1.8.4.md +277 -0
- package/docs/troubleshooting/ARDUINO_IDE_VERSION_FIX_SUMMARY.md +229 -0
- package/docs/troubleshooting/ARDUINO_LIBRARY_NAME_FIX.md +197 -0
- package/docs/troubleshooting/NPM_PUBLISHING_ISSUE_SUMMARY.md +110 -0
- package/docs/troubleshooting/station-reconnection-issues.md +172 -0
- package/examples/bridge_failover/README.md +17 -1
- package/examples/priority/README.md +274 -0
- package/examples/priority/priority_basic_example.ino +115 -0
- package/examples/priority/priority_with_queue.ino +249 -0
- package/examples/routing_demo/README.md +172 -0
- package/examples/routing_demo/routing_demo.ino +102 -0
- package/library.json +1 -1
- package/library.properties +3 -3
- package/package.json +1 -1
- package/src/arduino/wifi.hpp +62 -16
- package/src/painlessMesh.h +15 -0
- package/src/painlessMeshSTA.cpp +7 -1
- package/src/painlessmesh/buffer.hpp +218 -37
- package/src/painlessmesh/connection.hpp +21 -1
- package/src/painlessmesh/mesh.hpp +253 -19
- 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,
|
|
1144
|
+
// For indirect connections, use BFS to find shortest path
|
|
1087
1145
|
auto tree = this->asNodeTree();
|
|
1088
|
-
|
|
1089
|
-
//
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
|
1193
|
+
auto tree = this->asNodeTree();
|
|
1106
1194
|
|
|
1107
|
-
//
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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);
|