@alteriom/painlessmesh 1.9.19 → 1.10.0
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 +168 -0
- package/README.md +102 -63
- package/RELEASE_GUIDE.md +147 -8
- package/examples/alteriom/README.md +4 -4
- package/examples/alteriom/alteriom_custom_package_template.hpp +320 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +1 -1
- package/examples/alteriom/mppt_example/alteriom_mppt_example.ino +208 -0
- package/examples/bridge_failover/bridge_failover.ino +17 -0
- package/examples/sendToInternet/CMakeLists.txt +54 -0
- package/examples/sendToInternet/PC_NODE_README.md +517 -0
- package/examples/sendToInternet/README.md +39 -1
- package/examples/sendToInternet/build.sh +153 -0
- package/examples/sendToInternet/mock_server_test.ino +361 -0
- package/examples/sendToInternet/pc_mesh_node.cpp +361 -0
- package/examples/tcpRetryConfig/README.md +110 -0
- package/examples/tcpRetryConfig/platformio.ini +26 -0
- package/examples/tcpRetryConfig/tcpRetryConfig.ino +154 -0
- package/keywords.txt +3 -0
- package/library.json +4 -1
- package/library.properties +1 -1
- package/package.json +3 -3
- package/src/AlteriomPainlessMesh.h +6 -14
- package/src/arduino/wifi.hpp +352 -114
- package/src/connection.cpp +10 -0
- package/src/painlessMesh.h +2 -15
- package/src/painlessTaskOptions.h +9 -0
- package/src/painlessmesh/buffer.hpp +4 -1
- package/src/painlessmesh/configuration.hpp +13 -2
- package/src/painlessmesh/connection.hpp +36 -21
- package/src/painlessmesh/gateway.hpp +0 -1061
- package/src/painlessmesh/mesh.hpp +102 -107
- package/src/painlessmesh/message_queue.hpp +25 -15
- package/src/painlessmesh/metrics.hpp +2 -262
- package/src/painlessmesh/plugin.hpp +27 -5
- package/src/painlessmesh/tcp.hpp +158 -29
- package/src/painlessmesh/validation.hpp +0 -143
- package/docs/README.md +0 -132
- package/docs/alteriom/overview.md +0 -531
- package/docs/api/core-api.md +0 -607
- package/docs/api/shared-gateway.md +0 -1207
- package/docs/architecture/mesh-architecture.md +0 -399
- package/docs/architecture/plugin-system.md +0 -517
- package/docs/getting-started/arduino-manual-install.md +0 -313
- package/docs/getting-started/first-mesh.md +0 -410
- package/docs/getting-started/installation.md +0 -275
- package/docs/getting-started/quickstart.md +0 -158
- package/docs/troubleshooting/common-issues.md +0 -679
- package/docs/troubleshooting/debugging.md +0 -455
- package/docs/troubleshooting/external-device-connection.md +0 -283
- package/docs/troubleshooting/faq.md +0 -574
- package/docs/tutorials/basic-examples.md +0 -718
|
@@ -344,7 +344,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
344
344
|
(*conn)->close();
|
|
345
345
|
this->eraseClosedConnections();
|
|
346
346
|
}
|
|
347
|
-
plugin::PackageHandler<T>::stop();
|
|
347
|
+
plugin::PackageHandler<T>::stop(mScheduler);
|
|
348
348
|
|
|
349
349
|
newConnectionCallbacks.clear();
|
|
350
350
|
droppedConnectionCallbacks.clear();
|
|
@@ -352,6 +352,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
352
352
|
|
|
353
353
|
if (!isExternalScheduler) {
|
|
354
354
|
delete mScheduler;
|
|
355
|
+
mScheduler = nullptr;
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
358
|
|
|
@@ -876,7 +877,11 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
876
877
|
* @return Number of available gateways
|
|
877
878
|
*/
|
|
878
879
|
size_t getGatewayCount() {
|
|
879
|
-
|
|
880
|
+
size_t count = 0;
|
|
881
|
+
for (const auto& bridge : knownBridges) {
|
|
882
|
+
if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) count++;
|
|
883
|
+
}
|
|
884
|
+
return count;
|
|
880
885
|
}
|
|
881
886
|
|
|
882
887
|
/**
|
|
@@ -1005,6 +1010,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1005
1010
|
using namespace logger;
|
|
1006
1011
|
if (bridgeCleanupTask != nullptr) {
|
|
1007
1012
|
bridgeCleanupTask->disable();
|
|
1013
|
+
// Task remains in scheduler (disabled) — negligible overhead.
|
|
1014
|
+
// The scheduler will skip it on each cycle.
|
|
1008
1015
|
bridgeCleanupTask = nullptr;
|
|
1009
1016
|
Log(GENERAL, "disableBridgeCleanup(): Cleanup task disabled\n");
|
|
1010
1017
|
}
|
|
@@ -1387,6 +1394,32 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1387
1394
|
internetRetryDelay = delayMs;
|
|
1388
1395
|
}
|
|
1389
1396
|
|
|
1397
|
+
/**
|
|
1398
|
+
* Configure TCP connection retry behaviour
|
|
1399
|
+
*
|
|
1400
|
+
* Lets latency-sensitive, high-reliability or battery-powered deployments
|
|
1401
|
+
* pick their own retry envelope. Values outside safe operating bounds are
|
|
1402
|
+
* clamped - see painlessmesh::tcp::clampTcpRetryConfig(). Read the applied
|
|
1403
|
+
* (post-clamp) values back with getTcpRetryConfig().
|
|
1404
|
+
*
|
|
1405
|
+
* The defaults reproduce the previous hardcoded behaviour exactly, so
|
|
1406
|
+
* existing sketches that never call this see no change.
|
|
1407
|
+
*
|
|
1408
|
+
* @param config Retry parameters to apply
|
|
1409
|
+
*/
|
|
1410
|
+
void setTcpRetryConfig(const painlessmesh::tcp::TcpRetryConfig &config) {
|
|
1411
|
+
tcpRetryConfig = painlessmesh::tcp::clampTcpRetryConfig(config);
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Get the active TCP retry configuration
|
|
1416
|
+
*
|
|
1417
|
+
* @return The configuration currently in effect, after clamping
|
|
1418
|
+
*/
|
|
1419
|
+
painlessmesh::tcp::TcpRetryConfig getTcpRetryConfig() const {
|
|
1420
|
+
return tcpRetryConfig;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1390
1423
|
/**
|
|
1391
1424
|
* Get number of pending Internet requests
|
|
1392
1425
|
*
|
|
@@ -1405,10 +1438,13 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1405
1438
|
bool cancelInternetRequest(uint32_t messageId) {
|
|
1406
1439
|
auto it = pendingInternetRequests.find(messageId);
|
|
1407
1440
|
if (it != pendingInternetRequests.end()) {
|
|
1408
|
-
|
|
1409
|
-
it->second.callback(false, 0, "Request cancelled");
|
|
1410
|
-
}
|
|
1441
|
+
auto callback = it->second.callback;
|
|
1411
1442
|
pendingInternetRequests.erase(it);
|
|
1443
|
+
if (callback) {
|
|
1444
|
+
this->addTask([callback]() {
|
|
1445
|
+
callback(false, 0, "Request cancelled");
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1412
1448
|
Log(logger::GENERAL, "cancelInternetRequest(): Cancelled msgId=%u\n", messageId);
|
|
1413
1449
|
return true;
|
|
1414
1450
|
}
|
|
@@ -1541,19 +1577,22 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1541
1577
|
// These errors indicate infrastructure issues that won't be fixed by retrying:
|
|
1542
1578
|
// - "Router has no internet access" - WAN connection down
|
|
1543
1579
|
// - "Gateway WiFi not connected" - ESP not associated with WiFi
|
|
1580
|
+
// - "Captive portal detected" - Router requires web authentication
|
|
1544
1581
|
// Use find() for std::string (test env) or indexOf() for Arduino String
|
|
1545
|
-
bool routerError = false, wifiError = false;
|
|
1582
|
+
bool routerError = false, wifiError = false, captivePortalError = false;
|
|
1546
1583
|
#if defined(PAINLESSMESH_BOOST)
|
|
1547
1584
|
// Test environment: TSTRING is std::string, use find()
|
|
1548
1585
|
routerError = (ack.error.find("Router has no internet") != std::string::npos);
|
|
1549
1586
|
wifiError = (ack.error.find("Gateway WiFi not connected") != std::string::npos);
|
|
1587
|
+
captivePortalError = (ack.error.find("Captive portal detected") != std::string::npos);
|
|
1550
1588
|
#else
|
|
1551
1589
|
// Arduino environment: TSTRING is String, use indexOf()
|
|
1552
1590
|
routerError = (ack.error.indexOf("Router has no internet") >= 0);
|
|
1553
1591
|
wifiError = (ack.error.indexOf("Gateway WiFi not connected") >= 0);
|
|
1592
|
+
captivePortalError = (ack.error.indexOf("Captive portal detected") >= 0);
|
|
1554
1593
|
#endif
|
|
1555
1594
|
|
|
1556
|
-
if (routerError || wifiError) {
|
|
1595
|
+
if (routerError || wifiError || captivePortalError) {
|
|
1557
1596
|
isGatewayConnectivityError = true;
|
|
1558
1597
|
Log(COMMUNICATION, "handleGatewayAck(): Gateway connectivity error detected (non-retryable): %s\n",
|
|
1559
1598
|
ack.error.c_str());
|
|
@@ -1951,8 +1990,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
1951
1990
|
return rtcTime;
|
|
1952
1991
|
}
|
|
1953
1992
|
}
|
|
1954
|
-
// Fallback to mesh time (microseconds)
|
|
1955
|
-
return
|
|
1993
|
+
// Fallback to mesh time (microseconds), converted to seconds for consistency
|
|
1994
|
+
return getNodeTime() / 1000000;
|
|
1956
1995
|
}
|
|
1957
1996
|
|
|
1958
1997
|
/**
|
|
@@ -2047,16 +2086,34 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2047
2086
|
//
|
|
2048
2087
|
|
|
2049
2088
|
/**
|
|
2050
|
-
* Enable or disable message
|
|
2051
|
-
*
|
|
2052
|
-
* When enabled,
|
|
2053
|
-
*
|
|
2054
|
-
*
|
|
2089
|
+
* Enable or disable the manual message queue for offline mode
|
|
2090
|
+
*
|
|
2091
|
+
* When enabled, allocates a `MessageQueue` that your application can
|
|
2092
|
+
* push into while an upstream (MQTT/HTTP/etc.) is unreachable. The
|
|
2093
|
+
* mesh does NOT flush the queue automatically and does NOT observe
|
|
2094
|
+
* connectivity changes — the app is responsible for detecting when
|
|
2095
|
+
* to drain it via `flushMessageQueue()` + `removeQueuedMessage()`.
|
|
2096
|
+
*
|
|
2097
|
+
* Wire it to your own connectivity signal, for example
|
|
2098
|
+
* `onLocalInternetChanged` on the bridge node or
|
|
2099
|
+
* `onBridgeStatusChanged` on downstream nodes.
|
|
2100
|
+
*
|
|
2055
2101
|
* @param enabled True to enable queueing, false to disable
|
|
2056
2102
|
* @param maxSize Maximum number of messages in queue (default 1000)
|
|
2057
|
-
*
|
|
2103
|
+
*
|
|
2058
2104
|
* \code
|
|
2059
2105
|
* mesh.enableMessageQueue(true, 500); // Enable with 500 message capacity
|
|
2106
|
+
*
|
|
2107
|
+
* mesh.onLocalInternetChanged([&mesh](bool available) {
|
|
2108
|
+
* if (available) {
|
|
2109
|
+
* auto messages = mesh.flushMessageQueue();
|
|
2110
|
+
* for (auto& msg : messages) {
|
|
2111
|
+
* if (sendToCloud(msg.payload, msg.destination)) {
|
|
2112
|
+
* mesh.removeQueuedMessage(msg.id);
|
|
2113
|
+
* }
|
|
2114
|
+
* }
|
|
2115
|
+
* }
|
|
2116
|
+
* });
|
|
2060
2117
|
* \endcode
|
|
2061
2118
|
*/
|
|
2062
2119
|
void enableMessageQueue(bool enabled, uint32_t maxSize = 1000) {
|
|
@@ -2071,11 +2128,13 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2071
2128
|
}
|
|
2072
2129
|
|
|
2073
2130
|
/**
|
|
2074
|
-
* Queue a message with priority for later delivery
|
|
2075
|
-
*
|
|
2076
|
-
* Use this to
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2131
|
+
* Queue a message with priority for later manual delivery
|
|
2132
|
+
*
|
|
2133
|
+
* Use this to buffer critical messages when your upstream
|
|
2134
|
+
* (MQTT/HTTP/etc.) is unavailable. The library does NOT deliver these
|
|
2135
|
+
* on its own — your app must call `flushMessageQueue()` and
|
|
2136
|
+
* `removeQueuedMessage()` once its own connectivity check clears.
|
|
2137
|
+
*
|
|
2079
2138
|
* @param payload Message content to queue
|
|
2080
2139
|
* @param destination Optional destination metadata (e.g., MQTT topic, HTTP endpoint)
|
|
2081
2140
|
* @param priority Message priority (default: PRIORITY_NORMAL)
|
|
@@ -2102,16 +2161,14 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2102
2161
|
}
|
|
2103
2162
|
|
|
2104
2163
|
/**
|
|
2105
|
-
*
|
|
2106
|
-
*
|
|
2107
|
-
*
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
*
|
|
2113
|
-
* and calling removeQueuedMessage() when successful.
|
|
2114
|
-
*
|
|
2164
|
+
* Return a snapshot of all queued messages for manual delivery
|
|
2165
|
+
*
|
|
2166
|
+
* This does NOT transmit anything and does NOT clear the queue. The
|
|
2167
|
+
* application is responsible for actually sending each message and
|
|
2168
|
+
* calling `removeQueuedMessage(id)` for each one that succeeded.
|
|
2169
|
+
* Messages left in the queue remain available for retry on the next
|
|
2170
|
+
* flush.
|
|
2171
|
+
*
|
|
2115
2172
|
* @return Vector of queued messages to send
|
|
2116
2173
|
*
|
|
2117
2174
|
* \code
|
|
@@ -2323,7 +2380,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2323
2380
|
auto tree = this->asNodeTree();
|
|
2324
2381
|
|
|
2325
2382
|
// BFS to find hop count
|
|
2326
|
-
std::queue<std::pair<uint32_t,
|
|
2383
|
+
std::queue<std::pair<uint32_t, uint16_t>> queue; // (nodeId, hops)
|
|
2327
2384
|
std::set<uint32_t> visited;
|
|
2328
2385
|
|
|
2329
2386
|
// Start from this node
|
|
@@ -2335,7 +2392,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2335
2392
|
queue.pop();
|
|
2336
2393
|
|
|
2337
2394
|
uint32_t currentNode = current.first;
|
|
2338
|
-
|
|
2395
|
+
uint16_t hops = current.second;
|
|
2339
2396
|
|
|
2340
2397
|
// Found the target
|
|
2341
2398
|
if (currentNode == nodeId) {
|
|
@@ -2349,7 +2406,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2349
2406
|
for (auto neighbor : neighbors) {
|
|
2350
2407
|
if (visited.find(neighbor) == visited.end()) {
|
|
2351
2408
|
visited.insert(neighbor);
|
|
2352
|
-
queue.push({neighbor, static_cast<
|
|
2409
|
+
queue.push({neighbor, static_cast<uint16_t>(hops + 1)});
|
|
2353
2410
|
}
|
|
2354
2411
|
}
|
|
2355
2412
|
}
|
|
@@ -2478,7 +2535,8 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2478
2535
|
// Reconstruct path from parent map
|
|
2479
2536
|
if (found) {
|
|
2480
2537
|
uint32_t current = nodeId;
|
|
2481
|
-
|
|
2538
|
+
size_t maxIter = parent.size() + 1;
|
|
2539
|
+
while (current != 0 && maxIter-- > 0) {
|
|
2482
2540
|
path.insert(path.begin(), current);
|
|
2483
2541
|
current = parent[current];
|
|
2484
2542
|
}
|
|
@@ -2523,7 +2581,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2523
2581
|
uint32_t totalMessagesDropped = 0;
|
|
2524
2582
|
uint32_t totalLatency = 0;
|
|
2525
2583
|
uint32_t latencySampleCount = 0;
|
|
2526
|
-
|
|
2584
|
+
int32_t sumRSSI = 0;
|
|
2527
2585
|
int8_t minRSSI = 0;
|
|
2528
2586
|
int8_t maxRSSI = -127;
|
|
2529
2587
|
uint32_t rssiCount = 0;
|
|
@@ -2820,76 +2878,6 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2820
2878
|
return lastBridgeChange;
|
|
2821
2879
|
}
|
|
2822
2880
|
|
|
2823
|
-
/**
|
|
2824
|
-
* Get Internet path to specific node
|
|
2825
|
-
*
|
|
2826
|
-
* Returns the routing path from the specified node to the Internet bridge.
|
|
2827
|
-
* Path includes all intermediate nodes from source to bridge.
|
|
2828
|
-
*
|
|
2829
|
-
* \code
|
|
2830
|
-
* auto path = mesh.getInternetPath(targetNodeId);
|
|
2831
|
-
* Serial.print("Path to Internet: ");
|
|
2832
|
-
* for (auto nodeId : path) {
|
|
2833
|
-
* Serial.printf("%u -> ", nodeId);
|
|
2834
|
-
* }
|
|
2835
|
-
* Serial.println("Internet");
|
|
2836
|
-
* \endcode
|
|
2837
|
-
*
|
|
2838
|
-
* @param nodeId Node to find path from
|
|
2839
|
-
* @return Vector of node IDs representing the path (empty if no path)
|
|
2840
|
-
*/
|
|
2841
|
-
std::vector<uint32_t> getInternetPath(uint32_t nodeId) {
|
|
2842
|
-
std::vector<uint32_t> path;
|
|
2843
|
-
|
|
2844
|
-
// Get primary bridge
|
|
2845
|
-
auto primaryBridge = this->getPrimaryBridge();
|
|
2846
|
-
if (primaryBridge == nullptr) {
|
|
2847
|
-
Log(logger::GENERAL, "getInternetPath(): No bridge available\n");
|
|
2848
|
-
return path;
|
|
2849
|
-
}
|
|
2850
|
-
|
|
2851
|
-
// If requesting path for the bridge itself
|
|
2852
|
-
if (nodeId == primaryBridge->nodeId) {
|
|
2853
|
-
path.push_back(nodeId);
|
|
2854
|
-
return path;
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
// Start with the target node
|
|
2858
|
-
path.push_back(nodeId);
|
|
2859
|
-
|
|
2860
|
-
// For now, simplified routing: if direct connection, add bridge
|
|
2861
|
-
// TODO: Implement proper multi-hop path discovery
|
|
2862
|
-
if (this->isConnected(primaryBridge->nodeId)) {
|
|
2863
|
-
path.push_back(primaryBridge->nodeId);
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
return path;
|
|
2867
|
-
}
|
|
2868
|
-
|
|
2869
|
-
/**
|
|
2870
|
-
* Get bridge node ID for specific node
|
|
2871
|
-
*
|
|
2872
|
-
* Returns the bridge node ID that the specified node should use to reach Internet.
|
|
2873
|
-
* For most cases, this is the primary bridge.
|
|
2874
|
-
*
|
|
2875
|
-
* \code
|
|
2876
|
-
* uint32_t bridgeId = mesh.getBridgeForNodeId(targetNodeId);
|
|
2877
|
-
* if (bridgeId != 0) {
|
|
2878
|
-
* Serial.printf("Node %u uses bridge %u\n", targetNodeId, bridgeId);
|
|
2879
|
-
* }
|
|
2880
|
-
* \endcode
|
|
2881
|
-
*
|
|
2882
|
-
* @param nodeId Node to find bridge for
|
|
2883
|
-
* @return Bridge node ID, or 0 if no bridge available
|
|
2884
|
-
*/
|
|
2885
|
-
uint32_t getBridgeForNodeId(uint32_t nodeId) {
|
|
2886
|
-
auto primaryBridge = this->getPrimaryBridge();
|
|
2887
|
-
if (primaryBridge != nullptr) {
|
|
2888
|
-
return primaryBridge->nodeId;
|
|
2889
|
-
}
|
|
2890
|
-
return 0;
|
|
2891
|
-
}
|
|
2892
|
-
|
|
2893
2881
|
/**
|
|
2894
2882
|
* Export topology as DOT format (Graphviz)
|
|
2895
2883
|
*
|
|
@@ -3250,7 +3238,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3250
3238
|
bool isExternalScheduler = false;
|
|
3251
3239
|
|
|
3252
3240
|
/// Is the node a root node
|
|
3253
|
-
bool shouldContainRoot;
|
|
3241
|
+
bool shouldContainRoot = false;
|
|
3254
3242
|
|
|
3255
3243
|
Scheduler *mScheduler;
|
|
3256
3244
|
|
|
@@ -3265,7 +3253,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3265
3253
|
*/
|
|
3266
3254
|
bool semaphoreTake() {
|
|
3267
3255
|
#ifdef ESP32
|
|
3268
|
-
return xSemaphoreTake(xSemaphore, (TickType_t)
|
|
3256
|
+
return xSemaphoreTake(xSemaphore, (TickType_t)1000) == pdTRUE;
|
|
3269
3257
|
#else
|
|
3270
3258
|
return true;
|
|
3271
3259
|
#endif
|
|
@@ -3318,6 +3306,13 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
3318
3306
|
uint32_t internetRetryDelay = 1000; // Default 1 second base delay
|
|
3319
3307
|
bool sendToInternetEnabled = false;
|
|
3320
3308
|
|
|
3309
|
+
// TCP connect retry parameters, see setTcpRetryConfig().
|
|
3310
|
+
// Public because tcp::connect<Connection, wifi::Mesh> reads it but is NOT a
|
|
3311
|
+
// friend: the friend declaration below names connect<T, Mesh<T>>, whereas
|
|
3312
|
+
// wifi::Mesh::tcpConnect() instantiates connect<Connection, wifi::Mesh> - a
|
|
3313
|
+
// different specialisation. Same reason as the public: sections above.
|
|
3314
|
+
painlessmesh::tcp::TcpRetryConfig tcpRetryConfig;
|
|
3315
|
+
|
|
3321
3316
|
friend T;
|
|
3322
3317
|
friend void onDataCb(void *, AsyncClient *, void *, size_t);
|
|
3323
3318
|
friend void tcpSentCb(void *, AsyncClient *, size_t, uint32_t);
|
|
@@ -73,29 +73,40 @@ struct QueueStats {
|
|
|
73
73
|
typedef std::function<void(QueueState state, uint32_t messageCount)> queueStateChangedCallback_t;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
76
|
+
* Manual priority buffer for offline / Internet-unavailable mode
|
|
77
|
+
*
|
|
78
|
+
* This is a *storage* container only. It does NOT transmit anything on
|
|
79
|
+
* its own: painlessMesh will not enqueue, flush, or deliver messages
|
|
80
|
+
* automatically, and there is no connectivity-restored trigger inside
|
|
81
|
+
* the library. The application owns the send loop — enqueue when your
|
|
82
|
+
* upstream (MQTT/HTTP/etc.) is down, drain via `getMessages()` /
|
|
83
|
+
* `mesh.flushMessageQueue()` and `remove()` / `mesh.removeQueuedMessage()`
|
|
84
|
+
* when your own connectivity check tells you it is safe to resend.
|
|
85
|
+
*
|
|
78
86
|
* Provides priority-based message queueing with support for:
|
|
79
|
-
* - Priority levels (CRITICAL messages
|
|
87
|
+
* - Priority levels (CRITICAL messages evicted last, but may still be dropped
|
|
88
|
+
* if the queue is full and all entries are CRITICAL)
|
|
80
89
|
* - Queue size limits with intelligent eviction
|
|
81
90
|
* - Statistics tracking
|
|
82
91
|
* - State change callbacks
|
|
83
|
-
*
|
|
92
|
+
*
|
|
84
93
|
* Example usage:
|
|
85
94
|
* \code
|
|
86
95
|
* MessageQueue queue(1000); // Max 1000 messages
|
|
87
|
-
*
|
|
88
|
-
* // Queue a critical message
|
|
96
|
+
*
|
|
97
|
+
* // Queue a critical message while upstream is offline
|
|
89
98
|
* uint32_t msgId = queue.enqueue(PRIORITY_CRITICAL, "alarm_data", "mqtt://...");
|
|
90
|
-
*
|
|
99
|
+
*
|
|
91
100
|
* // Get queue size
|
|
92
101
|
* uint32_t size = queue.size();
|
|
93
|
-
*
|
|
94
|
-
* //
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
102
|
+
*
|
|
103
|
+
* // When your app detects the upstream is back, drain manually:
|
|
104
|
+
* auto messages = queue.getMessages();
|
|
105
|
+
* for (auto& msg : messages) {
|
|
106
|
+
* if (sendToCloud(msg.payload, msg.destination)) {
|
|
107
|
+
* queue.remove(msg.id);
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
99
110
|
* \endcode
|
|
100
111
|
*/
|
|
101
112
|
class MessageQueue {
|
|
@@ -104,9 +115,8 @@ public:
|
|
|
104
115
|
* Constructor
|
|
105
116
|
* @param maxSize Maximum number of messages in queue
|
|
106
117
|
*/
|
|
107
|
-
explicit MessageQueue(uint32_t maxSize = 1000)
|
|
118
|
+
explicit MessageQueue(uint32_t maxSize = 1000)
|
|
108
119
|
: maxQueueSize(maxSize), nextMessageId(1) {
|
|
109
|
-
messages.reserve(maxSize);
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
/**
|
|
@@ -33,8 +33,7 @@ class Timer {
|
|
|
33
33
|
|
|
34
34
|
uint32_t elapsed_us() const {
|
|
35
35
|
#ifdef ESP32
|
|
36
|
-
return static_cast<uint32_t>(
|
|
37
|
-
1000);
|
|
36
|
+
return static_cast<uint32_t>(esp_timer_get_time() - start_time_us_);
|
|
38
37
|
#else
|
|
39
38
|
return elapsed_ms() * 1000; // Fallback to millisecond precision
|
|
40
39
|
#endif
|
|
@@ -58,266 +57,7 @@ class Timer {
|
|
|
58
57
|
#endif
|
|
59
58
|
};
|
|
60
59
|
|
|
61
|
-
/**
|
|
62
|
-
* Message statistics tracking
|
|
63
|
-
*/
|
|
64
|
-
struct MessageStats {
|
|
65
|
-
uint32_t messages_sent = 0;
|
|
66
|
-
uint32_t messages_received = 0;
|
|
67
|
-
uint32_t messages_dropped = 0;
|
|
68
|
-
uint32_t messages_retransmitted = 0;
|
|
69
|
-
uint32_t bytes_sent = 0;
|
|
70
|
-
uint32_t bytes_received = 0;
|
|
71
|
-
uint32_t parse_errors = 0;
|
|
72
|
-
uint32_t validation_errors = 0;
|
|
73
|
-
|
|
74
|
-
// Latency tracking
|
|
75
|
-
uint32_t min_latency_ms = UINT32_MAX;
|
|
76
|
-
uint32_t max_latency_ms = 0;
|
|
77
|
-
uint32_t total_latency_ms = 0;
|
|
78
|
-
uint32_t latency_samples = 0;
|
|
79
|
-
|
|
80
|
-
void record_sent_message(size_t bytes) {
|
|
81
|
-
messages_sent++;
|
|
82
|
-
bytes_sent += bytes;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
void record_received_message(size_t bytes) {
|
|
86
|
-
messages_received++;
|
|
87
|
-
bytes_received += bytes;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
void record_dropped_message() { messages_dropped++; }
|
|
91
|
-
|
|
92
|
-
void record_retransmission() { messages_retransmitted++; }
|
|
93
|
-
|
|
94
|
-
void record_parse_error() { parse_errors++; }
|
|
95
|
-
|
|
96
|
-
void record_validation_error() { validation_errors++; }
|
|
97
|
-
|
|
98
|
-
void record_latency(uint32_t latency_ms) {
|
|
99
|
-
min_latency_ms = std::min(min_latency_ms, latency_ms);
|
|
100
|
-
max_latency_ms = std::max(max_latency_ms, latency_ms);
|
|
101
|
-
total_latency_ms += latency_ms;
|
|
102
|
-
latency_samples++;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
uint32_t average_latency_ms() const {
|
|
106
|
-
return latency_samples > 0 ? total_latency_ms / latency_samples : 0;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
double throughput_bps() const {
|
|
110
|
-
#ifdef ARDUINO
|
|
111
|
-
uint32_t uptime_s = millis() / 1000;
|
|
112
|
-
#else
|
|
113
|
-
// For non-Arduino builds, use a simpler approach or disable this feature
|
|
114
|
-
uint32_t uptime_s = 1; // Avoid division by zero
|
|
115
|
-
#endif
|
|
116
|
-
return uptime_s > 0 ? (bytes_sent + bytes_received) * 8.0 / uptime_s : 0.0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
double packet_loss_rate() const {
|
|
120
|
-
uint32_t total_attempted = messages_sent + messages_dropped;
|
|
121
|
-
return total_attempted > 0
|
|
122
|
-
? static_cast<double>(messages_dropped) / total_attempted
|
|
123
|
-
: 0.0;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
void reset() { *this = MessageStats{}; }
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Memory usage tracking
|
|
131
|
-
*/
|
|
132
|
-
struct MemoryStats {
|
|
133
|
-
uint32_t heap_free = 0;
|
|
134
|
-
uint32_t heap_max_alloc = 0;
|
|
135
|
-
uint32_t heap_min_free = UINT32_MAX;
|
|
136
|
-
uint32_t psram_free = 0;
|
|
137
|
-
uint32_t stack_high_water = 0;
|
|
138
|
-
|
|
139
|
-
void update() {
|
|
140
|
-
#ifdef ESP32
|
|
141
|
-
heap_free = ESP.getFreeHeap();
|
|
142
|
-
heap_max_alloc = ESP.getMaxAllocHeap();
|
|
143
|
-
heap_min_free = std::min(heap_min_free, heap_free);
|
|
144
|
-
|
|
145
|
-
#ifdef BOARD_HAS_PSRAM
|
|
146
|
-
psram_free = ESP.getFreePsram();
|
|
147
|
-
#endif
|
|
148
|
-
|
|
149
|
-
stack_high_water = uxTaskGetStackHighWaterMark(NULL);
|
|
150
|
-
#elif defined(ESP8266)
|
|
151
|
-
heap_free = ESP.getFreeHeap();
|
|
152
|
-
heap_max_alloc = ESP.getMaxFreeBlockSize();
|
|
153
|
-
heap_min_free = std::min(heap_min_free, heap_free);
|
|
154
|
-
#endif
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
bool is_memory_critical() const {
|
|
158
|
-
return heap_free < 10000; // Less than 10KB free is critical
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
bool is_memory_low() const {
|
|
162
|
-
return heap_free < 20000; // Less than 20KB free is low
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Network topology metrics
|
|
168
|
-
*/
|
|
169
|
-
struct TopologyStats {
|
|
170
|
-
uint32_t node_count = 0;
|
|
171
|
-
uint32_t connection_count = 0;
|
|
172
|
-
uint32_t max_hops = 0;
|
|
173
|
-
uint32_t connection_changes = 0;
|
|
174
|
-
uint32_t failed_connections = 0;
|
|
175
|
-
|
|
176
|
-
void record_topology_change(uint32_t nodes, uint32_t connections,
|
|
177
|
-
uint32_t hops) {
|
|
178
|
-
node_count = nodes;
|
|
179
|
-
connection_count = connections;
|
|
180
|
-
max_hops = hops;
|
|
181
|
-
connection_changes++;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
void record_failed_connection() { failed_connections++; }
|
|
185
|
-
|
|
186
|
-
double connection_stability() const {
|
|
187
|
-
return connection_changes > 0
|
|
188
|
-
? 1.0 - static_cast<double>(failed_connections) /
|
|
189
|
-
connection_changes
|
|
190
|
-
: 1.0;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Comprehensive metrics collector
|
|
196
|
-
*/
|
|
197
|
-
class MetricsCollector {
|
|
198
|
-
public:
|
|
199
|
-
MetricsCollector() : start_time_(get_current_time()) {}
|
|
200
|
-
|
|
201
|
-
MessageStats& message_stats() { return message_stats_; }
|
|
202
|
-
MemoryStats& memory_stats() { return memory_stats_; }
|
|
203
|
-
TopologyStats& topology_stats() { return topology_stats_; }
|
|
204
|
-
|
|
205
|
-
const MessageStats& message_stats() const { return message_stats_; }
|
|
206
|
-
const MemoryStats& memory_stats() const { return memory_stats_; }
|
|
207
|
-
const TopologyStats& topology_stats() const { return topology_stats_; }
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Update all metrics (call periodically)
|
|
211
|
-
*/
|
|
212
|
-
void update() {
|
|
213
|
-
memory_stats_.update();
|
|
214
|
-
last_update_ = get_current_time();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Get uptime in seconds
|
|
219
|
-
*/
|
|
220
|
-
uint32_t uptime_seconds() const {
|
|
221
|
-
return (get_current_time() - start_time_) / 1000;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Generate JSON status report
|
|
226
|
-
*/
|
|
227
|
-
TSTRING generate_status_json() const {
|
|
228
|
-
TSTRING json = "{";
|
|
229
|
-
json += "\"uptime\":";
|
|
230
|
-
json += std::to_string(uptime_seconds());
|
|
231
|
-
json += ",";
|
|
232
|
-
json += "\"messages\":{";
|
|
233
|
-
json += "\"sent\":";
|
|
234
|
-
json += std::to_string(message_stats_.messages_sent);
|
|
235
|
-
json += ",";
|
|
236
|
-
json += "\"received\":";
|
|
237
|
-
json += std::to_string(message_stats_.messages_received);
|
|
238
|
-
json += ",";
|
|
239
|
-
json += "\"dropped\":";
|
|
240
|
-
json += std::to_string(message_stats_.messages_dropped);
|
|
241
|
-
json += ",";
|
|
242
|
-
json += "\"avg_latency\":";
|
|
243
|
-
json += std::to_string(message_stats_.average_latency_ms());
|
|
244
|
-
json += ",";
|
|
245
|
-
json += "\"throughput\":";
|
|
246
|
-
json += std::to_string(message_stats_.throughput_bps());
|
|
247
|
-
json += ",";
|
|
248
|
-
json += "\"packet_loss\":";
|
|
249
|
-
json += std::to_string(message_stats_.packet_loss_rate());
|
|
250
|
-
json += "},";
|
|
251
|
-
json += "\"memory\":{";
|
|
252
|
-
json += "\"heap_free\":";
|
|
253
|
-
json += std::to_string(memory_stats_.heap_free);
|
|
254
|
-
json += ",";
|
|
255
|
-
json += "\"heap_min\":";
|
|
256
|
-
json += std::to_string(memory_stats_.heap_min_free);
|
|
257
|
-
json += ",";
|
|
258
|
-
json += "\"critical\":";
|
|
259
|
-
json += (memory_stats_.is_memory_critical() ? "true" : "false");
|
|
260
|
-
json += "},";
|
|
261
|
-
json += "\"topology\":{";
|
|
262
|
-
json += "\"nodes\":";
|
|
263
|
-
json += std::to_string(topology_stats_.node_count);
|
|
264
|
-
json += ",";
|
|
265
|
-
json += "\"connections\":";
|
|
266
|
-
json += std::to_string(topology_stats_.connection_count);
|
|
267
|
-
json += ",";
|
|
268
|
-
json += "\"max_hops\":";
|
|
269
|
-
json += std::to_string(topology_stats_.max_hops);
|
|
270
|
-
json += ",";
|
|
271
|
-
json += "\"stability\":";
|
|
272
|
-
json += std::to_string(topology_stats_.connection_stability());
|
|
273
|
-
json += "}";
|
|
274
|
-
json += "}";
|
|
275
|
-
return json;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Reset all metrics
|
|
280
|
-
*/
|
|
281
|
-
void reset() {
|
|
282
|
-
message_stats_.reset();
|
|
283
|
-
topology_stats_ = TopologyStats{};
|
|
284
|
-
start_time_ = get_current_time();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Check if metrics indicate performance issues
|
|
289
|
-
*/
|
|
290
|
-
bool has_performance_issues() const {
|
|
291
|
-
return memory_stats_.is_memory_critical() ||
|
|
292
|
-
message_stats_.packet_loss_rate() > 0.1 || // > 10% packet loss
|
|
293
|
-
topology_stats_.connection_stability() < 0.8; // < 80% stability
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private:
|
|
297
|
-
uint32_t get_current_time() const {
|
|
298
|
-
#ifdef ARDUINO
|
|
299
|
-
return millis();
|
|
300
|
-
#else
|
|
301
|
-
auto now = std::chrono::steady_clock::now();
|
|
302
|
-
auto duration = now.time_since_epoch();
|
|
303
|
-
return std::chrono::duration_cast<std::chrono::milliseconds>(duration)
|
|
304
|
-
.count();
|
|
305
|
-
#endif
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
MessageStats message_stats_;
|
|
309
|
-
MemoryStats memory_stats_;
|
|
310
|
-
TopologyStats topology_stats_;
|
|
311
|
-
uint32_t start_time_;
|
|
312
|
-
uint32_t last_update_ = 0;
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
// Global metrics instance (optional, can be disabled)
|
|
316
|
-
#ifdef PAINLESS_MESH_ENABLE_METRICS
|
|
317
|
-
extern MetricsCollector global_metrics;
|
|
318
|
-
#endif
|
|
319
|
-
|
|
320
60
|
} // namespace metrics
|
|
321
61
|
} // namespace painlessmesh
|
|
322
62
|
|
|
323
|
-
#endif // _PAINLESS_MESH_METRICS_HPP_
|
|
63
|
+
#endif // _PAINLESS_MESH_METRICS_HPP_
|