@alteriom/painlessmesh 1.8.14 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BRIDGE_TO_INTERNET.md +229 -0
- package/CHANGELOG.md +89 -1
- package/CONTRIBUTING.md +79 -0
- package/README.md +70 -143
- package/docs/README.md +1 -0
- package/docs/api/shared-gateway.md +1207 -0
- package/docs/troubleshooting/common-issues.md +28 -0
- package/docs/troubleshooting/faq.md +113 -12
- package/examples/basic/test/simulator/CMakeLists.txt +40 -0
- package/examples/basic/test/simulator/README.md +149 -0
- package/examples/basic/test/simulator/firmware/basic_firmware.hpp +117 -0
- package/examples/basic/test/simulator/scenarios/basic_mesh_test.yaml +81 -0
- package/examples/bridge/bridge.ino +17 -4
- package/examples/bridge_failover/README.md +81 -0
- package/examples/bridge_failover/bridge_failover.ino +51 -6
- package/examples/sharedGateway/README.md +235 -0
- package/examples/{meshCommandNode → sharedGateway}/platformio.ini +3 -2
- package/examples/sharedGateway/sharedGateway.ino +303 -0
- package/library.json +3 -22
- package/library.properties +1 -1
- package/package.json +3 -3
- package/src/arduino/wifi.hpp +380 -13
- package/src/painlessmesh/gateway.hpp +2120 -0
- package/src/painlessmesh/mesh.hpp +1034 -6
- package/src/painlessmesh/message_tracker.hpp +311 -0
- package/src/painlessmesh/protocol.hpp +6 -0
- package/DOCUMENTATION_INDEX.md +0 -146
- package/docs/API_DESIGN_GUIDELINES.md +0 -414
- package/docs/ARDUINO_LIBRARY_MANAGER_SUBMISSION.md +0 -331
- package/docs/BOOLEAN_NAMING_CONVENTION.md +0 -235
- package/docs/BRIDGE_FAILOVER.md +0 -512
- package/docs/BRIDGE_HEALTH_MONITORING.md +0 -293
- package/docs/CHANNEL_SYNCHRONIZATION.md +0 -209
- package/docs/CREATE_MISSING_RELEASES.md +0 -321
- package/docs/DOCUMENTATION_MIGRATION_PLAN.md +0 -176
- package/docs/FAQ_VERSION_NUMBERS.md +0 -152
- package/docs/IMPLEMENTATION_PLAN_MESH_TOPOLOGY.md +0 -1062
- package/docs/MESH_TOPOLOGY_GUIDE.md +0 -992
- package/docs/MESH_TOPOLOGY_PROGRESS.md +0 -422
- package/docs/MQTT_BRIDGE_COMMANDS.md +0 -894
- package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +0 -324
- package/docs/MQTT_COMMAND_SCHEMA_PROPOSAL.md +0 -576
- package/docs/MQTT_SCHEMA_COMPLIANCE.md +0 -340
- package/docs/MQTT_SCHEMA_PROPOSALS.md +0 -446
- package/docs/MQTT_SCHEMA_REVIEW.md +0 -690
- package/docs/OTA_COMMANDS_REFERENCE.md +0 -554
- package/docs/PHASE1_GUIDE.md +0 -349
- package/docs/PHASE2_GUIDE.md +0 -543
- package/docs/QUICK_REFERENCE_VERSIONING.md +0 -127
- package/docs/RELEASE_AGENT_SUMMARY.md +0 -386
- package/docs/SCHEMA_VALIDATION_CHECKLIST.md +0 -222
- package/docs/VERSION_MANAGEMENT.md +0 -213
- package/docs/archive/DOCUSAURUS_DEPLOYMENT.md +0 -166
- package/docs/archive/FEATURE_PROPOSALS.md +0 -337
- package/docs/archive/LIBRARY_JSON_FIX.md +0 -98
- package/docs/archive/LIBRARY_STRUCTURE_FIX.md +0 -215
- package/docs/archive/PHASE1_IMPLEMENTATION.md +0 -325
- package/docs/archive/PHASE2_IMPLEMENTATION.md +0 -567
- package/docs/archive/RELEASE_SUMMARY.md +0 -173
- package/docs/archive/SCONS_BUILD_FIX.md +0 -313
- package/docs/archive/TRIGGER_RELEASE.md +0 -280
- package/docs/archive/VECTOR_INCLUDE_FIX.md +0 -129
- package/docs/archive/ota-and-status-enhancements.md +0 -911
- package/docs/archive/ota-status-architecture-diagrams.md +0 -658
- package/docs/archive/ota-status-quick-reference.md +0 -284
- package/docs/design/.gitkeep +0 -1
- package/docs/design/STATION_CREDENTIALS_DESIGN.md +0 -182
- package/docs/development/ARDUINO_COMPLIANCE_SUMMARY.md +0 -71
- package/docs/development/CODE_REFACTORING_RECOMMENDATIONS.md +0 -1011
- package/docs/development/DOCKER_TESTING.md +0 -196
- package/docs/development/PLATFORMIO_USAGE.md +0 -180
- package/docs/development/TESTING_SUMMARY.md +0 -126
- package/docs/development/contributing.md +0 -301
- package/docs/development/documentation.md +0 -583
- package/docs/features/DIAGNOSTICS_API.md +0 -534
- package/docs/implementation/BRIDGE_ARCHITECTURE_IMPLEMENTATION.md +0 -340
- package/docs/implementation/BRIDGE_HEALTH_MONITORING_IMPLEMENTATION.md +0 -213
- package/docs/implementation/BRIDGE_STATUS_FEATURE.md +0 -635
- package/docs/implementation/DIAGNOSTICS_API_IMPLEMENTATION.md +0 -232
- package/docs/implementation/IMPLEMENTATION_COMPLETE.md +0 -228
- package/docs/implementation/IMPLEMENTATION_NTP_TIME_SYNC.md +0 -325
- package/docs/implementation/IMPLEMENTATION_SUMMARY.md +0 -316
- package/docs/implementation/MESSAGE_QUEUE_IMPLEMENTATION.md +0 -405
- package/docs/implementation/MULTI_BRIDGE_IMPLEMENTATION.md +0 -520
- package/docs/implementation/NTP_TIME_SYNC_FEATURE.md +0 -392
- package/docs/improvements/FUTURE_PROPOSALS.md +0 -1016
- package/docs/improvements/IMPLEMENTATION_HISTORY.md +0 -1091
- package/docs/improvements/OTA_STATUS_ENHANCEMENTS.md +0 -709
- package/docs/improvements/README.md +0 -212
- package/docs/internal/CUSTOM_AGENT_ANALYSIS.md +0 -391
- package/docs/internal/ISSUE_65_VERIFICATION.md +0 -947
- package/docs/internal/ISSUE_66_CLOSURE.md +0 -249
- package/docs/internal/ISSUE_66_STATUS.md +0 -316
- package/docs/internal/PR_SUMMARY.md +0 -315
- package/docs/internal/REVIEW_SUMMARY.md +0 -332
- package/docs/multi-bridge-setup.md +0 -1025
- package/docs/platformio-publishing.md +0 -255
- package/docs/platformio-setup-summary.md +0 -121
- package/docs/releases/ANNOUNCEMENT_v1.8.6.md +0 -63
- package/docs/releases/ANNOUNCEMENT_v1.8.7.md +0 -113
- package/docs/releases/BRIDGE_STATUS_SELF_REGISTRATION_FIX.md +0 -221
- package/docs/releases/FEATURE_HISTORY.md +0 -543
- package/docs/releases/GITHUB_RELEASE_v1.8.6.md +0 -71
- package/docs/releases/GITHUB_RELEASE_v1.8.7.md +0 -90
- package/docs/releases/PATCH_v1.7.2.md +0 -262
- package/docs/releases/PATCH_v1.7.3.md +0 -262
- package/docs/releases/PATCH_v1.7.4.md +0 -219
- package/docs/releases/PHASE1_SUMMARY.md +0 -246
- package/docs/releases/PHASE2_SUMMARY.md +0 -499
- package/docs/releases/PUBLISH_v1.8.0_INSTRUCTIONS.md +0 -163
- package/docs/releases/QUICK_START_RELEASES.md +0 -113
- package/docs/releases/RELEASE_CHECKLIST_1.8.10.md +0 -331
- package/docs/releases/RELEASE_CHECKLIST_1.8.9.md +0 -207
- package/docs/releases/RELEASE_CHECKLIST_v1.7.4.md +0 -253
- package/docs/releases/RELEASE_CHECKLIST_v1.7.5.md +0 -315
- package/docs/releases/RELEASE_CHECKLIST_v1.7.6.md +0 -389
- package/docs/releases/RELEASE_CHECKLIST_v1.8.0.md +0 -331
- package/docs/releases/RELEASE_CHECKLIST_v1.8.2.md +0 -309
- package/docs/releases/RELEASE_NOTES_1.7.0.md +0 -539
- package/docs/releases/RELEASE_NOTES_1.8.10.md +0 -239
- package/docs/releases/RELEASE_NOTES_1.8.9.md +0 -213
- package/docs/releases/RELEASE_NOTES_v1.8.0.md +0 -685
- package/docs/releases/RELEASE_NOTES_v1.8.1.md +0 -221
- package/docs/releases/RELEASE_NOTES_v1.8.2.md +0 -421
- package/docs/releases/RELEASE_NOTES_v1.8.3.md +0 -292
- package/docs/releases/RELEASE_NOTES_v1.8.4.md +0 -277
- package/docs/releases/RELEASE_NOTES_v1.8.6.md +0 -205
- package/docs/releases/RELEASE_NOTES_v1.8.7.md +0 -184
- package/docs/releases/RELEASE_PLAN_v1.7.6.md +0 -816
- package/docs/releases/RELEASE_SUMMARY_1.8.10.md +0 -193
- package/docs/releases/RELEASE_SUMMARY_v1.7.4.md +0 -276
- package/docs/releases/RELEASE_SUMMARY_v1.7.5.md +0 -322
- package/docs/releases/RELEASE_SUMMARY_v1.7.6.md +0 -436
- package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +0 -391
- package/docs/releases/RELEASE_SUMMARY_v1.7.8.md +0 -523
- package/docs/releases/RELEASE_SUMMARY_v1.7.9.md +0 -542
- package/docs/troubleshooting/ARDUINO_IDE_VERSION_FIX_SUMMARY.md +0 -229
- package/docs/troubleshooting/ARDUINO_LIBRARY_NAME_FIX.md +0 -197
- package/docs/troubleshooting/CRASH_QUICK_REF.md +0 -93
- package/docs/troubleshooting/ESP32_C6_COMPATIBILITY.md +0 -157
- package/docs/troubleshooting/FREERTOS_ASSERTION_FAILURE.md +0 -288
- package/docs/troubleshooting/FREERTOS_FIX_IMPLEMENTATION.md +0 -267
- package/docs/troubleshooting/NPM_PUBLISHING_ISSUE_SUMMARY.md +0 -110
- package/docs/troubleshooting/PAINLESSMESH_V1.7.4_COMPILATION_ISSUES.md +0 -547
- package/docs/troubleshooting/QUICK_FIX_FREERTOS.md +0 -164
- package/docs/troubleshooting/SENSOR_NODE_CONNECTION_CRASH.md +0 -264
- package/docs/troubleshooting/station-reconnection-issues.md +0 -172
- package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +0 -794
- package/docs/wiki/API-Reference.md +0 -246
- package/docs/wiki/Complete-Documentation.md +0 -123
- package/examples/alteriomImproved/alteriom_sensor_package.hpp +0 -224
- package/examples/alteriomImproved/improved_sensor_node.ino +0 -248
- package/examples/alteriomImproved/platformio.ini +0 -32
- package/examples/alteriomMetricsHealth/alteriom_sensor_package.hpp +0 -796
- package/examples/alteriomMetricsHealth/metrics_health_node.ino +0 -429
- package/examples/alteriomMetricsHealth/platformio.ini +0 -26
- package/examples/alteriomPhase1/alteriom_sensor_package.hpp +0 -224
- package/examples/alteriomPhase1/phase1_features.ino +0 -242
- package/examples/alteriomPhase1/platformio.ini +0 -26
- package/examples/alteriomPhase2/alteriom_sensor_package.hpp +0 -224
- package/examples/alteriomPhase2/phase2_features.ino +0 -186
- package/examples/alteriomPhase2/platformio.ini +0 -26
- package/examples/alteriomSensorNode/alteriom_sensor_node.ino +0 -186
- package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +0 -1227
- package/examples/alteriomSensorNode/platformio.ini +0 -26
- package/examples/bridge/alteriom_sensor_package.hpp +0 -1170
- package/examples/bridge/bridge_health_monitoring_example.ino +0 -188
- package/examples/bridge/enhanced_mqtt_bridge.hpp +0 -610
- package/examples/bridge/enhanced_mqtt_bridge_example.ino +0 -226
- package/examples/bridge/mesh_event_publisher.hpp +0 -253
- package/examples/bridge/mesh_topology_reporter.hpp +0 -303
- package/examples/bridge/mqtt_command_bridge.hpp +0 -459
- package/examples/bridge/mqtt_status_bridge.hpp +0 -519
- package/examples/bridgeAwareSensorNode/alteriom_sensor_package.hpp +0 -1227
- package/examples/bridgeAwareSensorNode/bridgeAwareSensorNode.ino +0 -342
- package/examples/bridgeAwareSensorNode/platformio.ini +0 -26
- package/examples/diagnosticsExample/diagnosticsExample.ino +0 -171
- package/examples/diagnosticsExample/platformio.ini +0 -26
- package/examples/echoNode/echoNode.ino +0 -33
- package/examples/echoNode/platformio.ini +0 -26
- package/examples/meshCommandNode/alteriom_sensor_package.hpp +0 -235
- package/examples/meshCommandNode/meshCommandNode.ino +0 -265
- package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +0 -235
- package/examples/mqttCommandBridge/mesh_event_publisher.hpp +0 -253
- package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +0 -303
- package/examples/mqttCommandBridge/mqttCommandBridge.ino +0 -254
- package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +0 -462
- package/examples/mqttCommandBridge/platformio.ini +0 -27
- package/examples/mqttStatusBridge/mqttStatusBridge.ino +0 -218
- package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +0 -522
- package/examples/mqttStatusBridge/platformio.ini +0 -27
- package/examples/mqttTopologyTest/README.md +0 -467
- package/examples/mqttTopologyTest/mqttTopologyTest.ino +0 -754
- package/examples/mqttTopologyTest/platformio.ini +0 -27
- package/examples/multi_bridge/README.md +0 -346
- package/examples/multi_bridge/primary_bridge.ino +0 -96
- package/examples/multi_bridge/regular_node.ino +0 -141
- package/examples/multi_bridge/secondary_bridge.ino +0 -111
- package/examples/ntpTimeSyncBridge/alteriom_sensor_package.hpp +0 -1383
- package/examples/ntpTimeSyncBridge/ntpTimeSyncBridge.ino +0 -86
- package/examples/ntpTimeSyncNode/alteriom_sensor_package.hpp +0 -1383
- package/examples/ntpTimeSyncNode/ntpTimeSyncNode.ino +0 -109
- package/examples/queued_alarms/README.md +0 -390
- package/examples/queued_alarms/queued_alarms.ino +0 -265
- package/examples/routing_demo/README.md +0 -172
- package/examples/routing_demo/routing_demo.ino +0 -102
- package/examples/rtcIntegration/README.md +0 -294
- package/examples/rtcIntegration/rtcIntegration.ino +0 -210
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#ifndef _PAINLESS_MESH_MESH_HPP_
|
|
2
2
|
#define _PAINLESS_MESH_MESH_HPP_
|
|
3
3
|
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <list>
|
|
4
6
|
#include <vector>
|
|
5
7
|
#include <map>
|
|
6
8
|
#include <set>
|
|
@@ -9,8 +11,10 @@
|
|
|
9
11
|
#include "painlessmesh/configuration.hpp"
|
|
10
12
|
|
|
11
13
|
#include "painlessmesh/connection.hpp"
|
|
14
|
+
#include "painlessmesh/gateway.hpp"
|
|
12
15
|
#include "painlessmesh/logger.hpp"
|
|
13
16
|
#include "painlessmesh/message_queue.hpp"
|
|
17
|
+
#include "painlessmesh/message_tracker.hpp"
|
|
14
18
|
#include "painlessmesh/ntp.hpp"
|
|
15
19
|
#include "painlessmesh/plugin.hpp"
|
|
16
20
|
#include "painlessmesh/protocol.hpp"
|
|
@@ -30,6 +34,44 @@ typedef std::function<void(int32_t offset)> nodeTimeAdjustedCallback_t;
|
|
|
30
34
|
typedef std::function<void(uint32_t nodeId, int32_t delay)> nodeDelayCallback_t;
|
|
31
35
|
typedef std::function<void(uint32_t bridgeNodeId, bool internetAvailable)> bridgeStatusChangedCallback_t;
|
|
32
36
|
typedef std::function<void(uint32_t timestamp)> rtcSyncCompleteCallback_t;
|
|
37
|
+
typedef std::function<void(bool available)> localInternetChangedCallback_t;
|
|
38
|
+
typedef std::function<void(uint32_t oldPrimary, uint32_t newPrimary)> gatewayChangedCallback_t;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Callback type for Internet request results
|
|
42
|
+
*
|
|
43
|
+
* @param success Whether the request was delivered successfully
|
|
44
|
+
* @param httpStatus HTTP status code from the destination (0 if not applicable)
|
|
45
|
+
* @param error Human-readable error message (empty if success)
|
|
46
|
+
*/
|
|
47
|
+
typedef std::function<void(bool success, uint16_t httpStatus, TSTRING error)>
|
|
48
|
+
internetResultCallback_t;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Pending Internet request entry
|
|
52
|
+
*
|
|
53
|
+
* Tracks a pending sendToInternet() request for acknowledgment handling
|
|
54
|
+
*/
|
|
55
|
+
struct PendingInternetRequest {
|
|
56
|
+
uint32_t messageId = 0; ///< Unique message ID
|
|
57
|
+
uint32_t timestamp = 0; ///< When request was sent (millis)
|
|
58
|
+
uint8_t retryCount = 0; ///< Number of retries attempted
|
|
59
|
+
uint8_t maxRetries = 3; ///< Maximum retry attempts
|
|
60
|
+
uint8_t priority = 2; ///< Priority level (0=CRITICAL to 3=LOW)
|
|
61
|
+
uint32_t timeoutMs = 30000; ///< Timeout in milliseconds
|
|
62
|
+
uint32_t retryDelayMs = 1000; ///< Current retry delay (for exponential backoff)
|
|
63
|
+
uint32_t gatewayNodeId = 0; ///< Target gateway node ID
|
|
64
|
+
TSTRING destination = ""; ///< Internet destination URL
|
|
65
|
+
TSTRING payload = ""; ///< Request payload
|
|
66
|
+
internetResultCallback_t callback; ///< User callback for result
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if this request has timed out
|
|
70
|
+
*/
|
|
71
|
+
bool isTimedOut() const {
|
|
72
|
+
return (static_cast<uint32_t>(millis()) - timestamp) > timeoutMs;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
33
75
|
|
|
34
76
|
/**
|
|
35
77
|
* Bridge information structure
|
|
@@ -52,7 +94,9 @@ public:
|
|
|
52
94
|
* A bridge is healthy if we've received a status update within the timeout period
|
|
53
95
|
*/
|
|
54
96
|
bool isHealthy(uint32_t timeoutMs = 60000) const {
|
|
55
|
-
|
|
97
|
+
// Cast millis() to uint32_t to handle overflow correctly
|
|
98
|
+
// This ensures consistent behavior on both embedded and test environments
|
|
99
|
+
return (static_cast<uint32_t>(millis()) - lastSeen) < timeoutMs;
|
|
56
100
|
}
|
|
57
101
|
};
|
|
58
102
|
|
|
@@ -564,17 +608,55 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
564
608
|
/**
|
|
565
609
|
* Check if any bridge in the mesh has Internet connectivity
|
|
566
610
|
*
|
|
567
|
-
*
|
|
611
|
+
* When connected to mesh, requires recent bridge status (within timeout).
|
|
612
|
+
* When disconnected from mesh, returns true if any bridge previously
|
|
613
|
+
* reported Internet connectivity - allowing operations to be queued for
|
|
614
|
+
* when mesh connectivity is restored.
|
|
615
|
+
*
|
|
616
|
+
* @return true if at least one bridge reports Internet connection
|
|
568
617
|
*/
|
|
569
618
|
bool hasInternetConnection() {
|
|
619
|
+
bool hasConnections = hasActiveMeshConnections();
|
|
620
|
+
|
|
570
621
|
for (const auto& bridge : knownBridges) {
|
|
571
|
-
|
|
622
|
+
// When connected: require fresh status
|
|
623
|
+
// When disconnected: use last known state
|
|
624
|
+
bool isUsable = hasConnections
|
|
625
|
+
? (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected)
|
|
626
|
+
: bridge.internetConnected;
|
|
627
|
+
|
|
628
|
+
if (isUsable) {
|
|
572
629
|
return true;
|
|
573
630
|
}
|
|
574
631
|
}
|
|
575
632
|
return false;
|
|
576
633
|
}
|
|
577
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Get list of nodes that currently have Internet access
|
|
637
|
+
*
|
|
638
|
+
* Returns node IDs of all healthy bridges with active Internet connectivity.
|
|
639
|
+
* Only bridges that have reported status within the timeout period are included.
|
|
640
|
+
*
|
|
641
|
+
* \code
|
|
642
|
+
* auto internetNodes = mesh.getNodesWithInternet();
|
|
643
|
+
* for (auto nodeId : internetNodes) {
|
|
644
|
+
* Serial.printf("Node %u has Internet access\n", nodeId);
|
|
645
|
+
* }
|
|
646
|
+
* \endcode
|
|
647
|
+
*
|
|
648
|
+
* @return Vector of node IDs with Internet connectivity
|
|
649
|
+
*/
|
|
650
|
+
std::vector<uint32_t> getNodesWithInternet() {
|
|
651
|
+
std::vector<uint32_t> nodesWithInternet;
|
|
652
|
+
for (const auto& bridge : knownBridges) {
|
|
653
|
+
if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) {
|
|
654
|
+
nodesWithInternet.push_back(bridge.nodeId);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return nodesWithInternet;
|
|
658
|
+
}
|
|
659
|
+
|
|
578
660
|
/**
|
|
579
661
|
* Get list of all known bridges in the mesh
|
|
580
662
|
*
|
|
@@ -588,18 +670,32 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
588
670
|
* Get the primary (best) bridge node
|
|
589
671
|
*
|
|
590
672
|
* Primary bridge is selected based on:
|
|
591
|
-
* 1. Must be healthy (seen within timeout)
|
|
673
|
+
* 1. Must be healthy (seen within timeout) - unless node is disconnected
|
|
592
674
|
* 2. Must have Internet connection
|
|
593
675
|
* 3. Best WiFi RSSI to router
|
|
594
676
|
*
|
|
677
|
+
* When this node is disconnected from the mesh (no active connections),
|
|
678
|
+
* returns the last known bridge even if lastSeen is stale. This allows
|
|
679
|
+
* nodes to attempt routing through the last known bridge once mesh
|
|
680
|
+
* connectivity is restored.
|
|
681
|
+
*
|
|
595
682
|
* @return pointer to BridgeInfo of primary bridge, or nullptr if no suitable bridge
|
|
596
683
|
*/
|
|
597
684
|
BridgeInfo* getPrimaryBridge() {
|
|
598
685
|
BridgeInfo* primary = nullptr;
|
|
599
686
|
int8_t bestRSSI = -127; // Worst possible RSSI
|
|
600
687
|
|
|
688
|
+
// Check if we have active mesh connections
|
|
689
|
+
bool hasConnections = hasActiveMeshConnections();
|
|
690
|
+
|
|
601
691
|
for (auto& bridge : knownBridges) {
|
|
602
|
-
|
|
692
|
+
// When connected to mesh: require healthy (recent) bridge status
|
|
693
|
+
// When disconnected: use any bridge that reported Internet (stale info is better than none)
|
|
694
|
+
bool isUsable = hasConnections
|
|
695
|
+
? (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected)
|
|
696
|
+
: bridge.internetConnected;
|
|
697
|
+
|
|
698
|
+
if (isUsable) {
|
|
603
699
|
if (bridge.routerRSSI > bestRSSI) {
|
|
604
700
|
bestRSSI = bridge.routerRSSI;
|
|
605
701
|
primary = &bridge;
|
|
@@ -610,6 +706,58 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
610
706
|
return primary;
|
|
611
707
|
}
|
|
612
708
|
|
|
709
|
+
/**
|
|
710
|
+
* Get the last known bridge with Internet connection (regardless of lastSeen timeout)
|
|
711
|
+
*
|
|
712
|
+
* Unlike getPrimaryBridge(), this method returns the best known bridge even if
|
|
713
|
+
* the lastSeen timestamp is stale. This is useful when the local node is temporarily
|
|
714
|
+
* disconnected from the mesh and cannot receive bridge status updates.
|
|
715
|
+
*
|
|
716
|
+
* The returned bridge may not be currently reachable, but represents the last
|
|
717
|
+
* known good configuration.
|
|
718
|
+
*
|
|
719
|
+
* @return pointer to BridgeInfo of last known bridge, or nullptr if no bridge ever seen
|
|
720
|
+
*/
|
|
721
|
+
BridgeInfo* getLastKnownBridge() {
|
|
722
|
+
BridgeInfo* best = nullptr;
|
|
723
|
+
int8_t bestRSSI = -127; // Worst possible RSSI
|
|
724
|
+
|
|
725
|
+
for (auto& bridge : knownBridges) {
|
|
726
|
+
// Include any bridge that reported Internet at some point
|
|
727
|
+
if (bridge.internetConnected) {
|
|
728
|
+
if (bridge.routerRSSI > bestRSSI) {
|
|
729
|
+
bestRSSI = bridge.routerRSSI;
|
|
730
|
+
best = &bridge;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return best;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Check if this node has active mesh connections
|
|
740
|
+
*
|
|
741
|
+
* Returns true if this node has at least one active connection to other
|
|
742
|
+
* mesh nodes. When false, the node is isolated and cannot receive any
|
|
743
|
+
* mesh messages including bridge status updates.
|
|
744
|
+
*
|
|
745
|
+
* This is useful for distinguishing between:
|
|
746
|
+
* - Bridge genuinely unavailable (mesh connected, but bridge not responding)
|
|
747
|
+
* - Local node temporarily disconnected (cannot receive any mesh messages)
|
|
748
|
+
*
|
|
749
|
+
* @return true if at least one mesh connection is active
|
|
750
|
+
*/
|
|
751
|
+
bool hasActiveMeshConnections() {
|
|
752
|
+
// Check if we have any active connections in our subs list
|
|
753
|
+
for (auto& conn : this->subs) {
|
|
754
|
+
if (conn && conn->connected()) {
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
|
|
613
761
|
/**
|
|
614
762
|
* Check if this node is acting as a bridge
|
|
615
763
|
*
|
|
@@ -619,6 +767,127 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
619
767
|
return this->root;
|
|
620
768
|
}
|
|
621
769
|
|
|
770
|
+
// ==================== Gateway Status API ====================
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Check if this node is the primary gateway
|
|
774
|
+
*
|
|
775
|
+
* A node is the primary gateway if it's the bridge with the best RSSI
|
|
776
|
+
* to the router and has Internet connectivity.
|
|
777
|
+
*
|
|
778
|
+
* \code
|
|
779
|
+
* if (mesh.isPrimaryGateway()) {
|
|
780
|
+
* Serial.println("I am the primary gateway!");
|
|
781
|
+
* }
|
|
782
|
+
* \endcode
|
|
783
|
+
*
|
|
784
|
+
* @return true if this node is the primary gateway
|
|
785
|
+
*/
|
|
786
|
+
bool isPrimaryGateway() {
|
|
787
|
+
BridgeInfo* primary = getPrimaryBridge();
|
|
788
|
+
if (primary == nullptr) {
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
return primary->nodeId == this->nodeId;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Get the node ID of the current primary gateway
|
|
796
|
+
*
|
|
797
|
+
* Returns the node ID of the bridge with the best RSSI to the router
|
|
798
|
+
* that has Internet connectivity, or 0 if no gateway is available.
|
|
799
|
+
*
|
|
800
|
+
* \code
|
|
801
|
+
* uint32_t gatewayId = mesh.getPrimaryGateway();
|
|
802
|
+
* if (gatewayId != 0) {
|
|
803
|
+
* Serial.printf("Primary gateway is node %u\n", gatewayId);
|
|
804
|
+
* } else {
|
|
805
|
+
* Serial.println("No gateway available");
|
|
806
|
+
* }
|
|
807
|
+
* \endcode
|
|
808
|
+
*
|
|
809
|
+
* @return Node ID of the primary gateway, or 0 if none available
|
|
810
|
+
*/
|
|
811
|
+
uint32_t getPrimaryGateway() {
|
|
812
|
+
BridgeInfo* primary = getPrimaryBridge();
|
|
813
|
+
if (primary == nullptr) {
|
|
814
|
+
return 0;
|
|
815
|
+
}
|
|
816
|
+
return primary->nodeId;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Get list of all nodes with Internet access
|
|
821
|
+
*
|
|
822
|
+
* Returns node IDs of all healthy bridges with active Internet connectivity.
|
|
823
|
+
* Only bridges that have reported status within the timeout period are included.
|
|
824
|
+
*
|
|
825
|
+
* This method provides the same data as getNodesWithInternet() but returns
|
|
826
|
+
* a std::list for consistency with other node list methods in painlessMesh.
|
|
827
|
+
*
|
|
828
|
+
* \code
|
|
829
|
+
* auto gateways = mesh.getGateways();
|
|
830
|
+
* Serial.printf("Available gateways: %d\n", gateways.size());
|
|
831
|
+
* for (auto gatewayId : gateways) {
|
|
832
|
+
* Serial.printf(" Gateway: %u\n", gatewayId);
|
|
833
|
+
* }
|
|
834
|
+
* \endcode
|
|
835
|
+
*
|
|
836
|
+
* @return List of node IDs with Internet connectivity
|
|
837
|
+
*/
|
|
838
|
+
std::list<uint32_t> getGateways() {
|
|
839
|
+
// Convert vector to list for API consistency with getNodeList()
|
|
840
|
+
// The copy overhead is acceptable since gateway lists are typically small
|
|
841
|
+
auto nodes = getNodesWithInternet();
|
|
842
|
+
return std::list<uint32_t>(nodes.begin(), nodes.end());
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Get count of available gateways
|
|
847
|
+
*
|
|
848
|
+
* Returns the number of healthy bridges with active Internet connectivity.
|
|
849
|
+
*
|
|
850
|
+
* \code
|
|
851
|
+
* size_t count = mesh.getGatewayCount();
|
|
852
|
+
* if (count == 0) {
|
|
853
|
+
* Serial.println("Warning: No gateways available!");
|
|
854
|
+
* } else {
|
|
855
|
+
* Serial.printf("%d gateway(s) available\n", count);
|
|
856
|
+
* }
|
|
857
|
+
* \endcode
|
|
858
|
+
*
|
|
859
|
+
* @return Number of available gateways
|
|
860
|
+
*/
|
|
861
|
+
size_t getGatewayCount() {
|
|
862
|
+
return getNodesWithInternet().size();
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Register callback for primary gateway changes
|
|
867
|
+
*
|
|
868
|
+
* This callback fires when the primary gateway changes, either because
|
|
869
|
+
* a new gateway becomes available, the current gateway becomes unavailable,
|
|
870
|
+
* or a better gateway is discovered.
|
|
871
|
+
*
|
|
872
|
+
* \code
|
|
873
|
+
* mesh.onGatewayChanged([](uint32_t oldPrimary, uint32_t newPrimary) {
|
|
874
|
+
* if (newPrimary == 0) {
|
|
875
|
+
* Serial.println("Warning: Lost all gateways!");
|
|
876
|
+
* } else if (oldPrimary == 0) {
|
|
877
|
+
* Serial.printf("Gateway available: %u\n", newPrimary);
|
|
878
|
+
* } else {
|
|
879
|
+
* Serial.printf("Gateway changed: %u -> %u\n", oldPrimary, newPrimary);
|
|
880
|
+
* }
|
|
881
|
+
* });
|
|
882
|
+
* \endcode
|
|
883
|
+
*
|
|
884
|
+
* @param callback Function to call when primary gateway changes
|
|
885
|
+
*/
|
|
886
|
+
void onGatewayChanged(gatewayChangedCallback_t callback) {
|
|
887
|
+
Log(logger::GENERAL, "onGatewayChanged():\n");
|
|
888
|
+
gatewayChangedCallback = callback;
|
|
889
|
+
}
|
|
890
|
+
|
|
622
891
|
/**
|
|
623
892
|
* Set the interval for bridge status broadcasts (bridge nodes only)
|
|
624
893
|
*
|
|
@@ -646,6 +915,719 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
646
915
|
bridgeStatusBroadcastEnabled = enabled;
|
|
647
916
|
}
|
|
648
917
|
|
|
918
|
+
/**
|
|
919
|
+
* Clean up expired bridge entries that haven't reported recently
|
|
920
|
+
*
|
|
921
|
+
* Removes bridges that haven't sent a status update within the timeout period.
|
|
922
|
+
* This helps maintain an accurate list of available gateways and prevents
|
|
923
|
+
* the bridge list from growing unbounded.
|
|
924
|
+
*
|
|
925
|
+
* Called periodically when bridge cleanup is enabled, or can be called manually.
|
|
926
|
+
*
|
|
927
|
+
* \code
|
|
928
|
+
* // Manual cleanup
|
|
929
|
+
* mesh.cleanupExpiredBridges();
|
|
930
|
+
*
|
|
931
|
+
* // Or enable automatic cleanup (recommended)
|
|
932
|
+
* mesh.enableBridgeCleanup();
|
|
933
|
+
* \endcode
|
|
934
|
+
*/
|
|
935
|
+
void cleanupExpiredBridges() {
|
|
936
|
+
using namespace logger;
|
|
937
|
+
size_t sizeBefore = knownBridges.size();
|
|
938
|
+
|
|
939
|
+
// Remove bridges that haven't reported within the timeout period
|
|
940
|
+
knownBridges.erase(
|
|
941
|
+
std::remove_if(knownBridges.begin(), knownBridges.end(),
|
|
942
|
+
[this](const BridgeInfo& bridge) {
|
|
943
|
+
return !bridge.isHealthy(bridgeTimeoutMs);
|
|
944
|
+
}),
|
|
945
|
+
knownBridges.end());
|
|
946
|
+
|
|
947
|
+
size_t removed = sizeBefore - knownBridges.size();
|
|
948
|
+
if (removed > 0) {
|
|
949
|
+
Log(GENERAL, "cleanupExpiredBridges(): Removed %u expired bridges\n", removed);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Enable periodic cleanup of expired bridge entries
|
|
955
|
+
*
|
|
956
|
+
* Starts a background task that periodically removes stale bridge entries.
|
|
957
|
+
* The cleanup runs at the same interval as the bridge timeout (default 60 seconds).
|
|
958
|
+
*
|
|
959
|
+
* This is recommended for all nodes to prevent memory growth from
|
|
960
|
+
* accumulating stale bridge entries.
|
|
961
|
+
*
|
|
962
|
+
* \code
|
|
963
|
+
* mesh.enableBridgeCleanup(); // Start automatic cleanup
|
|
964
|
+
* \endcode
|
|
965
|
+
*/
|
|
966
|
+
void enableBridgeCleanup() {
|
|
967
|
+
using namespace logger;
|
|
968
|
+
if (bridgeCleanupTask != nullptr) {
|
|
969
|
+
Log(GENERAL, "enableBridgeCleanup(): Already enabled\n");
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
Log(GENERAL, "enableBridgeCleanup(): Starting cleanup task (interval: %u ms)\n",
|
|
974
|
+
bridgeTimeoutMs);
|
|
975
|
+
|
|
976
|
+
bridgeCleanupTask = this->addTask(
|
|
977
|
+
bridgeTimeoutMs,
|
|
978
|
+
TASK_FOREVER,
|
|
979
|
+
[this]() {
|
|
980
|
+
cleanupExpiredBridges();
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Disable periodic cleanup of expired bridge entries
|
|
986
|
+
*/
|
|
987
|
+
void disableBridgeCleanup() {
|
|
988
|
+
using namespace logger;
|
|
989
|
+
if (bridgeCleanupTask != nullptr) {
|
|
990
|
+
bridgeCleanupTask->disable();
|
|
991
|
+
bridgeCleanupTask = nullptr;
|
|
992
|
+
Log(GENERAL, "disableBridgeCleanup(): Cleanup task disabled\n");
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Check if bridge cleanup is enabled
|
|
998
|
+
*
|
|
999
|
+
* @return true if periodic cleanup is running
|
|
1000
|
+
*/
|
|
1001
|
+
bool isBridgeCleanupEnabled() const {
|
|
1002
|
+
return bridgeCleanupTask != nullptr && bridgeCleanupTask->isEnabled();
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// ==================== Local Internet Health Check API ====================
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Check if THIS node has direct local Internet access
|
|
1009
|
+
*
|
|
1010
|
+
* This method checks whether this specific node can directly reach the Internet,
|
|
1011
|
+
* as opposed to hasInternetConnection() which checks if any bridge in the mesh
|
|
1012
|
+
* has Internet access.
|
|
1013
|
+
*
|
|
1014
|
+
* For gateway/bridge nodes, this returns the result of the local health check.
|
|
1015
|
+
* For regular nodes, this typically returns false unless they have their own
|
|
1016
|
+
* Internet connection.
|
|
1017
|
+
*
|
|
1018
|
+
* \code
|
|
1019
|
+
* if (mesh.hasLocalInternet()) {
|
|
1020
|
+
* Serial.println("This node has direct Internet access");
|
|
1021
|
+
* }
|
|
1022
|
+
* \endcode
|
|
1023
|
+
*
|
|
1024
|
+
* @return true if this node has verified local Internet connectivity
|
|
1025
|
+
*/
|
|
1026
|
+
bool hasLocalInternet() {
|
|
1027
|
+
return internetHealthChecker.hasLocalInternet();
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Register callback for local Internet connectivity changes
|
|
1032
|
+
*
|
|
1033
|
+
* This callback fires when THIS node's direct Internet connectivity changes,
|
|
1034
|
+
* as detected by the periodic health check.
|
|
1035
|
+
*
|
|
1036
|
+
* \code
|
|
1037
|
+
* mesh.onLocalInternetChanged([](bool available) {
|
|
1038
|
+
* if (available) {
|
|
1039
|
+
* Serial.println("Local Internet connected");
|
|
1040
|
+
* } else {
|
|
1041
|
+
* Serial.println("Local Internet disconnected");
|
|
1042
|
+
* }
|
|
1043
|
+
* });
|
|
1044
|
+
* \endcode
|
|
1045
|
+
*
|
|
1046
|
+
* @param callback Function to call when local Internet status changes
|
|
1047
|
+
*/
|
|
1048
|
+
void onLocalInternetChanged(localInternetChangedCallback_t callback) {
|
|
1049
|
+
Log(logger::GENERAL, "onLocalInternetChanged():\n");
|
|
1050
|
+
localInternetChangedCallback = callback;
|
|
1051
|
+
internetHealthChecker.onConnectivityChanged(callback);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Get detailed status of local Internet connectivity
|
|
1056
|
+
*
|
|
1057
|
+
* Returns comprehensive information about Internet connectivity checks
|
|
1058
|
+
* including success/failure counts, timing, and error messages.
|
|
1059
|
+
*
|
|
1060
|
+
* \code
|
|
1061
|
+
* auto status = mesh.getInternetStatus();
|
|
1062
|
+
* Serial.printf("Internet %s, uptime: %d%%\n",
|
|
1063
|
+
* status.available ? "UP" : "DOWN",
|
|
1064
|
+
* status.getUptimePercent());
|
|
1065
|
+
* \endcode
|
|
1066
|
+
*
|
|
1067
|
+
* @return InternetStatus structure with detailed connectivity info
|
|
1068
|
+
*/
|
|
1069
|
+
gateway::InternetStatus getInternetStatus() {
|
|
1070
|
+
return internetHealthChecker.getStatus();
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Configure Internet health checker with gateway settings
|
|
1075
|
+
*
|
|
1076
|
+
* Sets up the health checker with parameters from SharedGatewayConfig.
|
|
1077
|
+
* This should be called before enabling gateway mode.
|
|
1078
|
+
*
|
|
1079
|
+
* @param config Gateway configuration with check parameters
|
|
1080
|
+
*/
|
|
1081
|
+
void configureInternetHealthCheck(const gateway::SharedGatewayConfig& config) {
|
|
1082
|
+
internetHealthChecker.setConfig(config);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Set custom Internet check target
|
|
1087
|
+
*
|
|
1088
|
+
* Override the default check host/port (8.8.8.8:53).
|
|
1089
|
+
*
|
|
1090
|
+
* @param host Host to check (IP or hostname)
|
|
1091
|
+
* @param port Port to connect to (default 53 for DNS)
|
|
1092
|
+
*/
|
|
1093
|
+
void setInternetCheckTarget(const TSTRING& host, uint16_t port = 53) {
|
|
1094
|
+
internetHealthChecker.setCheckTarget(host, port);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Set Internet check interval
|
|
1099
|
+
*
|
|
1100
|
+
* @param intervalMs Interval between checks in milliseconds
|
|
1101
|
+
*/
|
|
1102
|
+
void setInternetCheckInterval(uint32_t intervalMs) {
|
|
1103
|
+
internetHealthChecker.setCheckInterval(intervalMs);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Set Internet check timeout
|
|
1108
|
+
*
|
|
1109
|
+
* @param timeoutMs Timeout for each check in milliseconds
|
|
1110
|
+
*/
|
|
1111
|
+
void setInternetCheckTimeout(uint32_t timeoutMs) {
|
|
1112
|
+
internetHealthChecker.setCheckTimeout(timeoutMs);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Perform an immediate Internet connectivity check
|
|
1117
|
+
*
|
|
1118
|
+
* Bypasses the periodic check schedule and performs an immediate check.
|
|
1119
|
+
* Fires the onLocalInternetChanged callback if status changes.
|
|
1120
|
+
*
|
|
1121
|
+
* @return true if Internet is reachable
|
|
1122
|
+
*/
|
|
1123
|
+
bool checkInternetNow() {
|
|
1124
|
+
return internetHealthChecker.checkNow();
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Enable periodic Internet health checking
|
|
1129
|
+
*
|
|
1130
|
+
* Starts a TaskScheduler task that periodically checks Internet connectivity.
|
|
1131
|
+
* The check interval is configured via setInternetCheckInterval() or
|
|
1132
|
+
* configureInternetHealthCheck().
|
|
1133
|
+
*
|
|
1134
|
+
* \code
|
|
1135
|
+
* mesh.enableInternetHealthCheck(); // Use default 30 second interval
|
|
1136
|
+
* // or
|
|
1137
|
+
* mesh.setInternetCheckInterval(15000); // 15 seconds
|
|
1138
|
+
* mesh.enableInternetHealthCheck();
|
|
1139
|
+
* \endcode
|
|
1140
|
+
*/
|
|
1141
|
+
void enableInternetHealthCheck() {
|
|
1142
|
+
using namespace logger;
|
|
1143
|
+
if (internetHealthCheckTask != nullptr) {
|
|
1144
|
+
Log(GENERAL, "enableInternetHealthCheck(): Already enabled\n");
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
Log(GENERAL, "enableInternetHealthCheck(): Starting health check task (interval: %u ms)\n",
|
|
1149
|
+
internetHealthChecker.getCheckInterval());
|
|
1150
|
+
|
|
1151
|
+
internetHealthCheckTask = this->addTask(
|
|
1152
|
+
internetHealthChecker.getCheckInterval(),
|
|
1153
|
+
TASK_FOREVER,
|
|
1154
|
+
[this]() {
|
|
1155
|
+
this->internetHealthChecker.checkNow();
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Disable periodic Internet health checking
|
|
1161
|
+
*/
|
|
1162
|
+
void disableInternetHealthCheck() {
|
|
1163
|
+
using namespace logger;
|
|
1164
|
+
if (internetHealthCheckTask != nullptr) {
|
|
1165
|
+
internetHealthCheckTask->disable();
|
|
1166
|
+
internetHealthCheckTask = nullptr;
|
|
1167
|
+
Log(GENERAL, "disableInternetHealthCheck(): Health check task disabled\n");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Check if Internet health checking is enabled
|
|
1173
|
+
*
|
|
1174
|
+
* @return true if periodic health checks are running
|
|
1175
|
+
*/
|
|
1176
|
+
bool isInternetHealthCheckEnabled() const {
|
|
1177
|
+
return internetHealthCheckTask != nullptr && internetHealthCheckTask->isEnabled();
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* Reset Internet health check statistics
|
|
1182
|
+
*/
|
|
1183
|
+
void resetInternetHealthStats() {
|
|
1184
|
+
internetHealthChecker.resetStats();
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
#ifdef PAINLESSMESH_BOOST
|
|
1188
|
+
/**
|
|
1189
|
+
* Set mock Internet connectivity (test environment only)
|
|
1190
|
+
*
|
|
1191
|
+
* @param connected Whether to simulate connected state
|
|
1192
|
+
*/
|
|
1193
|
+
void setMockInternetConnected(bool connected) {
|
|
1194
|
+
internetHealthChecker.setMockConnected(connected);
|
|
1195
|
+
}
|
|
1196
|
+
#endif
|
|
1197
|
+
|
|
1198
|
+
// ==================== Send to Internet API ====================
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Send data to an Internet destination through the mesh gateway
|
|
1202
|
+
*
|
|
1203
|
+
* This method provides a high-level API for sending data to the Internet
|
|
1204
|
+
* through the mesh network. It automatically handles:
|
|
1205
|
+
* - Local Internet: If this node has direct Internet access, sends directly
|
|
1206
|
+
* - Gateway routing: Otherwise routes through the best available gateway
|
|
1207
|
+
* - Acknowledgment tracking: Tracks message delivery with callbacks
|
|
1208
|
+
* - Retry logic: Implements exponential backoff for failed deliveries
|
|
1209
|
+
* - Priority handling: Respects message priority levels
|
|
1210
|
+
*
|
|
1211
|
+
* PRIORITY LEVELS:
|
|
1212
|
+
* - 0 (CRITICAL): Immediate processing, no delays
|
|
1213
|
+
* - 1 (HIGH): High priority, processed before normal
|
|
1214
|
+
* - 2 (NORMAL): Standard processing (default)
|
|
1215
|
+
* - 3 (LOW): Background processing when idle
|
|
1216
|
+
*
|
|
1217
|
+
* CALLBACK BEHAVIOR:
|
|
1218
|
+
* The callback is invoked when:
|
|
1219
|
+
* - Success: Gateway confirms delivery to Internet destination
|
|
1220
|
+
* - Failure: Timeout, no gateway available, or delivery error
|
|
1221
|
+
*
|
|
1222
|
+
* \code
|
|
1223
|
+
* // Send sensor data to cloud API
|
|
1224
|
+
* uint32_t msgId = mesh.sendToInternet(
|
|
1225
|
+
* "https://api.example.com/sensor",
|
|
1226
|
+
* "{\"temperature\": 23.5}",
|
|
1227
|
+
* [](bool success, uint16_t httpStatus, String error) {
|
|
1228
|
+
* if (success) {
|
|
1229
|
+
* Serial.printf("Delivered! HTTP %u\n", httpStatus);
|
|
1230
|
+
* } else {
|
|
1231
|
+
* Serial.printf("Failed: %s\n", error.c_str());
|
|
1232
|
+
* }
|
|
1233
|
+
* },
|
|
1234
|
+
* PRIORITY_NORMAL // Optional priority
|
|
1235
|
+
* );
|
|
1236
|
+
* Serial.printf("Message ID: %u\n", msgId);
|
|
1237
|
+
* \endcode
|
|
1238
|
+
*
|
|
1239
|
+
* @param destination The Internet destination URL (e.g., "https://api.example.com/data")
|
|
1240
|
+
* @param payload The data payload to send (typically JSON)
|
|
1241
|
+
* @param callback Function called with result (success, httpStatus, error)
|
|
1242
|
+
* @param priority Message priority (0=CRITICAL to 3=LOW, default: 2=NORMAL)
|
|
1243
|
+
* @return Unique message ID for tracking, or 0 if immediate failure
|
|
1244
|
+
*/
|
|
1245
|
+
uint32_t sendToInternet(
|
|
1246
|
+
TSTRING destination,
|
|
1247
|
+
TSTRING payload,
|
|
1248
|
+
internetResultCallback_t callback,
|
|
1249
|
+
uint8_t priority = static_cast<uint8_t>(gateway::GatewayPriority::PRIORITY_NORMAL)) {
|
|
1250
|
+
using namespace logger;
|
|
1251
|
+
|
|
1252
|
+
// Generate unique message ID
|
|
1253
|
+
uint32_t messageId = gateway::GatewayDataPackage::generateMessageId(this->nodeId);
|
|
1254
|
+
|
|
1255
|
+
Log(COMMUNICATION, "sendToInternet(): msgId=%u dest=%s priority=%u\n",
|
|
1256
|
+
messageId, destination.c_str(), priority);
|
|
1257
|
+
|
|
1258
|
+
// Check if we have local Internet access
|
|
1259
|
+
// Note: Even with local Internet, we still use the gateway protocol for consistency.
|
|
1260
|
+
// A future optimization could bypass the mesh for nodes with direct Internet access.
|
|
1261
|
+
if (hasLocalInternet()) {
|
|
1262
|
+
Log(COMMUNICATION, "sendToInternet(): Local Internet available, using gateway protocol for consistency\n");
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Find the best gateway to route through
|
|
1266
|
+
BridgeInfo* gateway = getPrimaryBridge();
|
|
1267
|
+
if (gateway == nullptr) {
|
|
1268
|
+
Log(ERROR, "sendToInternet(): No gateway available\n");
|
|
1269
|
+
if (callback) {
|
|
1270
|
+
// Schedule callback to avoid blocking
|
|
1271
|
+
this->addTask([callback]() {
|
|
1272
|
+
callback(false, 0, "No gateway available");
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
return 0;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Create and store the pending request
|
|
1279
|
+
PendingInternetRequest request;
|
|
1280
|
+
request.messageId = messageId;
|
|
1281
|
+
request.timestamp = millis();
|
|
1282
|
+
request.retryCount = 0;
|
|
1283
|
+
request.maxRetries = internetRetryCount;
|
|
1284
|
+
request.priority = priority;
|
|
1285
|
+
request.timeoutMs = internetRequestTimeout;
|
|
1286
|
+
request.retryDelayMs = internetRetryDelay;
|
|
1287
|
+
request.gatewayNodeId = gateway->nodeId;
|
|
1288
|
+
request.destination = destination;
|
|
1289
|
+
request.payload = payload;
|
|
1290
|
+
request.callback = callback;
|
|
1291
|
+
|
|
1292
|
+
// Store pending request
|
|
1293
|
+
pendingInternetRequests[messageId] = request;
|
|
1294
|
+
|
|
1295
|
+
// Create gateway data package
|
|
1296
|
+
gateway::GatewayDataPackage pkg;
|
|
1297
|
+
pkg.from = this->nodeId;
|
|
1298
|
+
pkg.dest = gateway->nodeId;
|
|
1299
|
+
pkg.messageId = messageId;
|
|
1300
|
+
pkg.originNode = this->nodeId;
|
|
1301
|
+
pkg.timestamp = this->getNodeTime();
|
|
1302
|
+
pkg.priority = priority;
|
|
1303
|
+
pkg.destination = destination;
|
|
1304
|
+
pkg.payload = payload;
|
|
1305
|
+
pkg.contentType = "application/json";
|
|
1306
|
+
pkg.retryCount = 0;
|
|
1307
|
+
pkg.requiresAck = true;
|
|
1308
|
+
|
|
1309
|
+
// Send the package with priority
|
|
1310
|
+
bool sent = false;
|
|
1311
|
+
auto conn = painlessmesh::router::findRoute<T>((*this), gateway->nodeId);
|
|
1312
|
+
if (conn) {
|
|
1313
|
+
sent = painlessmesh::router::sendWithPriority(pkg, conn, priority);
|
|
1314
|
+
if (!sent) {
|
|
1315
|
+
Log(ERROR, "sendToInternet(): sendWithPriority failed to gateway %u (send buffer full?)\n", gateway->nodeId);
|
|
1316
|
+
}
|
|
1317
|
+
} else {
|
|
1318
|
+
Log(ERROR, "sendToInternet(): No route to gateway %u\n", gateway->nodeId);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (!sent) {
|
|
1322
|
+
Log(ERROR, "sendToInternet(): Failed to send to gateway %u, scheduling retry\n", gateway->nodeId);
|
|
1323
|
+
// Schedule retry or failure callback
|
|
1324
|
+
scheduleInternetRetry(messageId);
|
|
1325
|
+
} else {
|
|
1326
|
+
Log(COMMUNICATION, "sendToInternet(): Sent to gateway %u\n", gateway->nodeId);
|
|
1327
|
+
// Schedule timeout check
|
|
1328
|
+
scheduleInternetTimeout(messageId);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return messageId;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Configure Internet request timeout
|
|
1336
|
+
*
|
|
1337
|
+
* @param timeoutMs Timeout in milliseconds (default: 30000)
|
|
1338
|
+
*/
|
|
1339
|
+
void setInternetRequestTimeout(uint32_t timeoutMs) {
|
|
1340
|
+
internetRequestTimeout = timeoutMs;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Configure Internet request retry count
|
|
1345
|
+
*
|
|
1346
|
+
* @param retryCount Number of retry attempts (default: 3)
|
|
1347
|
+
*/
|
|
1348
|
+
void setInternetRetryCount(uint8_t retryCount) {
|
|
1349
|
+
internetRetryCount = retryCount;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Configure base retry delay for exponential backoff
|
|
1354
|
+
*
|
|
1355
|
+
* @param delayMs Base delay in milliseconds (default: 1000)
|
|
1356
|
+
*/
|
|
1357
|
+
void setInternetRetryDelay(uint32_t delayMs) {
|
|
1358
|
+
internetRetryDelay = delayMs;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Get number of pending Internet requests
|
|
1363
|
+
*
|
|
1364
|
+
* @return Number of requests waiting for acknowledgment
|
|
1365
|
+
*/
|
|
1366
|
+
size_t getPendingInternetRequestCount() const {
|
|
1367
|
+
return pendingInternetRequests.size();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Cancel a pending Internet request
|
|
1372
|
+
*
|
|
1373
|
+
* @param messageId Message ID to cancel
|
|
1374
|
+
* @return true if request was found and cancelled
|
|
1375
|
+
*/
|
|
1376
|
+
bool cancelInternetRequest(uint32_t messageId) {
|
|
1377
|
+
auto it = pendingInternetRequests.find(messageId);
|
|
1378
|
+
if (it != pendingInternetRequests.end()) {
|
|
1379
|
+
if (it->second.callback) {
|
|
1380
|
+
it->second.callback(false, 0, "Request cancelled");
|
|
1381
|
+
}
|
|
1382
|
+
pendingInternetRequests.erase(it);
|
|
1383
|
+
Log(logger::GENERAL, "cancelInternetRequest(): Cancelled msgId=%u\n", messageId);
|
|
1384
|
+
return true;
|
|
1385
|
+
}
|
|
1386
|
+
return false;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/**
|
|
1390
|
+
* Enable the sendToInternet() API
|
|
1391
|
+
*
|
|
1392
|
+
* This registers the handler for GatewayAckPackage (Type 621) and
|
|
1393
|
+
* starts the periodic cleanup of timed-out requests.
|
|
1394
|
+
*
|
|
1395
|
+
* Call this after mesh.init() to enable the sendToInternet() functionality.
|
|
1396
|
+
*
|
|
1397
|
+
* \code
|
|
1398
|
+
* mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
1399
|
+
* mesh.enableSendToInternet(); // Enable the API
|
|
1400
|
+
* \endcode
|
|
1401
|
+
*/
|
|
1402
|
+
void enableSendToInternet() {
|
|
1403
|
+
using namespace logger;
|
|
1404
|
+
Log(GENERAL, "enableSendToInternet(): Enabling Internet routing API\n");
|
|
1405
|
+
|
|
1406
|
+
// Register handler for gateway acknowledgment packages
|
|
1407
|
+
this->callbackList.onPackage(
|
|
1408
|
+
protocol::GATEWAY_ACK,
|
|
1409
|
+
[this](protocol::Variant& variant, std::shared_ptr<T>, uint32_t) {
|
|
1410
|
+
auto ack = variant.to<gateway::GatewayAckPackage>();
|
|
1411
|
+
this->handleGatewayAck(ack);
|
|
1412
|
+
return false; // Don't consume, allow other handlers
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
// Start cleanup task for timed-out requests
|
|
1416
|
+
if (internetCleanupTask == nullptr) {
|
|
1417
|
+
internetCleanupTask = this->addTask(
|
|
1418
|
+
5000, // Check every 5 seconds
|
|
1419
|
+
TASK_FOREVER,
|
|
1420
|
+
[this]() {
|
|
1421
|
+
this->cleanupTimedOutRequests();
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
sendToInternetEnabled = true;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Disable the sendToInternet() API
|
|
1430
|
+
*/
|
|
1431
|
+
void disableSendToInternet() {
|
|
1432
|
+
using namespace logger;
|
|
1433
|
+
Log(GENERAL, "disableSendToInternet(): Disabling Internet routing API\n");
|
|
1434
|
+
|
|
1435
|
+
if (internetCleanupTask != nullptr) {
|
|
1436
|
+
internetCleanupTask->disable();
|
|
1437
|
+
internetCleanupTask = nullptr;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Cancel all pending requests
|
|
1441
|
+
for (auto& pair : pendingInternetRequests) {
|
|
1442
|
+
if (pair.second.callback) {
|
|
1443
|
+
pair.second.callback(false, 0, "API disabled");
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
pendingInternetRequests.clear();
|
|
1447
|
+
|
|
1448
|
+
sendToInternetEnabled = false;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Check if sendToInternet() API is enabled
|
|
1453
|
+
*
|
|
1454
|
+
* @return true if the API is enabled
|
|
1455
|
+
*/
|
|
1456
|
+
bool isSendToInternetEnabled() const {
|
|
1457
|
+
return sendToInternetEnabled;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
private:
|
|
1461
|
+
/**
|
|
1462
|
+
* Handle incoming gateway acknowledgment package
|
|
1463
|
+
*/
|
|
1464
|
+
void handleGatewayAck(const gateway::GatewayAckPackage& ack) {
|
|
1465
|
+
using namespace logger;
|
|
1466
|
+
Log(COMMUNICATION, "handleGatewayAck(): msgId=%u success=%d http=%u\n",
|
|
1467
|
+
ack.messageId, ack.success, ack.httpStatus);
|
|
1468
|
+
|
|
1469
|
+
auto it = pendingInternetRequests.find(ack.messageId);
|
|
1470
|
+
if (it == pendingInternetRequests.end()) {
|
|
1471
|
+
Log(GENERAL, "handleGatewayAck(): Unknown message ID %u\n", ack.messageId);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
PendingInternetRequest& request = it->second;
|
|
1476
|
+
|
|
1477
|
+
// Call user callback
|
|
1478
|
+
if (request.callback) {
|
|
1479
|
+
request.callback(ack.success, ack.httpStatus, ack.error);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// Remove from pending
|
|
1483
|
+
pendingInternetRequests.erase(it);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Schedule retry for a failed Internet request
|
|
1488
|
+
*/
|
|
1489
|
+
void scheduleInternetRetry(uint32_t messageId) {
|
|
1490
|
+
auto it = pendingInternetRequests.find(messageId);
|
|
1491
|
+
if (it == pendingInternetRequests.end()) {
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
PendingInternetRequest& request = it->second;
|
|
1496
|
+
|
|
1497
|
+
if (request.retryCount >= request.maxRetries) {
|
|
1498
|
+
// Max retries reached - fail the request
|
|
1499
|
+
Log(logger::ERROR, "scheduleInternetRetry(): Max retries reached for msgId=%u\n",
|
|
1500
|
+
messageId);
|
|
1501
|
+
if (request.callback) {
|
|
1502
|
+
request.callback(false, 0, "Max retries exceeded");
|
|
1503
|
+
}
|
|
1504
|
+
pendingInternetRequests.erase(it);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// Calculate exponential backoff delay
|
|
1509
|
+
uint32_t delay = request.retryDelayMs * (1 << request.retryCount);
|
|
1510
|
+
request.retryCount++;
|
|
1511
|
+
|
|
1512
|
+
Log(logger::COMMUNICATION, "scheduleInternetRetry(): Retry %u for msgId=%u in %u ms\n",
|
|
1513
|
+
request.retryCount, messageId, delay);
|
|
1514
|
+
|
|
1515
|
+
// Schedule retry
|
|
1516
|
+
this->addTask(delay, TASK_ONCE, [this, messageId]() {
|
|
1517
|
+
this->retryInternetRequest(messageId);
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/**
|
|
1522
|
+
* Retry sending an Internet request
|
|
1523
|
+
*/
|
|
1524
|
+
void retryInternetRequest(uint32_t messageId) {
|
|
1525
|
+
auto it = pendingInternetRequests.find(messageId);
|
|
1526
|
+
if (it == pendingInternetRequests.end()) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
PendingInternetRequest& request = it->second;
|
|
1531
|
+
|
|
1532
|
+
// Find gateway (may have changed)
|
|
1533
|
+
BridgeInfo* gateway = getPrimaryBridge();
|
|
1534
|
+
if (gateway == nullptr) {
|
|
1535
|
+
Log(logger::ERROR, "retryInternetRequest(): No gateway for retry msgId=%u\n",
|
|
1536
|
+
messageId);
|
|
1537
|
+
scheduleInternetRetry(messageId);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// Update gateway node ID (may have changed)
|
|
1542
|
+
request.gatewayNodeId = gateway->nodeId;
|
|
1543
|
+
|
|
1544
|
+
// Create gateway data package
|
|
1545
|
+
gateway::GatewayDataPackage pkg;
|
|
1546
|
+
pkg.from = this->nodeId;
|
|
1547
|
+
pkg.dest = gateway->nodeId;
|
|
1548
|
+
pkg.messageId = messageId;
|
|
1549
|
+
pkg.originNode = this->nodeId;
|
|
1550
|
+
pkg.timestamp = this->getNodeTime();
|
|
1551
|
+
pkg.priority = request.priority;
|
|
1552
|
+
pkg.destination = request.destination;
|
|
1553
|
+
pkg.payload = request.payload;
|
|
1554
|
+
pkg.contentType = "application/json";
|
|
1555
|
+
pkg.retryCount = request.retryCount;
|
|
1556
|
+
pkg.requiresAck = true;
|
|
1557
|
+
|
|
1558
|
+
// Send the package
|
|
1559
|
+
auto conn = painlessmesh::router::findRoute<T>((*this), gateway->nodeId);
|
|
1560
|
+
bool sent = false;
|
|
1561
|
+
if (conn) {
|
|
1562
|
+
sent = painlessmesh::router::sendWithPriority(pkg, conn, request.priority);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (!sent) {
|
|
1566
|
+
Log(logger::ERROR, "retryInternetRequest(): Retry send failed msgId=%u\n", messageId);
|
|
1567
|
+
scheduleInternetRetry(messageId);
|
|
1568
|
+
} else {
|
|
1569
|
+
Log(logger::COMMUNICATION, "retryInternetRequest(): Retry sent msgId=%u to gateway %u\n",
|
|
1570
|
+
messageId, gateway->nodeId);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* Schedule timeout check for an Internet request
|
|
1576
|
+
*/
|
|
1577
|
+
void scheduleInternetTimeout(uint32_t messageId) {
|
|
1578
|
+
auto it = pendingInternetRequests.find(messageId);
|
|
1579
|
+
if (it == pendingInternetRequests.end()) {
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
uint32_t timeout = it->second.timeoutMs;
|
|
1584
|
+
|
|
1585
|
+
this->addTask(timeout, TASK_ONCE, [this, messageId]() {
|
|
1586
|
+
this->checkInternetRequestTimeout(messageId);
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Check if a specific request has timed out
|
|
1592
|
+
*/
|
|
1593
|
+
void checkInternetRequestTimeout(uint32_t messageId) {
|
|
1594
|
+
auto it = pendingInternetRequests.find(messageId);
|
|
1595
|
+
if (it == pendingInternetRequests.end()) {
|
|
1596
|
+
return; // Already handled
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
PendingInternetRequest& request = it->second;
|
|
1600
|
+
|
|
1601
|
+
if (request.isTimedOut()) {
|
|
1602
|
+
Log(logger::ERROR, "checkInternetRequestTimeout(): Request timed out msgId=%u\n",
|
|
1603
|
+
messageId);
|
|
1604
|
+
if (request.callback) {
|
|
1605
|
+
request.callback(false, 0, "Request timed out");
|
|
1606
|
+
}
|
|
1607
|
+
pendingInternetRequests.erase(it);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/**
|
|
1612
|
+
* Cleanup all timed-out Internet requests
|
|
1613
|
+
*/
|
|
1614
|
+
void cleanupTimedOutRequests() {
|
|
1615
|
+
auto it = pendingInternetRequests.begin();
|
|
1616
|
+
while (it != pendingInternetRequests.end()) {
|
|
1617
|
+
if (it->second.isTimedOut()) {
|
|
1618
|
+
Log(logger::GENERAL, "cleanupTimedOutRequests(): Cleaning up msgId=%u\n",
|
|
1619
|
+
it->first);
|
|
1620
|
+
if (it->second.callback) {
|
|
1621
|
+
it->second.callback(false, 0, "Request timed out");
|
|
1622
|
+
}
|
|
1623
|
+
it = pendingInternetRequests.erase(it);
|
|
1624
|
+
} else {
|
|
1625
|
+
++it;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
public:
|
|
649
1631
|
/**
|
|
650
1632
|
* Update bridge information from received status package
|
|
651
1633
|
* Internal method called when bridge status is received
|
|
@@ -679,7 +1661,30 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
679
1661
|
bool wasConnected = false;
|
|
680
1662
|
bool isNewBridge = false;
|
|
681
1663
|
if (bridge == nullptr) {
|
|
682
|
-
// New bridge -
|
|
1664
|
+
// New bridge - enforce MAX_KNOWN_BRIDGES limit before adding
|
|
1665
|
+
if (knownBridges.size() >= MAX_KNOWN_BRIDGES) {
|
|
1666
|
+
// First try to remove expired bridges
|
|
1667
|
+
cleanupExpiredBridges();
|
|
1668
|
+
|
|
1669
|
+
// If still at capacity, remove the bridge with worst RSSI (lowest signal)
|
|
1670
|
+
if (knownBridges.size() >= MAX_KNOWN_BRIDGES && !knownBridges.empty()) {
|
|
1671
|
+
auto worstBridge = knownBridges.begin();
|
|
1672
|
+
int8_t worstRSSI = worstBridge->routerRSSI;
|
|
1673
|
+
|
|
1674
|
+
for (auto it = knownBridges.begin(); it != knownBridges.end(); ++it) {
|
|
1675
|
+
if (it->routerRSSI < worstRSSI) {
|
|
1676
|
+
worstRSSI = it->routerRSSI;
|
|
1677
|
+
worstBridge = it;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
Log(logger::GENERAL, "updateBridgeStatus(): Removing bridge %u (RSSI: %d) to make room\n",
|
|
1682
|
+
worstBridge->nodeId, worstBridge->routerRSSI);
|
|
1683
|
+
knownBridges.erase(worstBridge);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// Add new bridge
|
|
683
1688
|
BridgeInfo newBridge;
|
|
684
1689
|
newBridge.nodeId = bridgeNodeId;
|
|
685
1690
|
knownBridges.push_back(newBridge);
|
|
@@ -702,6 +1707,11 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
702
1707
|
auto newPrimary = this->getPrimaryBridge();
|
|
703
1708
|
uint32_t newPrimaryBridgeId = (newPrimary != nullptr) ? newPrimary->nodeId : 0;
|
|
704
1709
|
|
|
1710
|
+
// Trigger gateway changed callback if primary gateway changed
|
|
1711
|
+
if (oldPrimaryBridgeId != newPrimaryBridgeId && gatewayChangedCallback) {
|
|
1712
|
+
gatewayChangedCallback(oldPrimaryBridgeId, newPrimaryBridgeId);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
705
1715
|
if (diagnosticsEnabled && oldPrimaryBridgeId != newPrimaryBridgeId) {
|
|
706
1716
|
// Record bridge change
|
|
707
1717
|
lastBridgeChange.timestamp = millis();
|
|
@@ -2095,6 +3105,7 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2095
3105
|
nodeDelayCallback_t nodeDelayReceivedCallback;
|
|
2096
3106
|
bridgeStatusChangedCallback_t bridgeStatusChangedCallback;
|
|
2097
3107
|
rtcSyncCompleteCallback_t rtcSyncCompleteCallback;
|
|
3108
|
+
gatewayChangedCallback_t gatewayChangedCallback;
|
|
2098
3109
|
|
|
2099
3110
|
// Message queue for offline mode
|
|
2100
3111
|
MessageQueue* messageQueue = nullptr;
|
|
@@ -2143,6 +3154,10 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2143
3154
|
uint32_t bridgeStatusIntervalMs = 30000; // Default 30 seconds
|
|
2144
3155
|
uint32_t bridgeTimeoutMs = 60000; // Default 60 seconds
|
|
2145
3156
|
bool bridgeStatusBroadcastEnabled = true;
|
|
3157
|
+
|
|
3158
|
+
// Bridge cleanup configuration
|
|
3159
|
+
static const size_t MAX_KNOWN_BRIDGES = 20; // Memory efficient limit for ESP8266
|
|
3160
|
+
std::shared_ptr<Task> bridgeCleanupTask = nullptr;
|
|
2146
3161
|
|
|
2147
3162
|
// Health metrics tracking
|
|
2148
3163
|
uint32_t metricsDisconnectCount = 0;
|
|
@@ -2157,6 +3172,19 @@ class Mesh : public ntp::MeshTime, public plugin::PackageHandler<T> {
|
|
|
2157
3172
|
BridgeChangeEvent lastBridgeChange;
|
|
2158
3173
|
uint32_t lastBridgeChangeTime = 0;
|
|
2159
3174
|
|
|
3175
|
+
// Local Internet health check
|
|
3176
|
+
gateway::InternetHealthChecker internetHealthChecker;
|
|
3177
|
+
std::shared_ptr<Task> internetHealthCheckTask = nullptr;
|
|
3178
|
+
localInternetChangedCallback_t localInternetChangedCallback;
|
|
3179
|
+
|
|
3180
|
+
// sendToInternet API
|
|
3181
|
+
std::map<uint32_t, PendingInternetRequest> pendingInternetRequests;
|
|
3182
|
+
std::shared_ptr<Task> internetCleanupTask = nullptr;
|
|
3183
|
+
uint32_t internetRequestTimeout = 30000; // Default 30 seconds
|
|
3184
|
+
uint8_t internetRetryCount = 3; // Default 3 retries
|
|
3185
|
+
uint32_t internetRetryDelay = 1000; // Default 1 second base delay
|
|
3186
|
+
bool sendToInternetEnabled = false;
|
|
3187
|
+
|
|
2160
3188
|
friend T;
|
|
2161
3189
|
friend void onDataCb(void *, AsyncClient *, void *, size_t);
|
|
2162
3190
|
friend void tcpSentCb(void *, AsyncClient *, size_t, uint32_t);
|