AlteriomPainlessMesh v2.1.1
Easy ESP32/ESP8266 mesh networking with Alteriom IoT extensions
Loading...
Searching...
No Matches
mesh.hpp
Go to the documentation of this file.
1#ifndef _PAINLESS_MESH_MESH_HPP_
2#define _PAINLESS_MESH_MESH_HPP_
3
4#include <algorithm>
5#include <list>
6#include <vector>
7#include <map>
8#include <set>
9#include <queue>
10#include <type_traits>
11
13
14#include "painlessmesh/ack.hpp"
19#include "painlessmesh/ntp.hpp"
22#include "painlessmesh/rtc.hpp"
23#include "painlessmesh/tcp.hpp"
25
26#ifdef PAINLESSMESH_ENABLE_OTA
27#include "painlessmesh/ota.hpp"
28#endif
29
30namespace painlessmesh {
31typedef std::function<void(uint32_t nodeId)> newConnectionCallback_t;
32typedef std::function<void(uint32_t nodeId)> droppedConnectionCallback_t;
33typedef std::function<void(uint32_t from, TSTRING &msg)> receivedCallback_t;
34typedef std::function<void()> changedConnectionsCallback_t;
35typedef std::function<void(int32_t offset)> nodeTimeAdjustedCallback_t;
36typedef std::function<void(uint32_t nodeId, int32_t delay)> nodeDelayCallback_t;
37typedef std::function<void(uint32_t bridgeNodeId, bool internetAvailable)> bridgeStatusChangedCallback_t;
38typedef std::function<void(uint32_t timestamp)> rtcSyncCompleteCallback_t;
39typedef std::function<void(bool available)> localInternetChangedCallback_t;
40typedef std::function<void(uint32_t oldPrimary, uint32_t newPrimary)> gatewayChangedCallback_t;
41
73
81typedef std::function<void(bool success, uint16_t httpStatus, TSTRING error)>
83
90#define PAINLESSMESH_HAS_INTERNET_RESULT 1
91
102 uint32_t messageId = 0;
104 bool success = false;
106 uint16_t httpStatus = 0;
116 bool retryable = false;
118 uint8_t attempts = 0;
119};
120
130typedef std::function<void(const InternetResult& result)>
132
139 uint32_t messageId = 0;
140 uint32_t timestamp = 0;
141 uint8_t retryCount = 0;
142 uint8_t maxRetries = 3;
143 uint8_t priority = 2;
144 uint32_t timeoutMs = 30000;
145 uint32_t retryDelayMs = 1000;
146 uint32_t gatewayNodeId = 0;
153 uint8_t attempts = 0;
155 uint32_t requestNonce = 0;
159
163 bool isTimedOut() const {
164 return (static_cast<uint32_t>(millis()) - timestamp) > timeoutMs;
165 }
166};
167
174public:
175 uint32_t nodeId = 0; // Bridge node ID
176 bool internetConnected = false; // Is bridge connected to Internet?
177 int8_t routerRSSI = 0; // Router WiFi signal strength in dBm
178 uint8_t routerChannel = 0; // Router WiFi channel
179 uint32_t lastSeen = 0; // Timestamp when last status received (millis)
180 uint32_t uptime = 0; // Bridge uptime in milliseconds
181 TSTRING gatewayIP = ""; // Router gateway IP address
182 uint32_t timestamp = 0; // Timestamp from bridge status message
183
188 bool isHealthy(uint32_t timeoutMs = 60000) const {
189 // Cast millis() to uint32_t to handle overflow correctly
190 // This ensures consistent behavior on both embedded and test environments
191 return (static_cast<uint32_t>(millis()) - lastSeen) < timeoutMs;
192 }
193};
194
202 // Connectivity
203 uint32_t uptimeSeconds = 0;
205 uint32_t totalDisconnects = 0;
206 uint32_t currentUptime = 0;
207
208 // Signal Quality
209 int8_t currentRSSI = 0;
210 int8_t avgRSSI = 0;
211 int8_t minRSSI = 0;
212 int8_t maxRSSI = -127;
213
214 // Traffic
215 uint64_t bytesRx = 0;
216 uint64_t bytesTx = 0;
217 uint32_t messagesRx = 0;
218 uint32_t messagesTx = 0;
219 uint32_t messagesQueued = 0;
220 uint32_t messagesDropped = 0;
221
222 // Performance
223 uint32_t avgLatencyMs = 0;
224 uint8_t packetLossPercent = 0;
225 uint32_t meshNodeCount = 0;
226};
227
234 bool isBridge = false; // Is this node acting as a bridge?
235 bool internetConnected = false; // Is Internet connection available?
236 TSTRING role = "regular"; // Role: "regular", "bridge", "root"
237 uint32_t bridgeNodeId = 0; // Current bridge node ID (0 if none)
238 int8_t bridgeRSSI = 0; // Signal strength to bridge/router (dBm)
239 uint32_t timeSinceBridgeChange = 0; // Time since last bridge change (ms)
240};
241
248 uint32_t timestamp = 0; // When election occurred (millis)
249 uint32_t winnerNodeId = 0; // Node that won election
250 int8_t winnerRSSI = 0; // Winner's router RSSI
251 uint32_t candidateCount = 0; // Number of candidates
252 TSTRING reason = ""; // Why election was triggered
253};
254
261 uint32_t timestamp = 0; // When change occurred (millis)
262 uint32_t oldBridgeId = 0; // Previous bridge node ID
263 uint32_t newBridgeId = 0; // New bridge node ID
264 TSTRING reason = ""; // Reason for change
265 bool internetAvailable = false; // Internet available after change
266};
267
274 bool success = false; // Overall test success
275 bool bridgeReachable = false; // Can reach bridge node
276 bool internetReachable = false; // Can reach Internet (if bridge available)
277 uint32_t latencyMs = 0; // Round-trip latency to bridge
278 TSTRING message = ""; // Detailed test message
279};
280
286template <class T>
287class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
288 public:
289 void init(uint32_t id) {
290 using namespace logger;
291 if (!isExternalScheduler) {
292 mScheduler = new Scheduler();
293 }
294 this->nodeId = id;
295
296#ifdef ESP32
297 xSemaphore = xSemaphoreCreateMutex();
298#endif
299
300 // Add package handlers
303
304 // Every node answers a gateway request it cannot serve. A bridge that
305 // reboots, crashes or is reflashed as a regular node announces nothing
306 // -- only a bridge stepping down in-process sends `leaving` -- so its
307 // peers keep it in knownBridges until its last status ages out
308 // (bridgeTimeoutMs, 60 s) and may route every Internet request to it.
309 // A regular node had no GATEWAY_DATA handler and dropped them without a
310 // word; the origin waited out its request timeout, 30 s, each time.
311 // Found on the HIL rig while validating 2.0.3.
312 //
313 // Registered first, so a gateway's own handler (initGatewayInternetHandler
314 // in wifi.hpp, or a test's stand-in) is always the second: this one
315 // stays silent whenever any other GATEWAY_DATA handler exists.
316 this->callbackList.onPackage(
318 [this](protocol::Variant& variant, std::shared_ptr<T> ingress,
319 uint32_t) {
320 if (this->callbackList.count(protocol::GATEWAY_DATA) > 1) {
321 return false; // this node is a gateway; its handler answers
322 }
323 auto pkg = variant.to<gateway::GatewayDataPackage>();
324 char errorBuf[96];
325 snprintf(errorBuf, sizeof(errorBuf), "Node %u %s",
326 static_cast<unsigned>(this->nodeId),
327 gateway::GATEWAY_NOT_A_GATEWAY_PHRASE);
329 "GATEWAY_DATA from node %u answered: %s\n",
330 static_cast<unsigned>(pkg.originNode), errorBuf);
331 this->sendGatewayAck(pkg, false, 0, TSTRING(errorBuf), ingress);
332 return true;
333 });
334
335 // Seed delivery-confirmation ids with a random value so they do not
336 // restart at 1 after a reboot — a delayed ACK for a pre-reboot
337 // message could otherwise match a fresh message's id and report a
338 // false delivered = true.
340
341 // Per-message delivery confirmation (issue #379): reply with an ACK
342 // when a received application message carries a msgId. Uses the
343 // Variant field peeks — the common no-ack case must not pay for a
344 // full package materialization on every received message.
345 auto autoAck = [this](protocol::Variant& variant, std::shared_ptr<T>,
346 uint32_t) {
347 auto msgId = variant.msgId();
348 if (msgId == 0) return false;
349 auto origin = variant.from();
350 if (origin == 0 || origin == this->nodeId) return false;
351 if (variant.type() == protocol::BROADCAST) {
352 // Stagger broadcast ACKs by nodeId so N nodes do not fire N
353 // unicast ACKs at the sender in the same instant
354 this->queueBroadcastAck(origin, msgId);
355 } else {
356 auto ackPkg = ack::MessageAckPackage(this->nodeId, origin, msgId);
357 this->sendPackage(&ackPkg);
358 }
359 return false;
360 };
361 this->callbackList.onPackage(protocol::SINGLE, autoAck);
362 this->callbackList.onPackage(protocol::BROADCAST, autoAck);
363
364 // Match incoming ACKs to messages awaiting delivery confirmation
365 this->callbackList.onPackage(
367 [this](protocol::Variant& variant, std::shared_ptr<T>, uint32_t) {
368 std::list<ack::DeliveryResult> results;
369 this->ackTracker.collectAck(variant.msgId(), variant.from(),
370 static_cast<uint32_t>(millis()),
371 results);
372 if (!results.empty()) {
373 this->addTask([results]() {
374 for (const auto& result : results) {
375 result.callback(result.nodeId, result.delivered,
376 result.latencyMs);
377 }
378 });
379 }
380 return false;
381 });
382
383 // Add bridge status package handler (Type BRIDGE_STATUS)
384 // This will be called when any node receives a bridge status broadcast
385 this->callbackList.onPackage(
387 [this](protocol::Variant& variant, std::shared_ptr<T>, uint32_t) {
388 // We need to manually parse the JSON since BridgeStatusPackage is in alteriom namespace
389 // and may not be available in all contexts. We'll parse the critical fields directly.
390 JsonDocument doc;
391 TSTRING str;
392 variant.printTo(str);
393 deserializeJson(doc, str);
394 JsonObject obj = doc.as<JsonObject>();
395
396 if (obj["internetConnected"].is<bool>()) {
397 uint32_t bridgeNodeId = obj["from"];
398 if (obj["leaving"] | false) {
399 // A bridge stepping down says so, and is forgotten at once
400 // rather than when its last status ages out — a minute and
401 // more during which every candidate held it healthy.
402 Log(GENERAL, "Bridge %u is stepping down\n", bridgeNodeId);
403 this->forgetBridge(bridgeNodeId);
404 return false;
405 }
406 bool internetConnected = obj["internetConnected"];
407 int8_t routerRSSI = obj["routerRSSI"] | 0;
408 uint8_t routerChannel = obj["routerChannel"] | 0;
409 uint32_t uptime = obj["uptime"] | 0;
410 TSTRING gatewayIP = obj["gatewayIP"].as<TSTRING>();
411 uint32_t timestamp = obj["timestamp"] | 0;
412
413 // Update bridge status
414 this->updateBridgeStatus(bridgeNodeId, internetConnected, routerRSSI,
415 routerChannel, uptime, gatewayIP, timestamp);
416
417 Log(GENERAL, "Bridge status received from %u: Internet %s\n",
418 bridgeNodeId, internetConnected ? "Connected" : "Disconnected");
419 }
420 return false; // Don't consume the package, allow other handlers
421 });
422
423 this->changedConnectionCallbacks.push_back([this](uint32_t nodeId) {
424 Log(MESH_STATUS, "Changed connections in neighbour %u\n", nodeId);
425 if (nodeId != 0) layout::syncLayout<T>((*this), nodeId);
426 });
427 this->droppedConnectionCallbacks.push_back([this](uint32_t nodeId,
428 bool station) {
429 Log(MESH_STATUS, "Dropped connection %u, station %d\n", nodeId, station);
432 });
433 this->newConnectionCallbacks.push_back([](uint32_t nodeId) {
434 Log(MESH_STATUS, "New connection %u\n", nodeId);
435 });
436 }
437
438 void init(Scheduler *scheduler, uint32_t id) {
439 this->setScheduler(scheduler);
440 this->init(id);
441 }
442
443#ifdef PAINLESSMESH_ENABLE_OTA
444 std::shared_ptr<Task> offerOTA(painlessmesh::plugin::ota::Announce announce) {
445 auto announceTask = this->addTask(TASK_SECOND * 60, 60, [this, announce]() {
446 this->sendPackage(&announce);
447 });
448 return announceTask;
449 }
450
451 std::shared_ptr<Task> offerOTA(TSTRING role, TSTRING hardware, TSTRING md5,
452 size_t noPart, bool forced = false,
453 bool broadcasted = false, bool compressed = false) {
455 announce.md5 = md5;
456 announce.role = role;
457 announce.hardware = hardware;
458 announce.from = this->nodeId;
459 announce.noPart = noPart;
460 announce.forced = forced;
461 announce.broadcasted = broadcasted;
462 announce.compressed = compressed;
463 return offerOTA(announce);
464 }
465
466 void initOTASend(
468 size_t otaPartSize) {
470 *this->mScheduler, (*this), callback, otaPartSize);
471 }
472 void initOTAReceive(TSTRING role = "",
473 std::function<void(int, int)> progress_cb = NULL) {
475 *this->mScheduler, (*this), role, progress_cb);
476 }
477#endif
478
492 void setRoot(bool on = true) { this->root = on; };
493
501 void setContainsRoot(bool on = true) { shouldContainRoot = on; };
502
506 bool isRoot() { return this->root; };
507
511 void setDebugMsgTypes(uint16_t types) { Log.setLogLevel(types); }
512
516 void stop() {
517 using namespace logger;
518 // Close all connections
519 while (this->subs.size() > 0) {
520 auto conn = this->subs.begin();
521 (*conn)->close();
522 this->eraseClosedConnections();
523 }
524 // Disable the ack task BEFORE nulling the member: if stop() was
525 // reached from inside the task's own callback, the lambda's
526 // this->ackCheckTask guard is useless once the member is null and
527 // the task would keep firing forever on an external scheduler.
528 if (ackCheckTask) ackCheckTask->disable();
529 if (broadcastAckTask) broadcastAckTask->disable();
530 ackTracker.clear();
531 pendingBroadcastAcks.clear();
532 ackCheckTask = nullptr;
533 broadcastAckTask = nullptr;
534
536
537 newConnectionCallbacks.clear();
538 droppedConnectionCallbacks.clear();
539 changedConnectionCallbacks.clear();
540
541 if (!isExternalScheduler) {
542 if (mScheduler && mScheduler->getCurrentTask() != nullptr) {
543 // stop() was reached from inside a scheduler callback (e.g. a
544 // delivery/timeout callback). Deleting the scheduler here would
545 // free the object whose execute() is still on the call stack —
546 // the same bug class as issue #373. Retire it until execute()
547 // unwinds, then reclaim it during the next update.
548 retiredSchedulers.push_back(mScheduler);
549 mScheduler = nullptr;
550 } else {
551 delete mScheduler;
552 mScheduler = nullptr;
553 }
554 }
555 }
556
562 void update(void) {
563 if (semaphoreTake()) {
565 reclaimRetiredSchedulers();
566 // Check if something is executed (returns false)
567 if (!mScheduler->execute())
568 // DEBUG, not GENERAL: this fires every time any task runs, which on a
569 // busy node is thousands of lines a second. At GENERAL it drowned the
570 // level that carries the bridge and gateway diagnostics —
571 // sendBridgeStatus(), "Bridge status received from %u" — so nobody
572 // can turn those on to investigate a failover without being flooded.
573 Log(logger::DEBUG, "update(): Scheduler executed a task\n");
574 semaphoreGive();
575 }
576 return;
577 }
578
586 bool sendSingle(uint32_t destId, TSTRING msg) {
587 return sendSingle(destId, msg, SendOptions());
588 }
589
598 bool sendSingle(uint32_t destId, TSTRING msg, uint8_t priorityLevel) {
599 SendOptions options;
600 options.priority = priorityLevel;
601 return sendSingle(destId, msg, options);
602 }
603
620 bool sendSingle(uint32_t destId, TSTRING msg, const SendOptions& options) {
621 Log(logger::COMMUNICATION, "sendSingle(): dest=%u msg=%s priority=%u%s\n",
622 destId, msg.c_str(), options.priority,
623 options.ackCallback ? " with ack" : "");
624 auto single = painlessmesh::protocol::Single(this->nodeId, destId, msg);
625 single.priority = options.priority;
626 if (!options.ackCallback)
627 return painlessmesh::router::send<T>(single, (*this));
628 if (ackTracker.full()) {
629 Log(logger::ERROR, "sendSingle(): pending-ack limit reached\n");
630 return false;
631 }
632 single.msgId = ackTracker.nextMessageId();
633 if (!painlessmesh::router::send<T>(single, (*this))) return false;
634 ackTracker.track(single.msgId, {destId}, options.ackCallback,
635 options.ackTimeoutMs, static_cast<uint32_t>(millis()));
636 this->ensureAckScheduling();
637 return true;
638 }
639
671 bool sendSingle(uint32_t destId, TSTRING msg,
672 ack::deliveryCallback_t ackCallback,
673 uint32_t ackTimeoutMs = 5000) {
674 SendOptions options;
675 options.ackCallback = ackCallback;
676 options.ackTimeoutMs = ackTimeoutMs;
677 return sendSingle(destId, msg, options);
678 }
679
686 bool sendBroadcast(TSTRING msg, bool includeSelf = false) {
687 return sendBroadcast(msg, SendOptions(), includeSelf);
688 }
689
712 bool sendBroadcast(TSTRING msg, const SendOptions& options,
713 bool includeSelf = false) {
714 using namespace logger;
715 Log(COMMUNICATION, "sendBroadcast(): msg=%s priority=%u%s\n", msg.c_str(),
716 options.priority, options.ackCallback ? " with ack" : "");
717 painlessmesh::protocol::Broadcast pkg(this->nodeId, 0, msg);
718 pkg.priority = options.priority;
719
720 if (!options.ackCallback) {
721 auto success = router::broadcast<protocol::Broadcast, T>(pkg, (*this), 0);
722 if (includeSelf) {
723 protocol::Variant var(pkg);
724 this->callbackList.execute(var.type(), var, NULL, 0);
725 }
726 return success > 0;
727 }
728
729 if (ackTracker.full()) {
730 Log(ERROR, "sendBroadcast(): pending-ack limit reached\n");
731 return false;
732 }
733 // Broadcast recipients intentionally jitter ACK replies across this
734 // window. A shorter deadline would deterministically time out nodes
735 // whose slot falls after it, even when delivery is immediate.
736 auto ackTimeoutMs = options.ackTimeoutMs;
737 if (ackTimeoutMs < ACK_BROADCAST_MIN_TIMEOUT_MS)
738 ackTimeoutMs = ACK_BROADCAST_MIN_TIMEOUT_MS;
739 auto expected = this->getNodeList(false);
740 pkg.msgId = ackTracker.nextMessageId();
741 auto success = router::broadcast<protocol::Broadcast, T>(pkg, (*this), 0);
742 const bool trackingPeers = success > 0 && !expected.empty();
743 if (trackingPeers) {
744 ackTracker.track(pkg.msgId, expected, options.ackCallback, ackTimeoutMs,
745 static_cast<uint32_t>(millis()));
746 this->ensureAckScheduling();
747 }
748 if (includeSelf) {
749 protocol::Variant var(pkg);
750 this->callbackList.execute(var.type(), var, NULL, 0);
751 }
752 // Do not access mesh state after self-delivery: an onReceive callback
753 // may have called stop(). Local delivery still occurs without peers,
754 // while the return value continues to describe peer ACK tracking.
755 return trackingPeers;
756 }
757
787 bool sendBroadcast(TSTRING msg, bool includeSelf,
788 ack::deliveryCallback_t ackCallback,
789 uint32_t ackTimeoutMs = 5000) {
790 SendOptions options;
791 options.ackCallback = ackCallback;
792 options.ackTimeoutMs = ackTimeoutMs;
793 return sendBroadcast(msg, options, includeSelf);
794 }
795
806 size_t checkAcks() {
807 std::list<ack::DeliveryResult> results;
808 auto pending = ackTracker.collectExpired(static_cast<uint32_t>(millis()),
809 results);
810 if (!results.empty()) {
811 this->addTask([results]() {
812 for (const auto& result : results) {
813 result.callback(result.nodeId, result.delivered, result.latencyMs);
814 }
815 });
816 }
817 return pending;
818 }
819
821 size_t pendingAcks() const { return ackTracker.pending(); }
822
831 template <
832 typename Priority,
833 typename IncludeSelf = bool,
834 typename std::enable_if<
835 (std::is_integral<typename std::decay<Priority>::type>::value ||
836 std::is_enum<typename std::decay<Priority>::type>::value) &&
837 !std::is_same<typename std::decay<Priority>::type, bool>::value &&
838 std::is_same<typename std::decay<IncludeSelf>::type, bool>::value,
839 int>::type = 0>
840 bool sendBroadcast(TSTRING msg, Priority priorityLevel,
841 IncludeSelf includeSelf = false) {
842 SendOptions options;
843 options.priority = static_cast<uint8_t>(priorityLevel);
844 return sendBroadcast(msg, options, includeSelf);
845 }
846
854 bool startDelayMeas(uint32_t id) {
855 using namespace logger;
856 Log(S_TIME, "startDelayMeas(): NodeId %u\n", id);
857 auto conn = painlessmesh::router::findRoute<T>((*this), id);
858 if (!conn) return false;
859 return router::send<protocol::TimeDelay, T>(
860 protocol::TimeDelay(this->nodeId, id, this->getNodeTime()), conn);
861 }
862
887 using namespace painlessmesh;
888 this->callbackList.onPackage(
890 [onReceive](protocol::Variant &variant, std::shared_ptr<T>, uint32_t) {
891 auto pkg = variant.to<protocol::Single>();
892 onReceive(pkg.from, pkg.msg);
893 return false;
894 });
895 this->callbackList.onPackage(
897 [onReceive](protocol::Variant &variant, std::shared_ptr<T>, uint32_t) {
898 auto pkg = variant.to<protocol::Broadcast>();
899 onReceive(pkg.from, pkg.msg);
900 return false;
901 });
902 }
903
915 Log(logger::GENERAL, "onNewConnection():\n");
916 newConnectionCallbacks.push_back([onNewConnection](uint32_t nodeId) {
917 if (nodeId != 0) onNewConnection(nodeId);
918 });
919 }
920
931 droppedConnectionCallbacks.push_back(
932 [onDroppedConnection](uint32_t nodeId, bool station) {
933 if (nodeId != 0) onDroppedConnection(nodeId);
934 });
935 }
936
946 Log(logger::GENERAL, "onChangedConnections():\n");
947 changedConnectionCallbacks.push_back(
948 [onChangedConnections](uint32_t nodeId) {
949 if (nodeId != 0) onChangedConnections();
950 });
951 }
952
967 Log(logger::GENERAL, "onNodeTimeAdjusted():\n");
968 nodeTimeAdjustedCallback = onTimeAdjusted;
969 }
970
984 Log(logger::GENERAL, "onNodeDelayReceived():\n");
985 nodeDelayReceivedCallback = onDelayReceived;
986 }
987
1004 Log(logger::GENERAL, "onBridgeStatusChanged():\n");
1005 bridgeStatusChangedCallback = onBridgeStatusChanged;
1006 }
1007
1047 // Always require healthy bridge status to prevent false positives
1048 // when mesh is disconnected. Stale bridge data is unreliable.
1049 for (const auto& bridge : knownBridges) {
1050 if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) {
1051 return true;
1052 }
1053 }
1054 return false;
1055 }
1056
1072 std::vector<uint32_t> getNodesWithInternet() {
1073 std::vector<uint32_t> nodesWithInternet;
1074 for (const auto& bridge : knownBridges) {
1075 if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) {
1076 nodesWithInternet.push_back(bridge.nodeId);
1077 }
1078 }
1079 return nodesWithInternet;
1080 }
1081
1087 std::vector<BridgeInfo> getBridges() {
1088 return knownBridges;
1089 }
1090
1110 BridgeInfo* primary = nullptr;
1111 int8_t bestRSSI = -127; // Worst possible RSSI
1112
1113 // Always require healthy bridge status to prevent routing to stale/unreachable bridges
1114 for (auto& bridge : knownBridges) {
1115 if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) {
1116 if (bridge.routerRSSI > bestRSSI) {
1117 bestRSSI = bridge.routerRSSI;
1118 primary = &bridge;
1119 }
1120 }
1121 }
1122
1123 return primary;
1124 }
1125
1139 BridgeInfo* best = nullptr;
1140 int8_t bestRSSI = -127; // Worst possible RSSI
1141
1142 for (auto& bridge : knownBridges) {
1143 // Include any bridge that reported Internet at some point
1144 if (bridge.internetConnected) {
1145 if (bridge.routerRSSI > bestRSSI) {
1146 bestRSSI = bridge.routerRSSI;
1147 best = &bridge;
1148 }
1149 }
1150 }
1151
1152 return best;
1153 }
1154
1169 // Check if we have any active connections in our subs list
1170 for (auto& conn : this->subs) {
1171 if (conn && conn->connected()) {
1172 return true;
1173 }
1174 }
1175 return false;
1176 }
1177
1183 bool isBridge() {
1184 return this->root;
1185 }
1186
1187 // ==================== Capacity (ESP8266) ====================
1188
1194 size_t apChildren() {
1195 size_t n = 0;
1196 for (auto&& sub : this->subs) {
1197 if (sub && sub->connected() && !sub->station) ++n;
1198 }
1199 return n;
1200 }
1201
1214 bool overCapacity() { return _overCapacity; }
1215
1221 bool capacityCheck(uint32_t freeHeap, uint32_t floor) {
1222 using namespace logger;
1223 size_t children = apChildren();
1224 bool over = freeHeap < floor && children > 1;
1225 uint32_t now = millis();
1226 if (over && (!_overCapacity ||
1227 now - _capacityWarnedAt > CAPACITY_WARN_INTERVAL_MS)) {
1228 Log(ERROR,
1229 "capacity: %u B free with %u AP children on ESP8266. This part is "
1230 "specified as a leaf in meshes of this size; init(..., maxconn=0) "
1231 "or reduce the mesh.\n",
1232 freeHeap, children);
1233 _capacityWarnedAt = now;
1234 }
1235 _overCapacity = over;
1236 return over;
1237 }
1238
1239 static constexpr uint32_t ESP8266_CAPACITY_FLOOR = 12 * 1024;
1240 static constexpr uint32_t CAPACITY_WARN_INTERVAL_MS = 5 * 60 * 1000;
1241
1242 // ==================== Gateway Status API ====================
1243
1259 BridgeInfo* primary = getPrimaryBridge();
1260 if (primary == nullptr) {
1261 return false;
1262 }
1263 return primary->nodeId == this->nodeId;
1264 }
1265
1284 BridgeInfo* primary = getPrimaryBridge();
1285 if (primary == nullptr) {
1286 return 0;
1287 }
1288 return primary->nodeId;
1289 }
1290
1310 std::list<uint32_t> getGateways() {
1311 // Convert vector to list for API consistency with getNodeList()
1312 // The copy overhead is acceptable since gateway lists are typically small
1313 auto nodes = getNodesWithInternet();
1314 return std::list<uint32_t>(nodes.begin(), nodes.end());
1315 }
1316
1334 size_t count = 0;
1335 for (const auto& bridge : knownBridges) {
1336 if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) count++;
1337 }
1338 return count;
1339 }
1340
1363 Log(logger::GENERAL, "onGatewayChanged():\n");
1364 gatewayChangedCallback = callback;
1365 }
1366
1372 void setBridgeStatusInterval(uint32_t intervalMs) {
1373 bridgeStatusIntervalMs = intervalMs;
1374 }
1375
1381 void setBridgeTimeout(uint32_t timeoutMs) {
1382 bridgeTimeoutMs = timeoutMs;
1383 }
1384
1390 void enableBridgeStatusBroadcast(bool enabled) {
1391 bridgeStatusBroadcastEnabled = enabled;
1392 }
1393
1416 bool forgetBridge(uint32_t bridgeNodeId) {
1417 size_t sizeBefore = knownBridges.size();
1418 knownBridges.erase(
1419 std::remove_if(knownBridges.begin(), knownBridges.end(),
1420 [bridgeNodeId](const BridgeInfo& bridge) {
1421 return bridge.nodeId == bridgeNodeId;
1422 }),
1423 knownBridges.end());
1424 return knownBridges.size() != sizeBefore;
1425 }
1426
1428 using namespace logger;
1429 size_t sizeBefore = knownBridges.size();
1430
1431 // Remove bridges that haven't reported within the timeout period
1432 knownBridges.erase(
1433 std::remove_if(knownBridges.begin(), knownBridges.end(),
1434 [this](const BridgeInfo& bridge) {
1435 return !bridge.isHealthy(bridgeTimeoutMs);
1436 }),
1437 knownBridges.end());
1438
1439 size_t removed = sizeBefore - knownBridges.size();
1440 if (removed > 0) {
1441 Log(GENERAL, "cleanupExpiredBridges(): Removed %lu expired bridges\n",
1442 static_cast<unsigned long>(removed));
1443 }
1444 }
1445
1460 using namespace logger;
1461 if (bridgeCleanupTask != nullptr) {
1462 Log(GENERAL, "enableBridgeCleanup(): Already enabled\n");
1463 return;
1464 }
1465
1466 Log(GENERAL, "enableBridgeCleanup(): Starting cleanup task (interval: %u ms)\n",
1467 bridgeTimeoutMs);
1468
1469 bridgeCleanupTask = this->addTask(
1470 bridgeTimeoutMs,
1471 TASK_FOREVER,
1472 [this]() {
1473 cleanupExpiredBridges();
1474 });
1475 }
1476
1481 using namespace logger;
1482 if (bridgeCleanupTask != nullptr) {
1483 bridgeCleanupTask->disable();
1484 // Task remains in scheduler (disabled) — negligible overhead.
1485 // The scheduler will skip it on each cycle.
1486 bridgeCleanupTask = nullptr;
1487 Log(GENERAL, "disableBridgeCleanup(): Cleanup task disabled\n");
1488 }
1489 }
1490
1497 return bridgeCleanupTask != nullptr && bridgeCleanupTask->isEnabled();
1498 }
1499
1500 // ==================== Local Internet Health Check API ====================
1501
1522 return internetHealthChecker.hasLocalInternet();
1523 }
1524
1540 if (hasLocalInternet()) return true;
1541 if (!isInternetHealthCheckEnabled()) return false;
1542 if (internetHealthChecker.checkOnDemand()) {
1544 "uplinkServesLocally(): on-demand probe found the uplink up\n");
1545 return true;
1546 }
1547 return false;
1548 }
1549
1569 Log(logger::GENERAL, "onLocalInternetChanged():\n");
1570 localInternetChangedCallback = callback;
1571 internetHealthChecker.onConnectivityChanged(callback);
1572 }
1573
1590 return internetHealthChecker.getStatus();
1591 }
1592
1602 internetHealthChecker.setConfig(config);
1603 }
1604
1613 void setInternetCheckTarget(const TSTRING& host, uint16_t port = 53) {
1614 internetHealthChecker.setCheckTarget(host, port);
1615 }
1616
1622 void setInternetCheckInterval(uint32_t intervalMs) {
1623 internetHealthChecker.setCheckInterval(intervalMs);
1624 }
1625
1631 void setInternetCheckTimeout(uint32_t timeoutMs) {
1632 internetHealthChecker.setCheckTimeout(timeoutMs);
1633 }
1634
1644 return internetHealthChecker.checkNow();
1645 }
1646
1662 using namespace logger;
1663 if (internetHealthCheckTask != nullptr) {
1664 if (internetHealthCheckTask->isEnabled()) {
1665 Log(GENERAL, "enableInternetHealthCheck(): Already enabled\n");
1666 return;
1667 }
1668 // A stop()/re-init cycle -- what bridge promotion does before calling
1669 // initAsBridge() again -- disables every task and drops it from the
1670 // reusable pool, but this member still points at it. Refusing here
1671 // would leave a re-initialised node with no health check at all, and
1672 // so with hasLocalInternet() stuck false (issue #445). Re-arm instead,
1673 // releasing the spent task so the pool can reclaim it.
1674 internetHealthCheckTask = nullptr;
1675 }
1676
1677 Log(GENERAL, "enableInternetHealthCheck(): Starting health check task (interval: %u ms)\n",
1678 internetHealthChecker.getCheckInterval());
1679
1680 internetHealthCheckTask = this->addTask(
1681 internetHealthChecker.getCheckInterval(),
1682 TASK_FOREVER,
1683 [this]() {
1684 this->internetHealthChecker.checkNow();
1685 });
1686 }
1687
1692 using namespace logger;
1693 if (internetHealthCheckTask != nullptr) {
1694 internetHealthCheckTask->disable();
1695 internetHealthCheckTask = nullptr;
1696 Log(GENERAL, "disableInternetHealthCheck(): Health check task disabled\n");
1697 }
1698 }
1699
1706 return internetHealthCheckTask != nullptr && internetHealthCheckTask->isEnabled();
1707 }
1708
1713 internetHealthChecker.resetStats();
1714 }
1715
1716#ifdef PAINLESSMESH_BOOST
1722 void setMockInternetConnected(bool connected) {
1723 internetHealthChecker.setMockConnected(connected);
1724 }
1725#endif
1726
1727 // ==================== Send to Internet API ====================
1728
1775 TSTRING destination,
1776 TSTRING payload,
1777 internetResultCallback_t callback,
1778 uint8_t priority = static_cast<uint8_t>(gateway::GatewayPriority::PRIORITY_NORMAL)) {
1779 return startInternetRequest(destination, payload, callback, nullptr, priority);
1780 }
1781
1794 TSTRING destination,
1795 TSTRING payload,
1797 uint8_t priority = static_cast<uint8_t>(gateway::GatewayPriority::PRIORITY_NORMAL)) {
1798 return startInternetRequest(destination, payload, nullptr, onResult, priority);
1799 }
1800
1801 private:
1803 static void deliverInternetResult(const PendingInternetRequest& request,
1804 InternetResult result) {
1805 result.messageId = request.messageId;
1806 result.attempts = request.attempts;
1807 // A request that never left this node -- refused for want of a mesh or a
1808 // gateway, or whose every routing attempt failed to send -- cannot have
1809 // reached a server, whatever ended it: resending it cannot duplicate.
1810 if (!result.success && request.attempts == 0) {
1811 result.retryable = true;
1812 }
1813 if (request.onResult) {
1814 request.onResult(result);
1815 } else if (request.callback) {
1816 request.callback(result.success, result.httpStatus, result.error);
1817 }
1818 }
1819
1820 static InternetResult failedInternetResult(const TSTRING& error,
1821 uint16_t httpStatus = 0) {
1822 InternetResult result;
1823 result.httpStatus = httpStatus;
1824 result.error = error;
1825 return result;
1826 }
1827
1828 uint32_t startInternetRequest(
1829 TSTRING destination,
1830 TSTRING payload,
1831 internetResultCallback_t callback,
1833 uint8_t priority) {
1834 using namespace logger;
1835
1836 // Generate unique message ID
1837 uint32_t messageId = gateway::GatewayDataPackage::generateMessageId(this->nodeId);
1838
1839 Log(COMMUNICATION, "sendToInternet(): msgId=%u dest=%s priority=%u\n",
1840 messageId, destination.c_str(), priority);
1841
1842 // A shared gateway can execute its own gateway package locally. Keep the
1843 // same package handler, pending-request tracking and callback semantics,
1844 // but do not require a mesh peer merely to reach this node's own uplink.
1845 if (uplinkServesLocally()) {
1846 PendingInternetRequest request;
1847 request.messageId = messageId;
1848 request.timestamp = millis();
1849 request.retryCount = 0;
1850 request.maxRetries = internetRetryCount;
1851 request.priority = priority;
1852 request.timeoutMs = internetRequestTimeout;
1853 request.retryDelayMs = internetRetryDelay;
1854 request.gatewayNodeId = this->nodeId;
1855 request.destination = destination;
1856 request.payload = payload;
1857 request.callback = callback;
1858 request.onResult = onResult;
1860 // Handed to this node's own handler below, unconditionally.
1861 request.attempts = 1;
1862 const uint32_t nonce = request.requestNonce;
1863 pendingInternetRequests[messageId] = request;
1864
1865 dispatchInternetRequestLocally(messageId, priority, 0, destination,
1866 payload, nonce);
1867 return messageId;
1868 }
1869
1870 // A request refused before it is tracked still reports through the
1871 // callback it was given, on the scheduler rather than inside this call.
1872 PendingInternetRequest refused;
1873 refused.callback = callback;
1874 refused.onResult = onResult;
1875 refused.attempts = 0;
1876
1877 // Validate mesh connectivity before attempting to send
1878 if (!hasActiveMeshConnections()) {
1879 Log(ERROR, "sendToInternet(): No active mesh connections\n");
1880 if (callback || onResult) {
1881 this->addTask([refused]() {
1882 deliverInternetResult(refused, failedInternetResult(
1883 "No mesh connections - cannot route to gateway"));
1884 });
1885 }
1886 return 0;
1887 }
1888
1889 // Find the best gateway to route through
1890 BridgeInfo* gateway = getPrimaryBridge();
1891 if (gateway == nullptr) {
1892 Log(ERROR, "sendToInternet(): No gateway available\n");
1893 if (callback || onResult) {
1894 this->addTask([refused]() {
1895 deliverInternetResult(refused, failedInternetResult("No gateway available"));
1896 });
1897 }
1898 return 0;
1899 }
1900
1901 // Create and store the pending request
1902 PendingInternetRequest request;
1903 request.messageId = messageId;
1904 request.timestamp = millis();
1905 request.retryCount = 0;
1906 request.maxRetries = internetRetryCount;
1907 request.priority = priority;
1908 request.timeoutMs = internetRequestTimeout;
1909 request.retryDelayMs = internetRetryDelay;
1910 request.gatewayNodeId = gateway->nodeId;
1911 request.destination = destination;
1912 request.payload = payload;
1913 request.callback = callback;
1914 request.onResult = onResult;
1916
1917 // Store pending request
1918 pendingInternetRequests[messageId] = request;
1919
1920 // Create gateway data package
1922 pkg.from = this->nodeId;
1923 pkg.dest = gateway->nodeId;
1924 pkg.messageId = messageId;
1925 pkg.originNode = this->nodeId;
1926 pkg.timestamp = this->getNodeTime();
1927 pkg.priority = priority;
1928 pkg.destination = destination;
1929 pkg.payload = payload;
1930 pkg.contentType = "application/json";
1931 pkg.retryCount = 0;
1932 pkg.requiresAck = true;
1933 pkg.requestNonce = request.requestNonce;
1934
1935 // Send the package with priority
1936 bool sent = false;
1937 auto conn = painlessmesh::router::findRoute<T>((*this), gateway->nodeId);
1938 if (conn) {
1939 // HTTP runs on the gateway's cooperative scheduler. Preserve this
1940 // requester's route until the corresponding acknowledgment can return.
1942 sent = painlessmesh::router::sendWithPriority(pkg, conn, priority);
1943 if (!sent) {
1944 Log(ERROR, "sendToInternet(): sendWithPriority failed to gateway %u (send buffer full?)\n", gateway->nodeId);
1945 }
1946 } else {
1947 Log(ERROR, "sendToInternet(): No route to gateway %u\n", gateway->nodeId);
1948 }
1949
1950 if (!sent) {
1951 Log(ERROR, "sendToInternet(): Failed to send to gateway %u, scheduling retry\n", gateway->nodeId);
1952 // Schedule retry or failure callback
1953 scheduleInternetRetry(messageId);
1954 } else {
1955 Log(COMMUNICATION, "sendToInternet(): Sent to gateway %u\n", gateway->nodeId);
1956 // Only a send that left this node is an attempt.
1957 pendingInternetRequests[messageId].attempts = 1;
1958 // Schedule timeout check
1959 scheduleInternetTimeout(messageId);
1960 }
1961
1962 return messageId;
1963 }
1964
1965 public:
1971 void setInternetRequestTimeout(uint32_t timeoutMs) {
1972 internetRequestTimeout = timeoutMs;
1973 }
1974
1980 void setInternetRetryCount(uint8_t retryCount) {
1981 internetRetryCount = retryCount;
1982 }
1983
1989 void setInternetRetryDelay(uint32_t delayMs) {
1990 internetRetryDelay = delayMs;
1991 }
1992
2007 tcpRetryConfig = painlessmesh::tcp::clampTcpRetryConfig(config);
2008 }
2009
2016 return tcpRetryConfig;
2017 }
2018
2025 return pendingInternetRequests.size();
2026 }
2027
2034 bool cancelInternetRequest(uint32_t messageId) {
2035 auto it = pendingInternetRequests.find(messageId);
2036 if (it != pendingInternetRequests.end()) {
2037 const PendingInternetRequest cancelled = it->second;
2038 pendingInternetRequests.erase(it);
2039 if (cancelled.callback || cancelled.onResult) {
2040 this->addTask([cancelled]() {
2041 deliverInternetResult(cancelled, failedInternetResult("Request cancelled"));
2042 });
2043 }
2044 Log(logger::GENERAL, "cancelInternetRequest(): Cancelled msgId=%u\n", messageId);
2045 return true;
2046 }
2047 return false;
2048 }
2049
2064 using namespace logger;
2065 Log(GENERAL, "enableSendToInternet(): Enabling Internet routing API\n");
2066
2067 // Register handler for gateway acknowledgment packages
2068 this->callbackList.onPackage(
2070 [this](protocol::Variant& variant, std::shared_ptr<T>, uint32_t) {
2071 auto ack = variant.to<gateway::GatewayAckPackage>();
2072 this->handleGatewayAck(ack);
2073 return false; // Don't consume, allow other handlers
2074 });
2075
2076 // Start cleanup task for timed-out requests
2077 if (internetCleanupTask == nullptr) {
2078 internetCleanupTask = this->addTask(
2079 5000, // Check every 5 seconds
2080 TASK_FOREVER,
2081 [this]() {
2082 this->cleanupTimedOutRequests();
2083 });
2084 }
2085
2086 sendToInternetEnabled = true;
2087 }
2088
2093 using namespace logger;
2094 Log(GENERAL, "disableSendToInternet(): Disabling Internet routing API\n");
2095
2096 if (internetCleanupTask != nullptr) {
2097 internetCleanupTask->disable();
2098 internetCleanupTask = nullptr;
2099 }
2100
2101 // Cancel all pending requests
2102 for (auto& pair : pendingInternetRequests) {
2103 deliverInternetResult(pair.second, failedInternetResult("API disabled"));
2104 }
2105 pendingInternetRequests.clear();
2106
2107 sendToInternetEnabled = false;
2108 }
2109
2116 return sendToInternetEnabled;
2117 }
2118
2119 private:
2123 public:
2124 // Public: the gateway handler in wifi.hpp, a derived class, answers with it.
2138 void sendGatewayAck(const gateway::GatewayDataPackage& request, bool success,
2139 uint16_t httpStatus, const TSTRING& error,
2140 std::shared_ptr<T> ingressConnection = nullptr,
2141 const TSTRING& response = TSTRING(), int8_t retryable = -1,
2142 uint32_t retryAfterMs = 0) {
2143 using namespace logger;
2144
2146 ack.from = this->nodeId;
2147 ack.dest = request.originNode;
2148 ack.messageId = request.messageId;
2149 ack.originNode = request.originNode;
2150 ack.success = success;
2151 ack.httpStatus = httpStatus;
2152 ack.error = error;
2153 ack.timestamp = this->getNodeTime();
2154 ack.response = response;
2155 ack.retryable = retryable;
2156 ack.retryAfterMs = retryAfterMs;
2157
2158 if (request.originNode == this->nodeId) {
2159 // Completed from the scheduler, not from inside the gateway handler
2160 // that called this. There the handler's HTTPClient, its WiFiClient
2161 // and the response buffers are all still allocated, and the
2162 // application's callback runs on what is left: on the HIL rig an
2163 // ESP8266 shared gateway with ~11 KB free delivered its own result
2164 // into a callback that could no longer allocate the event it built.
2165 //
2166 // The ack is moved into one heap block and handed to the handler as
2167 // it is. It used to be copied into the task's closure, copied again
2168 // inside it, serialised into a JSON document by the Variant and
2169 // parsed back into a third package by the handler -- the reply
2170 // excerpt and the error text five times over on the heap the
2171 // handler had already spent -- and on the rig the closure's own
2172 // 76-byte allocation was the one that failed (#469). Nothing but
2173 // handleGatewayAck() listens for GATEWAY_ACK, and a request that
2174 // originated here exists only if enableSendToInternet() registered
2175 // it, so the round trip through the callback list bought nothing.
2176 // A shared_ptr rather than a C++14 init-capture: the ESP32 Arduino
2177 // 2.x core still builds as gnu++11.
2178 auto held = std::make_shared<gateway::GatewayAckPackage>(std::move(ack));
2179 this->addTask([this, held]() { this->handleGatewayAck(*held); });
2180 Log(COMMUNICATION,
2181 "Completing local GATEWAY_ACK from the scheduler (success=%d, http=%d)\n",
2182 success, httpStatus);
2183 return;
2184 }
2185
2186 auto conn = router::findRoute<T>((*this), request.originNode);
2187 if (!conn && ingressConnection) {
2188 conn = ingressConnection;
2189 Log(COMMUNICATION,
2190 "Routing GATEWAY_ACK to node %u through request ingress while "
2191 "topology converges\n",
2192 request.originNode);
2193 }
2194 if (conn) {
2195 protocol::Variant variant(&ack);
2196 router::send(std::move(variant), conn);
2197 Log(COMMUNICATION, "Sent GATEWAY_ACK to node %u (success=%d, http=%d)\n",
2198 request.originNode, success, httpStatus);
2199 } else {
2200 Log(ERROR, "Failed to send GATEWAY_ACK: no route to node %u\n",
2201 request.originNode);
2202 }
2203 }
2204
2205 private:
2206 void handleGatewayAck(const gateway::GatewayAckPackage& ack) {
2207 using namespace logger;
2208 Log(COMMUNICATION, "handleGatewayAck(): msgId=%u success=%d http=%u\n",
2209 ack.messageId, ack.success, ack.httpStatus);
2210
2211 auto it = pendingInternetRequests.find(ack.messageId);
2212 if (it == pendingInternetRequests.end()) {
2213 Log(GENERAL, "handleGatewayAck(): Unknown message ID %u\n", ack.messageId);
2214 return;
2215 }
2216
2217 PendingInternetRequest& request = it->second;
2218
2219 InternetResult result;
2220 result.success = ack.success;
2221 result.httpStatus = ack.httpStatus;
2222 result.error = ack.error;
2223 result.response = ack.response;
2224
2225 // Check if this is a success response
2226 if (ack.success) {
2227 // Taken out of the map, not copied out of it, and the result handed
2228 // on rather than copied: on an ESP8266 shared gateway every extra
2229 // copy of the destination, the payload and the reply excerpt lands
2230 // on a heap the request itself has nearly spent (#469).
2231 PendingInternetRequest done = std::move(request);
2232 pendingInternetRequests.erase(it);
2233 deliverInternetResult(done, std::move(result));
2234 return;
2235 }
2236
2237 // The node this request went to is not a gateway (issue found on the
2238 // HIL rig while validating 2.0.3): it rebooted or was reflashed as a
2239 // regular node without announcing it, and its last bridge status is
2240 // still inside bridgeTimeoutMs. Forget it and send again through the
2241 // next gateway at once. The retry budget is not spent: nothing was
2242 // attempted against the Internet, and every such answer removes one
2243 // entry from knownBridges, so this cannot loop.
2245 gateway::GATEWAY_NOT_A_GATEWAY_PHRASE)) {
2246 const uint32_t staleGateway = request.gatewayNodeId;
2247 bool forgot = this->forgetBridge(staleGateway);
2248 if (ack.from != staleGateway) forgot = this->forgetBridge(ack.from) || forgot;
2249 Log(ERROR, "handleGatewayAck(): %s; forgot it as a gateway for msgId=%u\n",
2250 ack.error.c_str(), ack.messageId);
2251
2252 if (!forgot) {
2253 // Nothing to forget, so a redispatch would reach the same node.
2254 // Fall through to the ordinary failure path below.
2255 } else if (this->getPrimaryBridge() == nullptr &&
2256 !this->uplinkServesLocally()) {
2257 // No gateway left right now. On the rig the live bridge's first
2258 // status arrived half a second after the ex-bridge answered, so an
2259 // accepted request rides the ordinary retry backoff rather than
2260 // failing on the spot; retryInternetRequest() reschedules while no
2261 // gateway is known, and the final callback carries this reason.
2262 char errorBuf[160];
2263 snprintf(errorBuf, sizeof(errorBuf),
2264 "No Internet gateway available: %s", ack.error.c_str());
2265 request.lastError = TSTRING(errorBuf);
2266 scheduleInternetRetry(ack.messageId);
2267 return;
2268 } else {
2269 const uint32_t messageId = ack.messageId;
2270 this->addTask([this, messageId]() {
2271 this->retryInternetRequest(messageId);
2272 });
2273 return;
2274 }
2275 }
2276
2277 // Failure: may the identical request be sent again?
2278 //
2279 // A gateway that says so decides (2.1.0 and later): it saw the transport
2280 // error or the status, and it knows whether the request can have reached
2281 // the server. Resending one that may have been processed delivers it
2282 // twice -- on the rig one timed-out send was issued four times -- so only
2283 // a request that cannot have arrived, or one the server told us to bring
2284 // back (429, 503), is retried.
2285 bool isRetryable = false;
2286 uint32_t minDelayMs = 0;
2287 if (ack.retryable >= 0) {
2288 isRetryable = ack.retryable == 1;
2289 if (isRetryable && ack.retryAfterMs > gateway::GATEWAY_RETRY_AFTER_MAX_MS) {
2290 // The server asked to wait longer than a retry will: say when, and
2291 // leave the resend to the application.
2292 isRetryable = false;
2293 char errorBuf[224];
2294 snprintf(errorBuf, sizeof(errorBuf), "%s (the server asked to retry after %lu s)",
2295 ack.error.c_str(),
2296 static_cast<unsigned long>(ack.retryAfterMs / 1000));
2297 result.error = TSTRING(errorBuf);
2298 } else if (isRetryable) {
2299 minDelayMs = ack.retryAfterMs;
2300 }
2301 } else {
2302 // A gateway that predates the field. Keep its behaviour -- it has
2303 // retried network errors and 5xx since 1.x -- except for a 2xx: the
2304 // server answered, so a resend is a second copy.
2305 isRetryable = legacyAckIsRetryable(ack);
2306 }
2307 result.retryable = isRetryable;
2308
2309 // If retryable and have retries left, schedule retry
2310 if (isRetryable && request.retryCount < request.maxRetries) {
2311 Log(COMMUNICATION, "handleGatewayAck(): Scheduling retry for msgId=%u (attempt %u/%u)\n",
2312 ack.messageId, request.retryCount + 1, request.maxRetries);
2313 request.lastError = result.error;
2314 scheduleInternetRetry(ack.messageId, minDelayMs);
2315 } else {
2316 if (isRetryable) {
2317 Log(ERROR, "handleGatewayAck(): Max retries reached for msgId=%u\n", ack.messageId);
2318 } else {
2319 Log(COMMUNICATION, "handleGatewayAck(): Final failure for msgId=%u (HTTP %u)\n",
2320 ack.messageId, ack.httpStatus);
2321 }
2322 PendingInternetRequest done = std::move(request);
2323 pendingInternetRequests.erase(it);
2324 deliverInternetResult(done, std::move(result));
2325 }
2326 }
2327
2332 static bool legacyAckIsRetryable(const gateway::GatewayAckPackage& ack) {
2333 if (ack.httpStatus >= 500 && ack.httpStatus < 600) return true;
2334 if (ack.httpStatus == 429) return true;
2335 if (ack.httpStatus != 0) return false;
2336 // Gateway-level connectivity failures are infrastructure, not transient.
2337 static const char* const FINAL[] = {"Router has no internet",
2338 "Gateway WiFi not connected",
2339 "Captive portal detected"};
2340 for (const char* phrase : FINAL) {
2341 if (gateway::responseContains(ack.error, phrase)) return false;
2342 }
2343 return true;
2344 }
2345
2358 void dispatchInternetRequestLocally(uint32_t messageId, uint8_t priority,
2359 uint8_t retryCount,
2360 const TSTRING& destination,
2361 const TSTRING& payload,
2362 uint32_t requestNonce) {
2364 pkg.from = this->nodeId;
2365 pkg.dest = this->nodeId;
2366 pkg.messageId = messageId;
2367 pkg.originNode = this->nodeId;
2368 pkg.timestamp = this->getNodeTime();
2369 pkg.priority = priority;
2370 pkg.destination = destination;
2371 pkg.payload = payload;
2372 pkg.contentType = "application/json";
2373 pkg.retryCount = retryCount;
2374 pkg.requiresAck = true;
2375 pkg.requestNonce = requestNonce;
2376 protocol::Variant variant(&pkg);
2377 this->callbackList.execute(protocol::GATEWAY_DATA, variant, nullptr, 0);
2378 }
2379
2383 void scheduleInternetRetry(uint32_t messageId, uint32_t minDelayMs = 0) {
2384 auto it = pendingInternetRequests.find(messageId);
2385 if (it == pendingInternetRequests.end()) {
2386 return;
2387 }
2388
2389 PendingInternetRequest& request = it->second;
2390
2391 if (request.retryCount >= request.maxRetries) {
2392 // Max retries reached - fail the request, with the reason the retries
2393 // were spent on when there is one
2394 Log(logger::ERROR, "scheduleInternetRetry(): Max retries reached for msgId=%u\n",
2395 messageId);
2396 const PendingInternetRequest done = request;
2397 pendingInternetRequests.erase(it);
2398 deliverInternetResult(done, failedInternetResult(
2399 done.lastError.length() > 0 ? done.lastError : TSTRING("Max retries exceeded")));
2400 return;
2401 }
2402
2403 // Exponential backoff, never shorter than what the server asked for
2404 uint32_t delay = request.retryDelayMs * (1 << request.retryCount);
2405 if (delay < minDelayMs) delay = minDelayMs;
2406 request.retryCount++;
2407
2408 // A wait the server asked for moves the request's deadline with it:
2409 // otherwise a Retry-After longer than what is left of the request
2410 // timeout (30 s by default) times the request out before the retry the
2411 // server invited can run. The retry gets a full timeout of its own.
2412 if (minDelayMs > 0) {
2413 const uint32_t elapsed = static_cast<uint32_t>(millis()) - request.timestamp;
2414 const uint32_t needed = elapsed + delay + internetRequestTimeout;
2415 if (needed > request.timeoutMs) request.timeoutMs = needed;
2416 }
2417
2418 Log(logger::COMMUNICATION, "scheduleInternetRetry(): Retry %u for msgId=%u in %u ms\n",
2419 request.retryCount, messageId, delay);
2420
2421 // Schedule retry
2422 this->addTask(delay, TASK_ONCE, [this, messageId]() {
2423 this->retryInternetRequest(messageId);
2424 });
2425 }
2426
2430 void retryInternetRequest(uint32_t messageId) {
2431 auto it = pendingInternetRequests.find(messageId);
2432 if (it == pendingInternetRequests.end()) {
2433 return;
2434 }
2435
2436 PendingInternetRequest& request = it->second;
2437
2438 // A request this node is serving from its own uplink never had a mesh
2439 // hop to repeat. The routing path below needs an active peer and a
2440 // discovered bridge, so a lone gateway would burn every retry without
2441 // issuing a single HTTP request and then report "Max retries exceeded"
2442 // in place of the transport error that actually occurred -- the same
2443 // dishonest failure issue #446 was about.
2444 //
2445 // Copy what the redispatch needs first: the local handler runs
2446 // synchronously and can erase this entry, leaving `request` dangling.
2447 if (request.gatewayNodeId == this->nodeId && uplinkServesLocally()) {
2448 const uint8_t priority = request.priority;
2449 const uint8_t retryCount = request.retryCount;
2450 const TSTRING destination = request.destination;
2451 const TSTRING payload = request.payload;
2452 const uint32_t nonce = request.requestNonce;
2453 request.attempts++;
2455 "retryInternetRequest(): Retrying msgId=%u on the local uplink\n",
2456 messageId);
2457 dispatchInternetRequestLocally(messageId, priority, retryCount,
2458 destination, payload, nonce);
2459 return;
2460 }
2461
2462 // Check mesh connectivity before attempting retry
2463 // During bridge failover, connection may be temporarily lost
2464 if (!hasActiveMeshConnections()) {
2465 Log(logger::ERROR, "retryInternetRequest(): No active mesh connections for retry msgId=%u, rescheduling\n",
2466 messageId);
2467 scheduleInternetRetry(messageId);
2468 return;
2469 }
2470
2471 // Find gateway (may have changed)
2472 BridgeInfo* gateway = getPrimaryBridge();
2473 if (gateway == nullptr) {
2474 Log(logger::ERROR, "retryInternetRequest(): No gateway for retry msgId=%u\n",
2475 messageId);
2476 scheduleInternetRetry(messageId);
2477 return;
2478 }
2479
2480 // Update gateway node ID (may have changed)
2481 request.gatewayNodeId = gateway->nodeId;
2482
2483 // Create gateway data package
2485 pkg.from = this->nodeId;
2486 pkg.dest = gateway->nodeId;
2487 pkg.messageId = messageId;
2488 pkg.originNode = this->nodeId;
2489 pkg.timestamp = this->getNodeTime();
2490 pkg.priority = request.priority;
2491 pkg.destination = request.destination;
2492 pkg.payload = request.payload;
2493 pkg.contentType = "application/json";
2494 pkg.retryCount = request.retryCount;
2495 pkg.requiresAck = true;
2496 pkg.requestNonce = request.requestNonce;
2497
2498 // Send the package
2499 auto conn = painlessmesh::router::findRoute<T>((*this), gateway->nodeId);
2500 bool sent = false;
2501 if (conn) {
2503 sent = painlessmesh::router::sendWithPriority(pkg, conn, request.priority);
2504 }
2505
2506 if (!sent) {
2507 Log(logger::ERROR, "retryInternetRequest(): Retry send failed msgId=%u\n", messageId);
2508 scheduleInternetRetry(messageId);
2509 } else {
2510 request.attempts++;
2511 Log(logger::COMMUNICATION, "retryInternetRequest(): Retry sent msgId=%u to gateway %u\n",
2512 messageId, gateway->nodeId);
2513 }
2514 }
2515
2519 void scheduleInternetTimeout(uint32_t messageId) {
2520 auto it = pendingInternetRequests.find(messageId);
2521 if (it == pendingInternetRequests.end()) {
2522 return;
2523 }
2524
2525 uint32_t timeout = it->second.timeoutMs;
2526
2527 this->addTask(timeout, TASK_ONCE, [this, messageId]() {
2528 this->checkInternetRequestTimeout(messageId);
2529 });
2530 }
2531
2535 void checkInternetRequestTimeout(uint32_t messageId) {
2536 auto it = pendingInternetRequests.find(messageId);
2537 if (it == pendingInternetRequests.end()) {
2538 return; // Already handled
2539 }
2540
2541 PendingInternetRequest& request = it->second;
2542
2543 if (request.isTimedOut()) {
2544 Log(logger::ERROR, "checkInternetRequestTimeout(): Request timed out msgId=%u\n",
2545 messageId);
2546 const PendingInternetRequest done = request;
2547 pendingInternetRequests.erase(it);
2548 deliverInternetResult(done, failedInternetResult("Request timed out"));
2549 }
2550 }
2551
2555 void cleanupTimedOutRequests() {
2556 auto it = pendingInternetRequests.begin();
2557 while (it != pendingInternetRequests.end()) {
2558 if (it->second.isTimedOut()) {
2559 Log(logger::GENERAL, "cleanupTimedOutRequests(): Cleaning up msgId=%u\n",
2560 it->first);
2561 deliverInternetResult(it->second, failedInternetResult("Request timed out"));
2562 it = pendingInternetRequests.erase(it);
2563 } else {
2564 ++it;
2565 }
2566 }
2567 }
2568
2569 public:
2582 void updateBridgeStatus(uint32_t bridgeNodeId, bool internetConnected,
2583 int8_t routerRSSI, uint8_t routerChannel,
2584 uint32_t uptime, TSTRING gatewayIP, uint32_t timestamp) {
2585 // Find existing bridge or add new one
2586 BridgeInfo* bridge = nullptr;
2587 uint32_t oldPrimaryBridgeId = 0;
2588 auto oldPrimary = this->getPrimaryBridge();
2589 if (oldPrimary != nullptr) {
2590 oldPrimaryBridgeId = oldPrimary->nodeId;
2591 }
2592
2593 for (auto& b : knownBridges) {
2594 if (b.nodeId == bridgeNodeId) {
2595 bridge = &b;
2596 break;
2597 }
2598 }
2599
2600 bool wasConnected = false;
2601 bool isNewBridge = false;
2602 if (bridge == nullptr) {
2603 // New bridge - enforce MAX_KNOWN_BRIDGES limit before adding
2604 if (knownBridges.size() >= MAX_KNOWN_BRIDGES) {
2605 // First try to remove expired bridges
2606 cleanupExpiredBridges();
2607
2608 // If still at capacity, remove the bridge with worst RSSI (lowest signal)
2609 if (knownBridges.size() >= MAX_KNOWN_BRIDGES && !knownBridges.empty()) {
2610 auto worstBridge = knownBridges.begin();
2611 int8_t worstRSSI = worstBridge->routerRSSI;
2612
2613 for (auto it = knownBridges.begin(); it != knownBridges.end(); ++it) {
2614 if (it->routerRSSI < worstRSSI) {
2615 worstRSSI = it->routerRSSI;
2616 worstBridge = it;
2617 }
2618 }
2619
2620 Log(logger::GENERAL, "updateBridgeStatus(): Removing bridge %u (RSSI: %d) to make room\n",
2621 worstBridge->nodeId, worstBridge->routerRSSI);
2622 knownBridges.erase(worstBridge);
2623 }
2624 }
2625
2626 // Add new bridge
2627 BridgeInfo newBridge;
2628 newBridge.nodeId = bridgeNodeId;
2629 knownBridges.push_back(newBridge);
2630 bridge = &knownBridges.back();
2631 isNewBridge = true;
2632 } else {
2633 wasConnected = bridge->internetConnected;
2634 }
2635
2636 // Update bridge info
2637 bridge->internetConnected = internetConnected;
2638 bridge->routerRSSI = routerRSSI;
2639 bridge->routerChannel = routerChannel;
2640 bridge->lastSeen = millis();
2641 bridge->uptime = uptime;
2642 bridge->gatewayIP = gatewayIP;
2643 bridge->timestamp = timestamp;
2644
2645 // Check if primary bridge changed
2646 auto newPrimary = this->getPrimaryBridge();
2647 uint32_t newPrimaryBridgeId = (newPrimary != nullptr) ? newPrimary->nodeId : 0;
2648
2649 // Trigger gateway changed callback if primary gateway changed
2650 if (oldPrimaryBridgeId != newPrimaryBridgeId && gatewayChangedCallback) {
2651 gatewayChangedCallback(oldPrimaryBridgeId, newPrimaryBridgeId);
2652 }
2653
2654 if (diagnosticsEnabled && oldPrimaryBridgeId != newPrimaryBridgeId) {
2655 // Record bridge change
2656 lastBridgeChange.timestamp = millis();
2657 lastBridgeChange.oldBridgeId = oldPrimaryBridgeId;
2658 lastBridgeChange.newBridgeId = newPrimaryBridgeId;
2659 lastBridgeChange.internetAvailable = internetConnected;
2660 if (isNewBridge) {
2661 lastBridgeChange.reason = "New bridge discovered";
2662 } else if (internetConnected && !wasConnected) {
2663 lastBridgeChange.reason = "Bridge Internet restored";
2664 } else if (!internetConnected && wasConnected) {
2665 lastBridgeChange.reason = "Bridge Internet lost";
2666 } else {
2667 lastBridgeChange.reason = "Primary bridge changed";
2668 }
2669
2670 lastBridgeChangeTime = millis();
2671 Log(logger::GENERAL, "updateBridgeStatus(): Bridge change recorded\n");
2672 }
2673
2674 // Trigger callback if status changed
2675 if (wasConnected != internetConnected && bridgeStatusChangedCallback) {
2676 bridgeStatusChangedCallback(bridgeNodeId, internetConnected);
2677 }
2678 }
2679
2699 bool enableRTC(rtc::RTCInterface* rtcInterface) {
2700 using namespace logger;
2701 Log(GENERAL, "enableRTC(): Initializing RTC\n");
2702 bool success = rtcManager.enable(rtcInterface);
2703 if (success) {
2704 // RTC enabled successfully - mark node as having time authority
2705 setTimeAuthority(true);
2706 }
2707 return success;
2708 }
2709
2713 void disableRTC() {
2714 using namespace logger;
2715 Log(GENERAL, "disableRTC(): Disabling RTC\n");
2716 rtcManager.disable();
2717 // RTC disabled - remove time authority if no other source
2718 // Note: Bridge nodes may still have time authority from Internet
2719 setTimeAuthority(false);
2720 }
2721
2744 bool syncRTCFromNTP(uint32_t ntpTimestamp) {
2745 using namespace logger;
2746 Log(GENERAL, "syncRTCFromNTP(): Syncing RTC to timestamp %u\n", ntpTimestamp);
2747
2748 if (!rtcManager.isEnabled()) {
2749 Log(ERROR, "syncRTCFromNTP(): RTC not enabled\n");
2750 return false;
2751 }
2752
2753 bool success = rtcManager.syncFromNTP(ntpTimestamp);
2754
2755 if (success && rtcSyncCompleteCallback) {
2756 rtcSyncCompleteCallback(ntpTimestamp);
2757 }
2758
2759 return success;
2760 }
2761
2770 uint32_t getAccurateTime() {
2771 if (rtcManager.isEnabled()) {
2772 uint32_t rtcTime = rtcManager.getTime();
2773 if (rtcTime > 0) {
2774 return rtcTime;
2775 }
2776 }
2777 // Fallback to mesh time (microseconds), converted to seconds for consistency
2778 return getNodeTime() / 1000000;
2779 }
2780
2786 bool hasRTC() const {
2787 return rtcManager.isEnabled();
2788 }
2789
2796 return rtcManager.getType();
2797 }
2798
2804 uint32_t getTimeSinceRTCSync() const {
2805 return rtcManager.getTimeSinceLastSync();
2806 }
2807
2820 Log(logger::GENERAL, "onRTCSyncComplete():\n");
2821 rtcSyncCompleteCallback = onRTCSyncComplete;
2822 }
2823
2843 void setTimeAuthority(bool hasAuthority) {
2844 using namespace logger;
2845 if (this->hasTimeAuthority != hasAuthority) {
2846 this->hasTimeAuthority = hasAuthority;
2847 Log(GENERAL, "setTimeAuthority(): Time authority %s\n",
2848 hasAuthority ? "enabled" : "disabled");
2849
2850 // Trigger time sync with all connections to propagate authority status
2851 for (auto&& connection : this->subs) {
2852 if (connection->nodeId != 0) {
2853 connection->timeSyncTask.forceNextIteration();
2854 }
2855 }
2856 }
2857 }
2858
2864 bool getTimeAuthority() const {
2865 return this->hasTimeAuthority;
2866 }
2867
2868 //
2869 // Message Queue API
2870 //
2871
2903 void enableMessageQueue(bool enabled, uint32_t maxSize = 1000) {
2904 if (enabled && !messageQueue) {
2905 messageQueue = new MessageQueue(maxSize);
2906 Log(logger::GENERAL, "enableMessageQueue(): Queue enabled with capacity %u\n", maxSize);
2907 } else if (!enabled && messageQueue) {
2908 delete messageQueue;
2909 messageQueue = nullptr;
2910 Log(logger::GENERAL, "enableMessageQueue(): Queue disabled\n");
2911 }
2912 }
2913
2936 uint32_t queueMessage(const TSTRING& payload,
2937 const TSTRING& destination = "",
2938 MessagePriority priority = PRIORITY_NORMAL) {
2939 if (!messageQueue) {
2940 Log(logger::ERROR, "queueMessage(): Message queue not enabled\n");
2941 return 0;
2942 }
2943
2944 return messageQueue->enqueue(priority, payload, destination);
2945 }
2946
2967 std::vector<QueuedMessage> flushMessageQueue() {
2968 if (!messageQueue) {
2969 return std::vector<QueuedMessage>();
2970 }
2971
2972 return messageQueue->getMessages();
2973 }
2974
2981 bool removeQueuedMessage(uint32_t messageId) {
2982 if (!messageQueue) {
2983 return false;
2984 }
2985
2986 return messageQueue->remove(messageId);
2987 }
2988
2995 uint32_t incrementQueuedMessageAttempts(uint32_t messageId) {
2996 if (!messageQueue) {
2997 return 0;
2998 }
2999
3000 return messageQueue->incrementAttempts(messageId);
3001 }
3002
3010 if (!messageQueue) {
3011 return 0;
3012 }
3013
3014 return messageQueue->size(priority);
3015 }
3016
3018 if (!messageQueue) {
3019 return 0;
3020 }
3021
3022 return messageQueue->size();
3023 }
3024
3031 if (!messageQueue) {
3032 return QueueStats();
3033 }
3034
3035 return messageQueue->getStats();
3036 }
3037
3052 if (messageQueue) {
3053 messageQueue->onStateChanged(callback);
3054 }
3055 }
3056
3063 uint32_t pruneQueue(uint32_t maxAgeMs) {
3064 if (!messageQueue) {
3065 return 0;
3066 }
3067
3068 return messageQueue->pruneOldMessages(maxAgeMs);
3069 }
3070
3074 void clearQueue() {
3075 if (messageQueue) {
3076 messageQueue->clear();
3077 }
3078 }
3079
3085 bool isConnected(uint32_t nodeId) {
3086 return painlessmesh::router::findRoute<T>((*this), nodeId) != NULL;
3087 }
3088
3094 std::list<uint32_t> getNodeList(bool includeSelf = false) {
3095 return painlessmesh::layout::asList(this->asNodeTree(), includeSelf);
3096 }
3097
3101 inline TSTRING subConnectionJson(bool pretty = false) {
3102 return this->asNodeTree().toString(pretty);
3103 }
3104
3109 uint32_t nodeId = 0; // Connected node ID
3110 uint32_t lastSeen = 0; // Timestamp of last message (ms)
3111 int rssi = 0; // Signal strength (dBm)
3112 int avgLatency = 0; // Average round-trip time (ms)
3113 int hopCount = 0; // Hops from current node
3114 int quality = 0; // Connection quality (0-100)
3115 uint32_t messagesRx = 0; // Messages received
3116 uint32_t messagesTx = 0; // Messages sent
3117 uint32_t messagesDropped = 0; // Failed transmissions
3118 };
3119
3123 std::vector<ConnectionInfo> getConnectionDetails() {
3124 std::vector<ConnectionInfo> connections;
3125
3126 for (auto conn : this->subs) {
3127 if (conn->connected()) {
3128 ConnectionInfo info;
3129 info.nodeId = conn->nodeId;
3130 info.lastSeen = conn->timeLastReceived;
3131 info.rssi = conn->getRSSI();
3132 info.avgLatency = conn->getLatency();
3133 info.hopCount = 1; // Direct connection
3134 info.quality = conn->getQuality();
3135 info.messagesRx = conn->messagesRx;
3136 info.messagesTx = conn->messagesTx;
3137 info.messagesDropped = conn->messagesDropped;
3138
3139 connections.push_back(info);
3140 }
3141 }
3142
3143 return connections;
3144 }
3145
3150 int getHopCount(uint32_t nodeId) {
3151 // Check if requesting hop count to self
3152 if (nodeId == this->getNodeId()) {
3153 return 0;
3154 }
3155
3156 // Check if it's a direct connection (1 hop)
3157 for (auto conn : this->subs) {
3158 if (conn->nodeId == nodeId && conn->connected()) {
3159 return 1;
3160 }
3161 }
3162
3163 // For indirect connections, use BFS to find shortest path
3164 auto tree = this->asNodeTree();
3165
3166 // BFS to find hop count
3167 std::queue<std::pair<uint32_t, uint16_t>> queue; // (nodeId, hops)
3168 std::set<uint32_t> visited;
3169
3170 // Start from this node
3171 queue.push({this->getNodeId(), 0});
3172 visited.insert(this->getNodeId());
3173
3174 while (!queue.empty()) {
3175 auto current = queue.front();
3176 queue.pop();
3177
3178 uint32_t currentNode = current.first;
3179 uint16_t hops = current.second;
3180
3181 // Found the target
3182 if (currentNode == nodeId) {
3183 return hops;
3184 }
3185
3186 // Get neighbors of current node
3187 std::list<uint32_t> neighbors;
3188 if (getNodeNeighbors(tree, currentNode, neighbors)) {
3189 // Add unvisited neighbors to queue
3190 for (auto neighbor : neighbors) {
3191 if (visited.find(neighbor) == visited.end()) {
3192 visited.insert(neighbor);
3193 queue.push({neighbor, static_cast<uint16_t>(hops + 1)});
3194 }
3195 }
3196 }
3197 }
3198
3199 return -1; // Node not found or unreachable
3200 }
3201
3210 std::map<uint32_t, uint32_t> getRoutingTable() {
3211 std::map<uint32_t, uint32_t> table;
3212 auto tree = this->asNodeTree();
3213
3214 // BFS from this node to build routing table
3215 std::queue<uint32_t> queue;
3216 std::set<uint32_t> visited;
3217 std::map<uint32_t, uint32_t> nextHop; // dest -> next hop from source
3218
3219 // Start from this node
3220 queue.push(this->getNodeId());
3221 visited.insert(this->getNodeId());
3222
3223 while (!queue.empty()) {
3224 uint32_t current = queue.front();
3225 queue.pop();
3226
3227 // Get neighbors of current node
3228 std::list<uint32_t> neighbors;
3229 if (getNodeNeighbors(tree, current, neighbors)) {
3230 for (auto neighbor : neighbors) {
3231 if (visited.find(neighbor) == visited.end()) {
3232 visited.insert(neighbor);
3233 queue.push(neighbor);
3234
3235 // Determine next hop for this neighbor
3236 if (current == this->getNodeId()) {
3237 // Direct connection - next hop is the neighbor itself
3238 nextHop[neighbor] = neighbor;
3239 } else {
3240 // Multi-hop - inherit parent's next hop
3241 nextHop[neighbor] = nextHop[current];
3242 }
3243
3244 // Add to routing table
3245 table[neighbor] = nextHop[neighbor];
3246 }
3247 }
3248 }
3249 }
3250
3251 return table;
3252 }
3253
3274 std::vector<uint32_t> getPathToNode(uint32_t nodeId) {
3275 std::vector<uint32_t> path;
3276
3277 // Check if requesting path to self
3278 if (nodeId == this->getNodeId()) {
3279 path.push_back(this->getNodeId());
3280 return path;
3281 }
3282
3283 auto tree = this->asNodeTree();
3284
3285 // BFS to find path
3286 std::queue<uint32_t> queue;
3287 std::set<uint32_t> visited;
3288 std::map<uint32_t, uint32_t> parent; // child -> parent mapping
3289
3290 // Start from this node
3291 queue.push(this->getNodeId());
3292 visited.insert(this->getNodeId());
3293 parent[this->getNodeId()] = 0; // Root has no parent
3294
3295 bool found = false;
3296 while (!queue.empty() && !found) {
3297 uint32_t current = queue.front();
3298 queue.pop();
3299
3300 // Check if we reached the target
3301 if (current == nodeId) {
3302 found = true;
3303 break;
3304 }
3305
3306 // Get neighbors of current node
3307 std::list<uint32_t> neighbors;
3308 if (getNodeNeighbors(tree, current, neighbors)) {
3309 for (auto neighbor : neighbors) {
3310 if (visited.find(neighbor) == visited.end()) {
3311 visited.insert(neighbor);
3312 parent[neighbor] = current;
3313 queue.push(neighbor);
3314 }
3315 }
3316 }
3317 }
3318
3319 // Reconstruct path from parent map
3320 if (found) {
3321 uint32_t current = nodeId;
3322 size_t maxIter = parent.size() + 1;
3323 while (current != 0 && maxIter-- > 0) {
3324 path.insert(path.begin(), current);
3325 current = parent[current];
3326 }
3327 }
3328
3329 return path;
3330 }
3331
3347 BridgeHealthMetrics metrics;
3348
3349 // Connectivity metrics
3350 metrics.uptimeSeconds = millis() / 1000;
3351 metrics.currentUptime = millis();
3352
3353 // Check for primary bridge with Internet connection
3354 auto primaryBridge = getPrimaryBridge();
3355 if (primaryBridge != nullptr) {
3356 metrics.internetUptimeSeconds = (millis() - primaryBridge->lastSeen) / 1000;
3357 metrics.currentRSSI = primaryBridge->routerRSSI;
3358 }
3359
3360 // Aggregate traffic and performance from all connections
3361 uint64_t totalBytesRx = 0;
3362 uint64_t totalBytesTx = 0;
3363 uint32_t totalMessagesRx = 0;
3364 uint32_t totalMessagesTx = 0;
3365 uint32_t totalMessagesDropped = 0;
3366 uint32_t totalLatency = 0;
3367 uint32_t latencySampleCount = 0;
3368 int32_t sumRSSI = 0;
3369 int8_t minRSSI = 0;
3370 int8_t maxRSSI = -127;
3371 uint32_t rssiCount = 0;
3372
3373 for (auto conn : this->subs) {
3374 if (conn->connected()) {
3375 totalBytesRx += conn->bytesRx;
3376 totalBytesTx += conn->bytesTx;
3377 totalMessagesRx += conn->messagesRx;
3378 totalMessagesTx += conn->messagesTx;
3379 totalMessagesDropped += conn->messagesDropped;
3380
3381 // Latency
3382 int latency = conn->getLatency();
3383 if (latency >= 0) {
3384 totalLatency += latency;
3385 latencySampleCount++;
3386 }
3387
3388 // RSSI
3389 int rssi = conn->getRSSI();
3390 if (rssi != 0) {
3391 sumRSSI += rssi;
3392 rssiCount++;
3393 if (rssi < minRSSI || minRSSI == 0) minRSSI = rssi;
3394 if (rssi > maxRSSI) maxRSSI = rssi;
3395 }
3396 }
3397 }
3398
3399 // Set traffic metrics
3400 metrics.bytesRx = totalBytesRx;
3401 metrics.bytesTx = totalBytesTx;
3402 metrics.messagesRx = totalMessagesRx;
3403 metrics.messagesTx = totalMessagesTx;
3404 metrics.messagesDropped = totalMessagesDropped;
3405
3406 // Calculate averages
3407 if (latencySampleCount > 0) {
3408 metrics.avgLatencyMs = totalLatency / latencySampleCount;
3409 }
3410
3411 if (rssiCount > 0) {
3412 metrics.avgRSSI = sumRSSI / rssiCount;
3413 metrics.minRSSI = minRSSI;
3414 metrics.maxRSSI = maxRSSI;
3415 }
3416
3417 // Calculate packet loss percentage
3418 uint32_t totalAttempted = totalMessagesTx + totalMessagesDropped;
3419 if (totalAttempted > 0) {
3420 metrics.packetLossPercent = (totalMessagesDropped * 100) / totalAttempted;
3421 }
3422
3423 // Mesh node count
3424 metrics.meshNodeCount = this->getNodeList(true).size();
3425
3426 // Track disconnects (stored in protected member)
3427 metrics.totalDisconnects = metricsDisconnectCount;
3428
3429 return metrics;
3430 }
3431
3444 using namespace logger;
3445 Log(GENERAL, "resetHealthMetrics(): Resetting all metric counters\n");
3446
3447 // Reset connection metrics
3448 for (auto conn : this->subs) {
3449 if (conn->connected()) {
3450 conn->messagesRx = 0;
3451 conn->messagesTx = 0;
3452 conn->messagesDropped = 0;
3453 conn->bytesRx = 0;
3454 conn->bytesTx = 0;
3455 conn->latencySamples.clear();
3456 }
3457 }
3458
3459 // Reset disconnect counter
3460 metricsDisconnectCount = 0;
3461 }
3462
3477 auto metrics = getBridgeHealthMetrics();
3478
3479 TSTRING json = "{";
3480 json += "\"connectivity\":{";
3481 json += "\"uptimeSeconds\":";
3482 json += std::to_string(metrics.uptimeSeconds);
3483 json += ",\"internetUptimeSeconds\":";
3484 json += std::to_string(metrics.internetUptimeSeconds);
3485 json += ",\"totalDisconnects\":";
3486 json += std::to_string(metrics.totalDisconnects);
3487 json += ",\"currentUptime\":";
3488 json += std::to_string(metrics.currentUptime);
3489 json += "},";
3490
3491 json += "\"signalQuality\":{";
3492 json += "\"currentRSSI\":";
3493 json += std::to_string(metrics.currentRSSI);
3494 json += ",\"avgRSSI\":";
3495 json += std::to_string(metrics.avgRSSI);
3496 json += ",\"minRSSI\":";
3497 json += std::to_string(metrics.minRSSI);
3498 json += ",\"maxRSSI\":";
3499 json += std::to_string(metrics.maxRSSI);
3500 json += "},";
3501
3502 json += "\"traffic\":{";
3503 json += "\"bytesRx\":";
3504 json += std::to_string(metrics.bytesRx);
3505 json += ",\"bytesTx\":";
3506 json += std::to_string(metrics.bytesTx);
3507 json += ",\"messagesRx\":";
3508 json += std::to_string(metrics.messagesRx);
3509 json += ",\"messagesTx\":";
3510 json += std::to_string(metrics.messagesTx);
3511 json += ",\"messagesQueued\":";
3512 json += std::to_string(metrics.messagesQueued);
3513 json += ",\"messagesDropped\":";
3514 json += std::to_string(metrics.messagesDropped);
3515 json += "},";
3516
3517 json += "\"performance\":{";
3518 json += "\"avgLatencyMs\":";
3519 json += std::to_string(metrics.avgLatencyMs);
3520 json += ",\"packetLossPercent\":";
3521 json += std::to_string(metrics.packetLossPercent);
3522 json += ",\"meshNodeCount\":";
3523 json += std::to_string(metrics.meshNodeCount);
3524 json += "}";
3525
3526 json += "}";
3527 return json;
3528 }
3529
3546 void onHealthMetricsUpdate(std::function<void(BridgeHealthMetrics)> callback,
3547 uint32_t intervalMs = 60000) {
3548 using namespace logger;
3549 Log(GENERAL, "onHealthMetricsUpdate(): Setting up periodic callback every %u ms\n", intervalMs);
3550
3551 // Create a task that periodically collects and reports metrics
3552 auto metricsTask = this->addTask(intervalMs, TASK_FOREVER, [this, callback]() {
3553 auto metrics = this->getBridgeHealthMetrics();
3554 callback(metrics);
3555 });
3556
3557 metricsTask->enable();
3558 }
3559
3560 // ==================== Enhanced Diagnostics API ====================
3561
3570 void enableDiagnostics(bool enabled = true) {
3571 diagnosticsEnabled = enabled;
3572 if (enabled) {
3573 Log(logger::GENERAL, "enableDiagnostics(): Diagnostics enabled\n");
3574 } else {
3575 Log(logger::GENERAL, "enableDiagnostics(): Diagnostics disabled\n");
3576 }
3577 }
3578
3594 BridgeStatus status;
3595
3596 status.isBridge = this->isBridge();
3597 status.internetConnected = this->hasInternetConnection();
3598
3599 if (status.isBridge) {
3600 status.role = "bridge";
3601 } else if (this->root) {
3602 status.role = "root";
3603 } else {
3604 status.role = "regular";
3605 }
3606
3607 // Get primary bridge info
3608 auto primaryBridge = this->getPrimaryBridge();
3609 if (primaryBridge != nullptr) {
3610 status.bridgeNodeId = primaryBridge->nodeId;
3611 status.bridgeRSSI = primaryBridge->routerRSSI;
3612 }
3613
3614 // Calculate time since last bridge change
3615 if (lastBridgeChangeTime > 0) {
3616 status.timeSinceBridgeChange = millis() - lastBridgeChangeTime;
3617 }
3618
3619 return status;
3620 }
3621
3638 std::vector<ElectionRecord> getElectionHistory() {
3639 if (!diagnosticsEnabled) {
3640 Log(logger::GENERAL, "getElectionHistory(): Diagnostics not enabled\n");
3641 return std::vector<ElectionRecord>();
3642 }
3643 return electionHistory;
3644 }
3645
3662 return lastBridgeChange;
3663 }
3664
3680 TSTRING dot = "digraph mesh {\n";
3681 dot += " rankdir=TB;\n";
3682 dot += " node [shape=box];\n\n";
3683
3684 // Add this node
3685 dot += " \"" + TSTRING(std::to_string(this->nodeId).c_str()) + "\" ";
3686 if (this->isBridge()) {
3687 dot += "[style=filled,fillcolor=lightblue,label=\"" + TSTRING(std::to_string(this->nodeId).c_str()) + "\\nBridge\"];\n";
3688 } else {
3689 dot += "[label=\"" + TSTRING(std::to_string(this->nodeId).c_str()) + "\"];\n";
3690 }
3691
3692 // Add Internet node if bridge exists
3693 auto primaryBridge = this->getPrimaryBridge();
3694 if (primaryBridge != nullptr && primaryBridge->internetConnected) {
3695 dot += " \"Internet\" [shape=cloud,style=filled,fillcolor=lightgreen];\n";
3696 dot += " \"" + TSTRING(std::to_string(primaryBridge->nodeId).c_str()) + "\" -> \"Internet\" [style=dashed,color=green];\n";
3697 }
3698
3699 // Add all known nodes and connections
3700 auto nodeList = this->getNodeList(false);
3701 for (auto node : nodeList) {
3702 dot += " \"" + TSTRING(std::to_string(node).c_str()) + "\";\n";
3703 }
3704
3705 // Add edges for direct connections
3706 for (auto conn : this->subs) {
3707 if (conn->connected()) {
3708 dot += " \"" + TSTRING(std::to_string(this->nodeId).c_str()) + "\" -> \"" + TSTRING(std::to_string(conn->nodeId).c_str()) + "\"";
3709
3710 // Add edge labels with latency
3711 int latency = conn->getLatency();
3712 if (latency >= 0) {
3713 dot += " [label=\"" + TSTRING(std::to_string(latency).c_str()) + "ms\"]";
3714 }
3715 dot += ";\n";
3716 }
3717 }
3718
3719 dot += "}\n";
3720 return dot;
3721 }
3722
3742 BridgeTestResult result;
3743
3744 // Check if we have a bridge
3745 auto primaryBridge = this->getPrimaryBridge();
3746 if (primaryBridge == nullptr) {
3747 result.success = false;
3748 result.message = "No bridge available";
3749 return result;
3750 }
3751
3752 // Check if bridge is reachable
3753 result.bridgeReachable = this->isConnected(primaryBridge->nodeId);
3754 if (!result.bridgeReachable) {
3755 result.success = false;
3756 result.message = "Bridge not reachable";
3757 return result;
3758 }
3759
3760 // Estimate latency from connection info
3761 for (auto conn : this->subs) {
3762 if (conn->nodeId == primaryBridge->nodeId && conn->connected()) {
3763 int latency = conn->getLatency();
3764 if (latency >= 0) {
3765 result.latencyMs = latency;
3766 }
3767 break;
3768 }
3769 }
3770
3771 // Check Internet connectivity through bridge
3772 result.internetReachable = primaryBridge->internetConnected;
3773
3774 result.success = result.bridgeReachable;
3775 if (result.internetReachable) {
3776 result.message = "Bridge reachable with Internet";
3777 } else {
3778 result.message = "Bridge reachable, no Internet";
3779 }
3780
3781 return result;
3782 }
3783
3798 bool isBridgeReachable(uint32_t bridgeNodeId) {
3799 return this->isConnected(bridgeNodeId);
3800 }
3801
3823 TSTRING report = "=== painlessMesh Diagnostics ===\n";
3824
3825 // Node info
3826 auto status = this->getBridgeStatus();
3827 report += "Node ID: " + TSTRING(std::to_string(this->nodeId).c_str()) + "\n";
3828 report += "Mode: " + status.role + "\n";
3829
3830 // Mesh info
3831 auto nodeList = this->getNodeList(true);
3832 report += "Mesh Nodes: " + TSTRING(std::to_string(nodeList.size()).c_str()) + "\n";
3833
3834 // Bridge info
3835 if (status.isBridge) {
3836 report += "Bridge: " + TSTRING(std::to_string(this->nodeId).c_str()) + " (this node)\n";
3837 } else {
3838 auto primaryBridge = this->getPrimaryBridge();
3839 if (primaryBridge != nullptr) {
3840 report += "Bridge: " + TSTRING(std::to_string(primaryBridge->nodeId).c_str());
3841 report += " (RSSI: " + TSTRING(std::to_string(primaryBridge->routerRSSI).c_str()) + " dBm";
3842 report += ", Internet: " + TSTRING(primaryBridge->internetConnected ? "✓" : "✗") + ")\n";
3843 } else {
3844 report += "Bridge: None available\n";
3845 }
3846 }
3847
3848 // Connection info
3849 report += "Direct Connections: " + TSTRING(std::to_string(this->subs.size()).c_str()) + "\n";
3850
3851 // Health metrics
3852 auto metrics = this->getBridgeHealthMetrics();
3853 report += "Messages RX: " + TSTRING(std::to_string(metrics.messagesRx).c_str()) + "\n";
3854 report += "Messages TX: " + TSTRING(std::to_string(metrics.messagesTx).c_str()) + "\n";
3855 report += "Messages Dropped: " + TSTRING(std::to_string(metrics.messagesDropped).c_str()) + "\n";
3856
3857 if (metrics.avgLatencyMs > 0) {
3858 report += "Avg Latency: " + TSTRING(std::to_string(metrics.avgLatencyMs).c_str()) + " ms\n";
3859 }
3860
3861 // Uptime
3862 uint32_t uptimeSeconds = millis() / 1000;
3863 uint32_t hours = uptimeSeconds / 3600;
3864 uint32_t minutes = (uptimeSeconds % 3600) / 60;
3865 uint32_t seconds = uptimeSeconds % 60;
3866
3867 char uptimeStr[32];
3868 snprintf(uptimeStr, sizeof(uptimeStr), "%02u:%02u:%02u", hours, minutes, seconds);
3869 report += "Uptime: " + TSTRING(uptimeStr) + "\n";
3870
3871 // Election info
3872 if (diagnosticsEnabled && !electionHistory.empty()) {
3873 auto& lastElection = electionHistory.back();
3874 uint32_t timeSinceElection = millis() - lastElection.timestamp;
3875 uint32_t electionMinutes = (timeSinceElection / 1000) / 60;
3876 uint32_t electionSeconds = (timeSinceElection / 1000) % 60;
3877
3878 char electionTimeStr[32];
3879 snprintf(electionTimeStr, sizeof(electionTimeStr), "%02u:%02u", electionMinutes, electionSeconds);
3880
3881 report += "Last Election: " + TSTRING(electionTimeStr) + " ago";
3882 report += " (Winner: " + TSTRING(std::to_string(lastElection.winnerNodeId).c_str());
3883 report += ", " + TSTRING(std::to_string(lastElection.candidateCount).c_str()) + " candidates)\n";
3884 }
3885
3886 report += "================================\n";
3887 return report;
3888 }
3889
3890 inline std::shared_ptr<Task> addTask(unsigned long aInterval,
3891 long aIterations,
3892 std::function<void()> aCallback) {
3893 return plugin::PackageHandler<T>::addTask((*this->mScheduler), aInterval,
3894 aIterations, aCallback);
3895 }
3896
3897 inline std::shared_ptr<Task> addTask(std::function<void()> aCallback) {
3898 return plugin::PackageHandler<T>::addTask((*this->mScheduler), aCallback);
3899 }
3900
3908 inline std::shared_ptr<Task> addTask(std::function<void()> aCallback,
3909 unsigned long delayMs) {
3910 return plugin::PackageHandler<T>::addTask((*this->mScheduler), delayMs,
3911 TASK_ONCE, aCallback);
3912 }
3913
3915 this->stop();
3917 reclaimRetiredSchedulers();
3918 if (!isExternalScheduler) delete mScheduler;
3919 if (messageQueue) delete messageQueue;
3920 }
3921
3922 protected:
3924 for (auto it = retiredSchedulers.begin();
3925 it != retiredSchedulers.end();) {
3926 if ((*it)->getCurrentTask() != nullptr) {
3927 ++it;
3928 continue;
3929 }
3930 delete *it;
3931 it = retiredSchedulers.erase(it);
3932 }
3933 }
3934
3935 std::list<Scheduler*> retiredSchedulers = {};
3936
3937 void queueBroadcastAck(uint32_t origin, uint32_t msgId) {
3938 const auto key = std::make_pair(origin, msgId);
3939 if (std::find(pendingBroadcastAcks.begin(), pendingBroadcastAcks.end(),
3940 key) != pendingBroadcastAcks.end())
3941 return;
3942 if (pendingBroadcastAcks.size() >= MAX_QUEUED_BROADCAST_ACKS) {
3943 Log(logger::ERROR, "Mesh: broadcast-ack queue limit (%u) reached\n",
3944 (unsigned)MAX_QUEUED_BROADCAST_ACKS);
3945 return;
3946 }
3947 pendingBroadcastAcks.push_back(key);
3948 if (!broadcastAckTask) {
3949 broadcastAckTask = this->addTask(
3950 [this]() {
3951 while (!this->pendingBroadcastAcks.empty()) {
3952 auto queued = std::move(this->pendingBroadcastAcks);
3953 this->pendingBroadcastAcks.clear();
3954 for (const auto& item : queued) {
3955 auto pkg = ack::MessageAckPackage(this->nodeId, item.first,
3956 item.second);
3957 this->sendPackage(&pkg);
3958 }
3959 }
3960 },
3961 this->nodeId % ACK_BROADCAST_JITTER_MS);
3962 } else if (!broadcastAckTask->isEnabled() ||
3963 broadcastAckTask->isLastIteration()) {
3964 broadcastAckTask->restartDelayed();
3965 }
3966 }
3967
3975 if (!ackCheckTask) {
3976 ackCheckTask = this->addTask(ACK_CHECK_INTERVAL_MS, TASK_FOREVER,
3977 [this]() {
3978 if (this->checkAcks() == 0 &&
3979 this->ackCheckTask)
3980 this->ackCheckTask->disable();
3981 });
3982 } else if (!ackCheckTask->isEnabled()) {
3983 ackCheckTask->enable();
3984 }
3985 }
3986
3987 // Poll interval for ack timeouts while acks are pending. Note for
3988 // battery/light-sleep nodes: while any ack is outstanding the
3989 // scheduler wakes at this cadence for up to ackTimeoutMs; override at
3990 // build time (coarser is cheaper, timeout resolution degrades
3991 // accordingly).
3992#ifndef PAINLESSMESH_ACK_CHECK_INTERVAL_MS
3993#define PAINLESSMESH_ACK_CHECK_INTERVAL_MS 100
3994#endif
3995 static constexpr unsigned long ACK_CHECK_INTERVAL_MS =
3997 // Broadcast ACK replies are staggered by nodeId within this window so
3998 // N nodes do not converge N simultaneous unicasts on the sender
3999 static constexpr uint32_t ACK_BROADCAST_JITTER_MS = 50;
4000 // Allow the jittered ACK to traverse a multi-hop route after its slot.
4001 static constexpr uint32_t ACK_BROADCAST_MIN_TIMEOUT_MS = 1000;
4002 static constexpr size_t MAX_QUEUED_BROADCAST_ACKS =
4005 std::shared_ptr<Task> ackCheckTask;
4006 std::vector<std::pair<uint32_t, uint32_t> > pendingBroadcastAcks;
4007 std::shared_ptr<Task> broadcastAckTask;
4008
4009 void setScheduler(Scheduler *baseScheduler) {
4010 this->mScheduler = baseScheduler;
4011 isExternalScheduler = true;
4012 }
4013
4014 public: // Windows MSVC: router functions need access
4015 void startTimeSync(std::shared_ptr<T> conn) {
4016 using namespace logger;
4017 Log(S_TIME, "startTimeSync(): from %u with %u\n", this->nodeId,
4018 conn->nodeId);
4020 if (ntp::adopt(this->asNodeTree(), (*conn))) {
4021 timeSync = painlessmesh::protocol::TimeSync(this->nodeId, conn->nodeId,
4022 this->getNodeTime());
4023 Log(S_TIME, "startTimeSync(): Requesting time from %u\n", conn->nodeId);
4024 } else {
4025 timeSync = painlessmesh::protocol::TimeSync(this->nodeId, conn->nodeId);
4026 Log(S_TIME, "startTimeSync(): Requesting %u to adopt our time\n",
4027 conn->nodeId);
4028 }
4029 router::send<protocol::TimeSync, T>(timeSync, conn, true);
4030 }
4031
4041 bool getNodeNeighbors(const protocol::NodeTree& tree, uint32_t nodeId,
4042 std::list<uint32_t>& neighbors, uint32_t parent = 0) const {
4043 if (tree.nodeId == nodeId) {
4044 // Found the target node - collect its neighbors
4045 // Add parent if it exists
4046 if (parent != 0) {
4047 neighbors.push_back(parent);
4048 }
4049 // Add all direct children
4050 for (auto& sub : tree.subs) {
4051 if (sub.nodeId != 0) {
4052 neighbors.push_back(sub.nodeId);
4053 }
4054 }
4055 return true;
4056 }
4057
4058 // Recursively search in children
4059 for (auto& sub : tree.subs) {
4060 if (getNodeNeighbors(sub, nodeId, neighbors, tree.nodeId)) {
4061 return true;
4062 }
4063 }
4064
4065 return false;
4066 }
4067
4069 auto connection = this->subs.begin();
4070 while (connection != this->subs.end()) {
4071 if ((*connection)->station) {
4072 // We found the STA connection, close it
4073 (*connection)->close();
4074 return true;
4075 }
4076 ++connection;
4077 }
4078 return false;
4079 }
4080
4082 using namespace logger;
4083 Log(CONNECTION, "eraseClosedConnections():\n");
4084 this->subs.remove_if(
4085 [](const std::shared_ptr<T> &conn) {
4086 // Null check for safety - should never happen but prevents crashes
4087 if (!conn) return true;
4088 return !conn->connected();
4089 });
4090 }
4091
4092 public: // Windows MSVC: TCP lambdas need access to droppedConnectionCallbacks
4093 // Callback functions
4102
4103 // Message queue for offline mode
4104 MessageQueue* messageQueue = nullptr;
4105
4106#ifdef ESP32
4107 SemaphoreHandle_t xSemaphore = NULL;
4108#endif
4109
4110 bool isExternalScheduler = false;
4111
4113 bool shouldContainRoot = false;
4114
4115 // init() either creates one or takes the caller's; null until then.
4116 Scheduler *mScheduler = nullptr;
4117
4118 public: // Windows MSVC: lambdas in friend functions need public access
4119
4128#ifdef ESP32
4129 return xSemaphoreTake(xSemaphore, (TickType_t)1000) == pdTRUE;
4130#else
4131 return true;
4132#endif
4133 }
4134
4141#ifdef ESP32
4142 xSemaphoreGive(xSemaphore);
4143#endif
4144 }
4145
4146 // Bridge status tracking
4147 std::vector<BridgeInfo> knownBridges;
4148 uint32_t bridgeStatusIntervalMs = 30000; // Default 30 seconds
4149 uint32_t bridgeTimeoutMs = 60000; // Default 60 seconds
4150 bool bridgeStatusBroadcastEnabled = true;
4151 // See capacityCheck(). Only ever set on ESP8266.
4152 bool _overCapacity = false;
4153 uint32_t _capacityWarnedAt = 0;
4154
4155 // Bridge cleanup configuration
4156 static const size_t MAX_KNOWN_BRIDGES = 20; // Memory efficient limit for ESP8266
4157 std::shared_ptr<Task> bridgeCleanupTask = nullptr;
4158
4159 // Health metrics tracking
4160 uint32_t metricsDisconnectCount = 0;
4161
4162 // RTC management
4164
4165 // Diagnostics tracking
4166 bool diagnosticsEnabled = false;
4167 std::vector<ElectionRecord> electionHistory;
4168 static const size_t MAX_ELECTION_HISTORY = 10;
4170 uint32_t lastBridgeChangeTime = 0;
4171
4172 // Local Internet health check
4174 std::shared_ptr<Task> internetHealthCheckTask = nullptr;
4176
4177 // sendToInternet API
4178 std::map<uint32_t, PendingInternetRequest> pendingInternetRequests;
4179 std::shared_ptr<Task> internetCleanupTask = nullptr;
4180 uint32_t internetRequestTimeout = 30000; // Default 30 seconds
4181 uint8_t internetRetryCount = 3; // Default 3 retries
4182 uint32_t internetRetryDelay = 1000; // Default 1 second base delay
4183 bool sendToInternetEnabled = false;
4184
4185 // TCP connect retry parameters, see setTcpRetryConfig().
4186 // Public because tcp::connect<Connection, wifi::Mesh> reads it but is NOT a
4187 // friend: the friend declaration below names connect<T, Mesh<T>>, whereas
4188 // wifi::Mesh::tcpConnect() instantiates connect<Connection, wifi::Mesh> - a
4189 // different specialisation. Same reason as the public: sections above.
4191
4192 friend T;
4193 friend void onDataCb(void *, AsyncClient *, void *, size_t);
4194 friend void tcpSentCb(void *, AsyncClient *, size_t, uint32_t);
4195 friend void meshRecvCb(void *, AsyncClient *, void *, size_t);
4196 friend void painlessmesh::ntp::handleTimeSync<Mesh, T>(
4197 Mesh &, painlessmesh::protocol::TimeSync, std::shared_ptr<T>, uint32_t);
4198 friend void painlessmesh::ntp::handleTimeDelay<Mesh, T>(
4199 Mesh &, painlessmesh::protocol::TimeDelay, std::shared_ptr<T>, uint32_t);
4200 friend void painlessmesh::router::handleNodeSync<Mesh, T>(
4201 Mesh &, protocol::NodeTree, std::shared_ptr<T> conn);
4202 friend void painlessmesh::tcp::initServer<T, Mesh>(AsyncServer &, Mesh &);
4203 friend void painlessmesh::tcp::connect<T, Mesh>(AsyncClient &, IPAddress,
4204 uint16_t, Mesh &, uint8_t);
4205};
4206
4209 public:
4211 bool station = true;
4212 bool newConnection = true;
4213
4217
4218 // Connection metrics tracking
4219 uint32_t messagesRx = 0;
4220 uint32_t messagesTx = 0;
4221 uint32_t messagesDropped = 0;
4222 uint32_t timeLastReceived = 0;
4223 uint64_t bytesRx = 0;
4224 uint64_t bytesTx = 0;
4225
4226 // Latency tracking (rolling window)
4227 std::vector<uint32_t> latencySamples;
4228 static const size_t MAX_LATENCY_SAMPLES = 10;
4229
4231 bool station)
4232 : painlessmesh::tcp::BufferedConnection(client),
4233 mesh(mesh),
4234 station(station) {}
4235
4236 void initTasks() {
4237 auto self = this->shared_from_this();
4238 auto mesh = this->mesh;
4239 this->onReceive([self](const TSTRING &str) {
4240 auto variant = painlessmesh::protocol::Variant(str);
4241
4242 router::routePackage<painlessmesh::Connection>(
4243 (*self->mesh), self->shared_from_this(), str,
4244 self->mesh->callbackList, self->mesh->getNodeTime());
4245 });
4246
4247 this->onDisconnect([mesh, self]() {
4248 Log.remote("id:%u AsyncClient disconnect\n", self->nodeId);
4249 self->timeSyncTask.setCallback(NULL);
4250 self->timeSyncTask.disable();
4251 self->nodeSyncTask.setCallback(NULL);
4252 self->nodeSyncTask.disable();
4253 self->timeOutTask.setCallback(NULL);
4254 self->timeOutTask.disable();
4255 auto nodeId = self->nodeId;
4256 auto station = self->station;
4257 mesh->addTask([mesh, nodeId, station]() {
4258 mesh->changedConnectionCallbacks.execute(nodeId);
4259 mesh->droppedConnectionCallbacks.execute(nodeId, station);
4260 });
4261 self->clear();
4262 });
4263
4264 using namespace logger;
4265
4266 timeOutTask.set(NODE_TIMEOUT, TASK_ONCE, [self]() {
4267 Log(CONNECTION, "Time out reached\n");
4268 Log.remote("id:%u TimeOut\n", self->nodeId);
4269 self->close();
4270 });
4271 mesh->mScheduler->addTask(timeOutTask);
4272
4273 this->nodeSyncTask.set(TASK_MINUTE, TASK_FOREVER, [self]() {
4274 Log(SYNC, "nodeSyncTask(): request with %u\n", self->nodeId);
4275 router::send<protocol::NodeSyncRequest, Connection>(
4276 self->request(self->mesh->asNodeTree()), self);
4277 self->timeOutTask.disable();
4278 self->timeOutTask.restartDelayed();
4279 });
4280
4281 mesh->mScheduler->addTask(this->nodeSyncTask);
4282 if (station)
4283 this->nodeSyncTask.enable();
4284 else
4285 this->nodeSyncTask.enableDelayed(10 * TASK_SECOND);
4286
4287 Log(CONNECTION, "painlessmesh::Connection: New connection established.\n");
4288 this->initialize(mesh->mScheduler);
4289 }
4290
4291 bool addMessage(const TSTRING &msg, bool priority = false) {
4292 return this->write(msg, priority);
4293 }
4294
4301 bool addMessageWithPriority(const TSTRING &msg, uint8_t priorityLevel) {
4302 return this->writeWithPriority(msg, priorityLevel);
4303 }
4304
4308 void onMessageReceived(size_t bytes = 0) {
4309 messagesRx++;
4310 bytesRx += bytes;
4311 timeLastReceived = millis();
4312 }
4313
4317 void onMessageSent(bool success, size_t bytes = 0) {
4318 if (success) {
4319 messagesTx++;
4320 bytesTx += bytes;
4321 } else {
4322 messagesDropped++;
4323 }
4324 }
4325
4329 void recordLatency(uint32_t latencyMs) {
4330 latencySamples.push_back(latencyMs);
4331 if (latencySamples.size() > MAX_LATENCY_SAMPLES) {
4332 latencySamples.erase(latencySamples.begin());
4333 }
4334 }
4335
4341 if (latencySamples.empty()) return -1;
4342
4343 uint32_t sum = 0;
4344 for (auto sample : latencySamples) {
4345 sum += sample;
4346 }
4347 return sum / latencySamples.size();
4348 }
4349
4355 // Start with perfect quality
4356 int quality = 100;
4357
4358 // Penalize high latency (>100ms)
4359 int latency = getLatency();
4360 if (latency > 100) {
4361 quality -= (latency - 100) / 5;
4362 }
4363
4364 // Penalize packet loss
4365 if (messagesTx > 0) {
4366 int lossRate = (messagesDropped * 100) / messagesTx;
4367 quality -= lossRate;
4368 }
4369
4370 // Penalize weak RSSI (if available)
4371 int rssi = getRSSI();
4372 if (rssi < -80 && rssi != 0) {
4373 quality -= (80 + rssi); // e.g., -90 dBm = penalty of 10
4374 }
4375
4376 return (std::max)(0, (std::min)(100, quality));
4377 }
4378
4383 int getRSSI() {
4384#if defined(ESP32)
4385 // On ESP32, get WiFi RSSI
4386 if (WiFi.status() == WL_CONNECTED) {
4387 return WiFi.RSSI();
4388 }
4389 return 0;
4390#elif defined(ESP8266)
4391 // On ESP8266, get WiFi RSSI
4392 if (WiFi.status() == WL_CONNECTED) {
4393 return WiFi.RSSI();
4394 }
4395 return 0;
4396#else
4397 // Not available on non-WiFi platforms
4398 return 0;
4399#endif
4400 }
4401
4402 protected:
4403 std::shared_ptr<Connection> shared_from_this() { return shared_from(this); }
4404};
4405}; // namespace painlessmesh
4406#endif
#define PAINLESSMESH_MAX_QUEUED_BROADCAST_ACKS
Definition ack.hpp:25
boost::asio::ip::address IPAddress
Definition asynctcp.hpp:29
Definition asynctcp.hpp:31
Definition asynctcp.hpp:217
Definition mesh.hpp:173
bool isHealthy(uint32_t timeoutMs=60000) const
Definition mesh.hpp:188
bool internetConnected
Definition mesh.hpp:176
uint32_t timestamp
Definition mesh.hpp:182
uint32_t nodeId
Definition mesh.hpp:175
uint32_t lastSeen
Definition mesh.hpp:179
uint32_t uptime
Definition mesh.hpp:180
int8_t routerRSSI
Definition mesh.hpp:177
TSTRING gatewayIP
Definition mesh.hpp:181
uint8_t routerChannel
Definition mesh.hpp:178
Definition mesh.hpp:4208
void recordLatency(uint32_t latencyMs)
Definition mesh.hpp:4329
int getRSSI()
Definition mesh.hpp:4383
int getLatency()
Definition mesh.hpp:4340
Task nodeSyncTask
Definition mesh.hpp:4215
bool addMessage(const TSTRING &msg, bool priority=false)
Definition mesh.hpp:4291
int getQuality()
Definition mesh.hpp:4354
bool addMessageWithPriority(const TSTRING &msg, uint8_t priorityLevel)
Definition mesh.hpp:4301
Task timeOutTask
Definition mesh.hpp:4216
std::shared_ptr< Connection > shared_from_this()
Definition mesh.hpp:4403
void initTasks()
Definition mesh.hpp:4236
std::vector< uint32_t > latencySamples
Definition mesh.hpp:4227
void onMessageReceived(size_t bytes=0)
Definition mesh.hpp:4308
Task timeSyncTask
Definition mesh.hpp:4214
Connection(AsyncClient *client, Mesh< painlessmesh::Connection > *mesh, bool station)
Definition mesh.hpp:4230
void onMessageSent(bool success, size_t bytes=0)
Definition mesh.hpp:4317
Mesh< Connection > * mesh
Definition mesh.hpp:4210
Definition mesh.hpp:287
friend void onDataCb(void *, AsyncClient *, void *, size_t)
size_t apChildren()
Definition mesh.hpp:1194
friend void meshRecvCb(void *, AsyncClient *, void *, size_t)
bool sendBroadcast(TSTRING msg, Priority priorityLevel, IncludeSelf includeSelf=false)
Definition mesh.hpp:840
void enableBridgeStatusBroadcast(bool enabled)
Definition mesh.hpp:1390
void setTimeAuthority(bool hasAuthority)
Definition mesh.hpp:2843
void stop()
Definition mesh.hpp:516
void enableBridgeCleanup()
Definition mesh.hpp:1459
BridgeChangeEvent getLastBridgeChange()
Definition mesh.hpp:3661
bool sendSingle(uint32_t destId, TSTRING msg, const SendOptions &options)
Definition mesh.hpp:620
void queueBroadcastAck(uint32_t origin, uint32_t msgId)
Definition mesh.hpp:3937
void onReceive(receivedCallback_t onReceive)
Definition mesh.hpp:886
std::list< uint32_t > getNodeList(bool includeSelf=false)
Definition mesh.hpp:3094
callback::List< uint32_t > newConnectionCallbacks
Definition mesh.hpp:4094
bool syncRTCFromNTP(uint32_t ntpTimestamp)
Definition mesh.hpp:2744
bool sendBroadcast(TSTRING msg, bool includeSelf, ack::deliveryCallback_t ackCallback, uint32_t ackTimeoutMs=5000)
Definition mesh.hpp:787
void setInternetRetryCount(uint8_t retryCount)
Definition mesh.hpp:1980
bool enableRTC(rtc::RTCInterface *rtcInterface)
Definition mesh.hpp:2699
void setInternetRequestTimeout(uint32_t timeoutMs)
Definition mesh.hpp:1971
bool cancelInternetRequest(uint32_t messageId)
Definition mesh.hpp:2034
uint32_t pruneQueue(uint32_t maxAgeMs)
Definition mesh.hpp:3063
std::map< uint32_t, uint32_t > getRoutingTable()
Definition mesh.hpp:3210
void setInternetRetryDelay(uint32_t delayMs)
Definition mesh.hpp:1989
void setBridgeStatusInterval(uint32_t intervalMs)
Definition mesh.hpp:1372
void onNodeTimeAdjusted(nodeTimeAdjustedCallback_t onTimeAdjusted)
Definition mesh.hpp:966
BridgeStatus getBridgeStatus()
Definition mesh.hpp:3593
friend void tcpSentCb(void *, AsyncClient *, size_t, uint32_t)
uint32_t getQueuedMessageCount(MessagePriority priority)
Definition mesh.hpp:3009
void resetHealthMetrics()
Definition mesh.hpp:3443
uint32_t getAccurateTime()
Definition mesh.hpp:2770
rtc::RTCType getRTCType() const
Definition mesh.hpp:2795
uint32_t metricsDisconnectCount
Definition mesh.hpp:4160
bool isRoot()
Definition mesh.hpp:506
std::shared_ptr< Task > ackCheckTask
Definition mesh.hpp:4005
uint32_t getTimeSinceRTCSync() const
Definition mesh.hpp:2804
void sendGatewayAck(const gateway::GatewayDataPackage &request, bool success, uint16_t httpStatus, const TSTRING &error, std::shared_ptr< T > ingressConnection=nullptr, const TSTRING &response=TSTRING(), int8_t retryable=-1, uint32_t retryAfterMs=0)
Definition mesh.hpp:2138
size_t getGatewayCount()
Definition mesh.hpp:1333
std::shared_ptr< Task > addTask(std::function< void()> aCallback)
Definition mesh.hpp:3897
std::vector< std::pair< uint32_t, uint32_t > > pendingBroadcastAcks
Definition mesh.hpp:4006
void reclaimRetiredSchedulers()
Definition mesh.hpp:3923
bool uplinkServesLocally()
Definition mesh.hpp:1539
void setTcpRetryConfig(const painlessmesh::tcp::TcpRetryConfig &config)
Definition mesh.hpp:2006
void disableSendToInternet()
Definition mesh.hpp:2092
int getHopCount(uint32_t nodeId)
Definition mesh.hpp:3150
uint32_t queueMessage(const TSTRING &payload, const TSTRING &destination="", MessagePriority priority=PRIORITY_NORMAL)
Definition mesh.hpp:2936
gateway::InternetStatus getInternetStatus()
Definition mesh.hpp:1589
uint32_t incrementQueuedMessageAttempts(uint32_t messageId)
Definition mesh.hpp:2995
BridgeInfo * getLastKnownBridge()
Definition mesh.hpp:1138
BridgeHealthMetrics getBridgeHealthMetrics()
Definition mesh.hpp:3346
std::vector< QueuedMessage > flushMessageQueue()
Definition mesh.hpp:2967
void resetInternetHealthStats()
Definition mesh.hpp:1712
localInternetChangedCallback_t localInternetChangedCallback
Definition mesh.hpp:4175
bool getNodeNeighbors(const protocol::NodeTree &tree, uint32_t nodeId, std::list< uint32_t > &neighbors, uint32_t parent=0) const
Definition mesh.hpp:4041
void onLocalInternetChanged(localInternetChangedCallback_t callback)
Definition mesh.hpp:1568
ack::AckTracker ackTracker
Definition mesh.hpp:4004
void onChangedConnections(changedConnectionsCallback_t onChangedConnections)
Definition mesh.hpp:945
uint32_t getQueuedMessageCount()
Definition mesh.hpp:3017
rtcSyncCompleteCallback_t rtcSyncCompleteCallback
Definition mesh.hpp:4100
bool isConnected(uint32_t nodeId)
Definition mesh.hpp:3085
std::vector< ConnectionInfo > getConnectionDetails()
Definition mesh.hpp:3123
void clearQueue()
Definition mesh.hpp:3074
bool getTimeAuthority() const
Definition mesh.hpp:2864
void configureInternetHealthCheck(const gateway::SharedGatewayConfig &config)
Definition mesh.hpp:1601
void ensureAckScheduling()
Definition mesh.hpp:3974
QueueStats getQueueStats()
Definition mesh.hpp:3030
void onHealthMetricsUpdate(std::function< void(BridgeHealthMetrics)> callback, uint32_t intervalMs=60000)
Definition mesh.hpp:3546
void setBridgeTimeout(uint32_t timeoutMs)
Definition mesh.hpp:1381
std::vector< BridgeInfo > getBridges()
Definition mesh.hpp:1087
void enableDiagnostics(bool enabled=true)
Definition mesh.hpp:3570
void setInternetCheckInterval(uint32_t intervalMs)
Definition mesh.hpp:1622
nodeTimeAdjustedCallback_t nodeTimeAdjustedCallback
Definition mesh.hpp:4097
TSTRING getHealthMetricsJSON()
Definition mesh.hpp:3476
bool sendBroadcast(TSTRING msg, bool includeSelf=false)
Definition mesh.hpp:686
bool forgetBridge(uint32_t bridgeNodeId)
Definition mesh.hpp:1416
bool hasRTC() const
Definition mesh.hpp:2786
std::vector< ElectionRecord > electionHistory
Definition mesh.hpp:4167
bool isInternetHealthCheckEnabled() const
Definition mesh.hpp:1705
void setRoot(bool on=true)
Definition mesh.hpp:492
bool sendSingle(uint32_t destId, TSTRING msg, uint8_t priorityLevel)
Definition mesh.hpp:598
Scheduler * mScheduler
Definition mesh.hpp:4116
void init(Scheduler *scheduler, uint32_t id)
Definition mesh.hpp:438
void eraseClosedConnections()
Definition mesh.hpp:4081
std::map< uint32_t, PendingInternetRequest > pendingInternetRequests
Definition mesh.hpp:4178
TSTRING getDiagnosticReport()
Definition mesh.hpp:3822
callback::List< uint32_t > changedConnectionCallbacks
Definition mesh.hpp:4096
uint32_t sendToInternet(TSTRING destination, TSTRING payload, internetResponseCallback_t onResult, uint8_t priority=static_cast< uint8_t >(gateway::GatewayPriority::PRIORITY_NORMAL))
Definition mesh.hpp:1793
size_t getPendingInternetRequestCount() const
Definition mesh.hpp:2024
uint32_t getPrimaryGateway()
Definition mesh.hpp:1283
void setInternetCheckTarget(const TSTRING &host, uint16_t port=53)
Definition mesh.hpp:1613
void cleanupExpiredBridges()
Definition mesh.hpp:1427
void setScheduler(Scheduler *baseScheduler)
Definition mesh.hpp:4009
rtc::RTCManager rtcManager
Definition mesh.hpp:4163
void enableMessageQueue(bool enabled, uint32_t maxSize=1000)
Definition mesh.hpp:2903
bool isBridgeCleanupEnabled() const
Definition mesh.hpp:1496
bool sendBroadcast(TSTRING msg, const SendOptions &options, bool includeSelf=false)
Definition mesh.hpp:712
std::list< uint32_t > getGateways()
Definition mesh.hpp:1310
void setInternetCheckTimeout(uint32_t timeoutMs)
Definition mesh.hpp:1631
std::vector< BridgeInfo > knownBridges
Definition mesh.hpp:4147
bool startDelayMeas(uint32_t id)
Definition mesh.hpp:854
bool closeConnectionSTA()
Definition mesh.hpp:4068
bool sendSingle(uint32_t destId, TSTRING msg)
Definition mesh.hpp:586
void onBridgeStatusChanged(bridgeStatusChangedCallback_t onBridgeStatusChanged)
Definition mesh.hpp:1003
void onQueueStateChanged(queueStateChangedCallback_t callback)
Definition mesh.hpp:3051
void disableInternetHealthCheck()
Definition mesh.hpp:1691
bool capacityCheck(uint32_t freeHeap, uint32_t floor)
Definition mesh.hpp:1221
void enableSendToInternet()
Definition mesh.hpp:2063
bool hasActiveMeshConnections()
Definition mesh.hpp:1168
void updateBridgeStatus(uint32_t bridgeNodeId, bool internetConnected, int8_t routerRSSI, uint8_t routerChannel, uint32_t uptime, TSTRING gatewayIP, uint32_t timestamp)
Definition mesh.hpp:2582
void setContainsRoot(bool on=true)
Definition mesh.hpp:501
callback::List< uint32_t, bool > droppedConnectionCallbacks
Definition mesh.hpp:4095
size_t checkAcks()
Definition mesh.hpp:806
painlessmesh::tcp::TcpRetryConfig tcpRetryConfig
Definition mesh.hpp:4190
void enableInternetHealthCheck()
Definition mesh.hpp:1661
void disableRTC()
Definition mesh.hpp:2713
nodeDelayCallback_t nodeDelayReceivedCallback
Definition mesh.hpp:4098
TSTRING exportTopologyDOT()
Definition mesh.hpp:3679
bool isExternalScheduler
Definition mesh.hpp:4110
painlessmesh::tcp::TcpRetryConfig getTcpRetryConfig() const
Definition mesh.hpp:2015
bool isPrimaryGateway()
Definition mesh.hpp:1258
bool overCapacity()
Definition mesh.hpp:1214
gateway::InternetHealthChecker internetHealthChecker
Definition mesh.hpp:4173
bool semaphoreTake()
Definition mesh.hpp:4127
void onNodeDelayReceived(nodeDelayCallback_t onDelayReceived)
Definition mesh.hpp:983
bool sendSingle(uint32_t destId, TSTRING msg, ack::deliveryCallback_t ackCallback, uint32_t ackTimeoutMs=5000)
Definition mesh.hpp:671
void update(void)
Definition mesh.hpp:562
uint32_t sendToInternet(TSTRING destination, TSTRING payload, internetResultCallback_t callback, uint8_t priority=static_cast< uint8_t >(gateway::GatewayPriority::PRIORITY_NORMAL))
Definition mesh.hpp:1774
size_t pendingAcks() const
Definition mesh.hpp:821
~Mesh()
Definition mesh.hpp:3914
bool isSendToInternetEnabled() const
Definition mesh.hpp:2115
BridgeTestResult testBridgeConnectivity()
Definition mesh.hpp:3741
TSTRING subConnectionJson(bool pretty=false)
Definition mesh.hpp:3101
std::vector< uint32_t > getPathToNode(uint32_t nodeId)
Definition mesh.hpp:3274
bridgeStatusChangedCallback_t bridgeStatusChangedCallback
Definition mesh.hpp:4099
void onNewConnection(newConnectionCallback_t onNewConnection)
Definition mesh.hpp:914
bool removeQueuedMessage(uint32_t messageId)
Definition mesh.hpp:2981
friend T
Definition mesh.hpp:4192
bool hasLocalInternet()
Definition mesh.hpp:1521
void semaphoreGive()
Definition mesh.hpp:4140
void onDroppedConnection(droppedConnectionCallback_t onDroppedConnection)
Definition mesh.hpp:930
gatewayChangedCallback_t gatewayChangedCallback
Definition mesh.hpp:4101
bool isBridgeReachable(uint32_t bridgeNodeId)
Definition mesh.hpp:3798
bool isBridge()
Definition mesh.hpp:1183
bool checkInternetNow()
Definition mesh.hpp:1643
std::shared_ptr< Task > broadcastAckTask
Definition mesh.hpp:4007
std::vector< uint32_t > getNodesWithInternet()
Definition mesh.hpp:1072
BridgeChangeEvent lastBridgeChange
Definition mesh.hpp:4169
std::shared_ptr< Task > addTask(unsigned long aInterval, long aIterations, std::function< void()> aCallback)
Definition mesh.hpp:3890
void init(uint32_t id)
Definition mesh.hpp:289
BridgeInfo * getPrimaryBridge()
Definition mesh.hpp:1109
void startTimeSync(std::shared_ptr< T > conn)
Definition mesh.hpp:4015
std::shared_ptr< Task > addTask(std::function< void()> aCallback, unsigned long delayMs)
Definition mesh.hpp:3908
void disableBridgeCleanup()
Definition mesh.hpp:1480
void setDebugMsgTypes(uint16_t types)
Definition mesh.hpp:511
bool hasInternetConnection()
Definition mesh.hpp:1046
void onRTCSyncComplete(rtcSyncCompleteCallback_t onRTCSyncComplete)
Definition mesh.hpp:2819
void onGatewayChanged(gatewayChangedCallback_t callback)
Definition mesh.hpp:1362
std::vector< ElectionRecord > getElectionHistory()
Definition mesh.hpp:3638
Definition message_queue.hpp:112
Definition ack.hpp:123
bool collectAck(uint32_t msgId, uint32_t fromNode, uint32_t now, std::list< DeliveryResult > &results)
Definition ack.hpp:196
void seed(uint32_t value)
Definition ack.hpp:133
Definition callback.hpp:18
int execute(Args... args)
Definition callback.hpp:20
void push_back(std::function< void(Args...)> func)
Definition callback.hpp:45
Gateway Acknowledgment Package for delivery confirmations.
Definition gateway.hpp:1651
TSTRING response
The start of the response body, summarized to one line.
Definition gateway.hpp:1709
bool success
Delivery success status.
Definition gateway.hpp:1675
uint32_t messageId
Original message ID being acknowledged.
Definition gateway.hpp:1659
int8_t retryable
Whether the origin node may resend the identical request.
Definition gateway.hpp:1719
uint32_t timestamp
Acknowledgment timestamp.
Definition gateway.hpp:1700
uint32_t retryAfterMs
How long the server asked to wait before a retry, in milliseconds.
Definition gateway.hpp:1727
uint16_t httpStatus
HTTP response code (if applicable)
Definition gateway.hpp:1684
uint32_t originNode
Original sender node ID.
Definition gateway.hpp:1667
TSTRING error
Error message (if failed)
Definition gateway.hpp:1692
Gateway Data Package for routing Internet requests through mesh.
Definition gateway.hpp:815
uint32_t timestamp
Creation timestamp.
Definition gateway.hpp:839
uint8_t priority
Message priority (0=CRITICAL, 1=HIGH, 2=NORMAL, 3=LOW)
Definition gateway.hpp:847
TSTRING destination
Destination URL or endpoint.
Definition gateway.hpp:857
TSTRING payload
Application payload data.
Definition gateway.hpp:865
TSTRING contentType
MIME content type.
Definition gateway.hpp:875
uint32_t messageId
Unique message identifier.
Definition gateway.hpp:823
bool requiresAck
Whether acknowledgment is required.
Definition gateway.hpp:891
uint32_t requestNonce
Random value drawn once per sendToInternet() call.
Definition gateway.hpp:903
uint32_t originNode
Node ID that originated this message.
Definition gateway.hpp:831
uint8_t retryCount
Number of relay attempts.
Definition gateway.hpp:883
static uint32_t generateMessageId(uint32_t nodeId)
Generate a unique message ID.
Definition gateway.hpp:1011
Internet Health Checker class for periodic connectivity monitoring.
Definition gateway.hpp:498
uint32_t nodeId
Definition layout.hpp:125
Definition layout.hpp:159
void remote(const char *format...)
Definition logger.hpp:160
void setLogLevel(uint16_t newTypes)
Definition logger.hpp:56
Definition ntp.hpp:33
uint32_t from
Definition plugin.hpp:71
Definition plugin.hpp:178
bool sendPackage(const protocol::PackageInterface *pkg)
Definition plugin.hpp:223
void reclaimQuarantinedTasks()
Definition plugin.hpp:294
std::shared_ptr< Task > addTask(Scheduler &scheduler, unsigned long aInterval, long aIterations, std::function< void()> aCallback)
Definition plugin.hpp:255
callback::MeshPackageCallbackList< T > callbackList
Definition plugin.hpp:307
void stop(Scheduler *scheduler=nullptr)
Definition plugin.hpp:185
uint32_t dest
Definition plugin.hpp:46
uint32_t from
Definition plugin.hpp:45
TSTRING md5
Definition ota.hpp:78
size_t noPart
Definition ota.hpp:115
bool compressed
Definition ota.hpp:113
bool forced
Definition ota.hpp:97
TSTRING role
The type of node the firmware is meant for.
Definition ota.hpp:91
TSTRING hardware
Definition ota.hpp:79
bool broadcasted
Definition ota.hpp:105
Definition protocol.hpp:146
Definition protocol.hpp:167
std::list< NodeTree > subs
Definition protocol.hpp:172
uint32_t nodeId
Definition protocol.hpp:169
Definition protocol.hpp:93
Definition protocol.hpp:463
Definition protocol.hpp:355
Definition protocol.hpp:481
int type()
Definition protocol.hpp:750
uint32_t from()
Definition protocol.hpp:758
uint32_t msgId()
Definition protocol.hpp:766
T to()
Definition protocol.hpp:743
Definition rtc.hpp:30
Definition rtc.hpp:76
Definition connection.hpp:151
static uint32_t generate()
Definition validation.hpp:54
#define NODE_TIMEOUT
Definition configuration.hpp:105
String TSTRING
Definition configuration.hpp:132
Shared Gateway Mode configuration for painlessMesh.
#define PAINLESSMESH_ACK_CHECK_INTERVAL_MS
Definition mesh.hpp:3993
std::function< void(uint32_t nodeId, bool delivered, uint32_t latencyMs)> deliveryCallback_t
Definition ack.hpp:53
@ PRIORITY_NORMAL
Normal priority - standard processing.
bool reserveGatewayBlockingBudget(T &connection)
Definition gateway.hpp:1929
bool responseContains(const TSTRING &haystack, const char *needle)
Definition gateway.hpp:1162
uint32_t newRequestNonce()
Definition gateway.hpp:1432
std::list< uint32_t > asList(protocol::NodeTree nodeTree, bool includeSelf=true)
Definition layout.hpp:262
@ GENERAL
Definition logger.hpp:26
@ ERROR
Definition logger.hpp:19
@ COMMUNICATION
Definition logger.hpp:25
@ DEBUG
Definition logger.hpp:30
void addPackageCallback(callback::MeshPackageCallbackList< U > &callbackList, T &mesh)
Definition ntp.hpp:252
void addSendPackageCallback(Scheduler &scheduler, plugin::PackageHandler< T > &mesh, otaDataPacketCallbackType_t callback, size_t otaPartSize)
Definition ota.hpp:348
void addReceivePackageCallback(Scheduler &scheduler, plugin::PackageHandler< T > &mesh, TSTRING role="", std::function< void(int, int)> progress_cb=NULL)
Definition ota.hpp:388
std::function< size_t(painlessmesh::plugin::ota::DataRequest, char *buffer)> otaDataPacketCallbackType_t
Definition ota.hpp:345
constexpr int GATEWAY_ACK
Definition protocol.hpp:62
constexpr uint8_t PRIORITY_NORMAL
Definition protocol.hpp:73
constexpr int MESSAGE_ACK
Definition protocol.hpp:66
@ SINGLE
Definition protocol.hpp:42
@ BROADCAST
Definition protocol.hpp:41
constexpr int GATEWAY_DATA
Definition protocol.hpp:61
constexpr int BRIDGE_STATUS
Definition protocol.hpp:54
void addPackageCallback(callback::MeshPackageCallbackList< U > &callbackList, T &mesh)
Definition router.hpp:465
bool send(T &package, std::shared_ptr< U > conn, bool priority=false)
Definition router.hpp:65
bool sendWithPriority(T &package, std::shared_ptr< U > conn, uint8_t priorityLevel)
Definition router.hpp:98
RTCType
Definition rtc.hpp:15
TcpRetryConfig clampTcpRetryConfig(TcpRetryConfig cfg)
Definition tcp.hpp:101
Definition wifi.hpp:34
std::function< void(uint32_t timestamp)> rtcSyncCompleteCallback_t
Definition mesh.hpp:38
std::function< void()> changedConnectionsCallback_t
Definition mesh.hpp:34
std::function< void(bool success, uint16_t httpStatus, TSTRING error)> internetResultCallback_t
Definition mesh.hpp:82
std::function< void(QueueState state, uint32_t messageCount)> queueStateChangedCallback_t
Definition message_queue.hpp:73
std::function< void(uint32_t nodeId)> newConnectionCallback_t
Definition mesh.hpp:31
MessagePriority
Definition message_queue.hpp:18
@ PRIORITY_NORMAL
Definition message_queue.hpp:21
std::function< void(uint32_t from, TSTRING &msg)> receivedCallback_t
Definition mesh.hpp:33
std::function< void(int32_t offset)> nodeTimeAdjustedCallback_t
Definition mesh.hpp:35
std::function< void(uint32_t bridgeNodeId, bool internetAvailable)> bridgeStatusChangedCallback_t
Definition mesh.hpp:37
std::function< void(const InternetResult &result)> internetResponseCallback_t
Definition mesh.hpp:131
std::function< void(bool available)> localInternetChangedCallback_t
Definition mesh.hpp:39
std::function< void(uint32_t oldPrimary, uint32_t newPrimary)> gatewayChangedCallback_t
Definition mesh.hpp:40
std::function< void(uint32_t nodeId, int32_t delay)> nodeDelayCallback_t
Definition mesh.hpp:36
std::function< void(uint32_t nodeId)> droppedConnectionCallback_t
Definition mesh.hpp:32
Definition mesh.hpp:260
TSTRING reason
Definition mesh.hpp:264
uint32_t oldBridgeId
Definition mesh.hpp:262
uint32_t timestamp
Definition mesh.hpp:261
uint32_t newBridgeId
Definition mesh.hpp:263
bool internetAvailable
Definition mesh.hpp:265
Definition mesh.hpp:201
uint32_t messagesQueued
Definition mesh.hpp:219
uint32_t messagesTx
Definition mesh.hpp:218
uint64_t bytesTx
Definition mesh.hpp:216
int8_t minRSSI
Definition mesh.hpp:211
int8_t avgRSSI
Definition mesh.hpp:210
uint32_t avgLatencyMs
Definition mesh.hpp:223
uint32_t currentUptime
Definition mesh.hpp:206
uint32_t internetUptimeSeconds
Definition mesh.hpp:204
uint32_t meshNodeCount
Definition mesh.hpp:225
uint32_t messagesDropped
Definition mesh.hpp:220
uint32_t messagesRx
Definition mesh.hpp:217
int8_t maxRSSI
Definition mesh.hpp:212
int8_t currentRSSI
Definition mesh.hpp:209
uint32_t uptimeSeconds
Definition mesh.hpp:203
uint8_t packetLossPercent
Definition mesh.hpp:224
uint64_t bytesRx
Definition mesh.hpp:215
uint32_t totalDisconnects
Definition mesh.hpp:205
Definition mesh.hpp:233
bool internetConnected
Definition mesh.hpp:235
uint32_t timeSinceBridgeChange
Definition mesh.hpp:239
TSTRING role
Definition mesh.hpp:236
int8_t bridgeRSSI
Definition mesh.hpp:238
uint32_t bridgeNodeId
Definition mesh.hpp:237
bool isBridge
Definition mesh.hpp:234
Definition mesh.hpp:273
bool success
Definition mesh.hpp:274
TSTRING message
Definition mesh.hpp:278
bool internetReachable
Definition mesh.hpp:276
uint32_t latencyMs
Definition mesh.hpp:277
bool bridgeReachable
Definition mesh.hpp:275
Definition mesh.hpp:247
uint32_t candidateCount
Definition mesh.hpp:251
uint32_t timestamp
Definition mesh.hpp:248
uint32_t winnerNodeId
Definition mesh.hpp:249
TSTRING reason
Definition mesh.hpp:252
int8_t winnerRSSI
Definition mesh.hpp:250
Definition mesh.hpp:100
bool retryable
Definition mesh.hpp:116
uint8_t attempts
Definition mesh.hpp:118
bool success
Definition mesh.hpp:104
uint16_t httpStatus
Definition mesh.hpp:106
uint32_t messageId
Definition mesh.hpp:102
TSTRING response
Definition mesh.hpp:110
TSTRING error
Definition mesh.hpp:108
Definition mesh.hpp:3108
uint32_t nodeId
Definition mesh.hpp:3109
uint32_t messagesRx
Definition mesh.hpp:3115
int quality
Definition mesh.hpp:3114
int rssi
Definition mesh.hpp:3111
uint32_t lastSeen
Definition mesh.hpp:3110
int avgLatency
Definition mesh.hpp:3112
uint32_t messagesTx
Definition mesh.hpp:3116
uint32_t messagesDropped
Definition mesh.hpp:3117
int hopCount
Definition mesh.hpp:3113
uint8_t priority
Priority level (0=CRITICAL to 3=LOW)
Definition mesh.hpp:143
internetResponseCallback_t onResult
Definition mesh.hpp:150
uint32_t gatewayNodeId
Target gateway node ID.
Definition mesh.hpp:146
TSTRING lastError
Definition mesh.hpp:158
uint32_t timestamp
When request was sent (millis)
Definition mesh.hpp:140
uint32_t messageId
Unique message ID.
Definition mesh.hpp:139
uint32_t retryDelayMs
Current retry delay (for exponential backoff)
Definition mesh.hpp:145
uint8_t attempts
Definition mesh.hpp:153
uint8_t maxRetries
Maximum retry attempts.
Definition mesh.hpp:142
uint32_t requestNonce
Drawn once for the call, sent on every attempt (GatewayDataPackage).
Definition mesh.hpp:155
internetResultCallback_t callback
User callback for result.
Definition mesh.hpp:149
bool isTimedOut() const
Definition mesh.hpp:163
uint32_t timeoutMs
Timeout in milliseconds.
Definition mesh.hpp:144
TSTRING payload
Request payload.
Definition mesh.hpp:148
TSTRING destination
Internet destination URL.
Definition mesh.hpp:147
uint8_t retryCount
Number of retries attempted.
Definition mesh.hpp:141
Definition message_queue.hpp:58
Definition mesh.hpp:62
uint8_t priority
Definition mesh.hpp:65
ack::deliveryCallback_t ackCallback
Definition mesh.hpp:69
uint32_t ackTimeoutMs
Definition mesh.hpp:71
Structure to hold Internet connectivity check results.
Definition gateway.hpp:431
Configuration structure for Shared Gateway Mode.
Definition gateway.hpp:162
painlessmesh::logger::LogClass Log
Definition wifi.cpp:2