@alteriom/painlessmesh 1.8.15 → 1.9.1
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 +101 -1
- package/CONTRIBUTING.md +79 -0
- package/README.md +69 -144
- package/docs/README.md +1 -0
- package/docs/api/shared-gateway.md +1207 -0
- package/examples/bridge_failover/README.md +113 -0
- package/examples/bridge_failover/bridge_failover.ino +38 -4
- 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 -6
- package/src/arduino/wifi.hpp +504 -4
- 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/RELEASE_NOTES_1.8.15.md +0 -160
- package/RELEASE_READINESS_PLAN.md +0 -323
- package/TESTING_WITH_SIMULATOR.md +0 -259
- 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/BRIDGE_INITIALIZATION_FALLBACK.md +0 -357
- 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/SIMULATOR_TESTING.md +0 -408
- 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/common-architecture-mistakes.md +0 -438
- package/docs/troubleshooting/internet-access-faq.md +0 -299
- 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 -108
- package/examples/multi_bridge/regular_node.ino +0 -141
- package/examples/multi_bridge/secondary_bridge.ino +0 -123
- 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
package/src/arduino/wifi.hpp
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#include "painlessMeshSTA.h"
|
|
9
9
|
|
|
10
10
|
#include "painlessmesh/callback.hpp"
|
|
11
|
+
#include "painlessmesh/gateway.hpp"
|
|
11
12
|
#include "painlessmesh/mesh.hpp"
|
|
12
13
|
#include "painlessmesh/router.hpp"
|
|
13
14
|
#include "painlessmesh/tcp.hpp"
|
|
@@ -168,8 +169,18 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
// Skip check during startup period
|
|
172
|
-
if (millis() <
|
|
172
|
+
// Skip check during startup period to allow initial bridge discovery
|
|
173
|
+
if (millis() < electionStartupDelayMs) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// IMPORTANT: Don't trigger election if we're disconnected from the mesh
|
|
178
|
+
// When isolated, we can't receive bridge status broadcasts, so lack of
|
|
179
|
+
// healthy bridge could simply mean WE are disconnected, not that the bridge
|
|
180
|
+
// is unavailable. Wait until mesh connectivity is restored before considering
|
|
181
|
+
// an election.
|
|
182
|
+
if (!this->hasActiveMeshConnections()) {
|
|
183
|
+
Log(CONNECTION, "Bridge monitor: Skipping - no active mesh connections\n");
|
|
173
184
|
return;
|
|
174
185
|
}
|
|
175
186
|
|
|
@@ -185,14 +196,80 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
185
196
|
// If no healthy bridge exists, trigger an election
|
|
186
197
|
if (!hasHealthyBridge) {
|
|
187
198
|
Log(CONNECTION, "Bridge monitor: No healthy bridge detected, triggering election\n");
|
|
188
|
-
//
|
|
189
|
-
uint32_t randomDelay = random(
|
|
199
|
+
// Random delay to prevent simultaneous elections when multiple nodes start together
|
|
200
|
+
uint32_t randomDelay = random(electionRandomDelayMinMs, electionRandomDelayMaxMs);
|
|
201
|
+
Log(CONNECTION, "Bridge monitor: Scheduling election in %u ms\n", randomDelay);
|
|
190
202
|
this->addTask(randomDelay, TASK_ONCE, [this]() {
|
|
191
203
|
this->startBridgeElection();
|
|
192
204
|
});
|
|
193
205
|
}
|
|
194
206
|
});
|
|
195
207
|
|
|
208
|
+
// Add separate periodic task for isolated bridge retry
|
|
209
|
+
// This handles the case where a node:
|
|
210
|
+
// - Has router credentials configured
|
|
211
|
+
// - Is isolated (no mesh connections)
|
|
212
|
+
// - Should attempt to become a bridge directly
|
|
213
|
+
// This is different from the election mechanism which requires mesh connectivity
|
|
214
|
+
this->addTask(isolatedBridgeRetryIntervalMs, TASK_FOREVER, [this]() {
|
|
215
|
+
// Only retry if failover is enabled and we have credentials
|
|
216
|
+
if (!bridgeFailoverEnabled || !routerCredentialsConfigured) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Don't retry if we're already a bridge
|
|
221
|
+
if (this->isBridge()) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Skip during startup period
|
|
226
|
+
if (millis() < electionStartupDelayMs) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Only retry when isolated (no mesh connections found)
|
|
231
|
+
if (this->hasActiveMeshConnections()) {
|
|
232
|
+
// Reset retry counter when mesh is active
|
|
233
|
+
_isolatedBridgeRetryAttempts = 0;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Limit retry attempts with reset after timeout
|
|
238
|
+
if (_isolatedBridgeRetryAttempts >= MAX_ISOLATED_BRIDGE_RETRY_ATTEMPTS) {
|
|
239
|
+
// Check if enough time has passed to reset the counter
|
|
240
|
+
if (millis() > _isolatedBridgeRetryResetTime) {
|
|
241
|
+
Log(CONNECTION, "Isolated bridge retry: Reset timeout reached, resetting attempt counter\n");
|
|
242
|
+
_isolatedBridgeRetryAttempts = 0;
|
|
243
|
+
} else {
|
|
244
|
+
Log(CONNECTION, "Isolated bridge retry: Max attempts (%d) reached, reset in %u seconds\n",
|
|
245
|
+
MAX_ISOLATED_BRIDGE_RETRY_ATTEMPTS, (_isolatedBridgeRetryResetTime - millis()) / 1000);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check if mesh network exists on any channel before trying to become bridge
|
|
251
|
+
// If mesh exists but we can't connect, don't try to become bridge
|
|
252
|
+
uint16_t emptyScans = stationScan.getConsecutiveEmptyScans();
|
|
253
|
+
if (emptyScans < ISOLATED_BRIDGE_RETRY_SCAN_THRESHOLD) {
|
|
254
|
+
Log(CONNECTION, "Isolated bridge retry: Only %d empty scans, waiting for more scans\n",
|
|
255
|
+
emptyScans);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
Log(CONNECTION, "Isolated bridge retry: Node isolated with %d empty scans, attempting bridge promotion\n",
|
|
260
|
+
emptyScans);
|
|
261
|
+
|
|
262
|
+
// Attempt to become bridge directly (bypassing election since we're isolated)
|
|
263
|
+
// Only increment retry counter if we actually attempted promotion
|
|
264
|
+
if (this->attemptIsolatedBridgePromotion()) {
|
|
265
|
+
_isolatedBridgeRetryAttempts++;
|
|
266
|
+
// Set reset time when reaching max attempts
|
|
267
|
+
if (_isolatedBridgeRetryAttempts >= MAX_ISOLATED_BRIDGE_RETRY_ATTEMPTS) {
|
|
268
|
+
_isolatedBridgeRetryResetTime = millis() + isolatedBridgeRetryResetIntervalMs;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
196
273
|
tcpServerInit();
|
|
197
274
|
eventHandleInit();
|
|
198
275
|
|
|
@@ -384,6 +461,150 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
384
461
|
return success;
|
|
385
462
|
}
|
|
386
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Initialize mesh as a shared gateway node
|
|
466
|
+
*
|
|
467
|
+
* This method initializes all mesh nodes in AP+STA mode with router
|
|
468
|
+
* connectivity. Unlike initAsBridge() which creates a single bridge node,
|
|
469
|
+
* initAsSharedGateway() allows all nodes to connect to the router while
|
|
470
|
+
* maintaining mesh communication.
|
|
471
|
+
*
|
|
472
|
+
* Key features:
|
|
473
|
+
* - All nodes operate in AP+STA mode
|
|
474
|
+
* - All nodes connect to the same router
|
|
475
|
+
* - Mesh and router operate on the same channel for reliability
|
|
476
|
+
* - Automatic router reconnection on disconnect
|
|
477
|
+
* - Channel synchronization between mesh and router
|
|
478
|
+
*
|
|
479
|
+
* @param meshPrefix The name prefix for the mesh network
|
|
480
|
+
* @param meshPassword WiFi password for the mesh network
|
|
481
|
+
* @param routerSSID SSID of the router to connect to
|
|
482
|
+
* @param routerPassword Password for the router
|
|
483
|
+
* @param userScheduler Task scheduler for mesh operations
|
|
484
|
+
* @param port TCP port for mesh communication (default: 5555)
|
|
485
|
+
* @param config SharedGatewayConfig with advanced settings (optional)
|
|
486
|
+
* @return true if initialization succeeded, false otherwise
|
|
487
|
+
*/
|
|
488
|
+
bool initAsSharedGateway(TSTRING meshPrefix, TSTRING meshPassword,
|
|
489
|
+
TSTRING routerSSID, TSTRING routerPassword,
|
|
490
|
+
Scheduler *userScheduler, uint16_t port = 5555,
|
|
491
|
+
gateway::SharedGatewayConfig config = gateway::SharedGatewayConfig()) {
|
|
492
|
+
using namespace logger;
|
|
493
|
+
|
|
494
|
+
Log(STARTUP, "=== Shared Gateway Mode Initialization ===\n");
|
|
495
|
+
|
|
496
|
+
// Validate configuration if enabled
|
|
497
|
+
if (config.enabled) {
|
|
498
|
+
auto result = config.validate();
|
|
499
|
+
if (!result.valid) {
|
|
500
|
+
Log(ERROR, "initAsSharedGateway(): Config validation failed: %s\n",
|
|
501
|
+
result.errorMessage.c_str());
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Store shared gateway configuration
|
|
507
|
+
_sharedGatewayConfig = config;
|
|
508
|
+
_sharedGatewayConfig.routerSSID = routerSSID;
|
|
509
|
+
_sharedGatewayConfig.routerPassword = routerPassword;
|
|
510
|
+
_sharedGatewayConfig.enabled = true;
|
|
511
|
+
_sharedGatewayMode = true;
|
|
512
|
+
|
|
513
|
+
Log(STARTUP, "Step 1: Scanning for router %s to detect channel...\n", routerSSID.c_str());
|
|
514
|
+
|
|
515
|
+
// Step 1: Scan for router to detect its channel
|
|
516
|
+
// We need to ensure mesh and router operate on the same channel
|
|
517
|
+
if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
|
|
518
|
+
|
|
519
|
+
#if ESP_ARDUINO_VERSION_MAJOR >= 3
|
|
520
|
+
WiFi.setAutoReconnect(false);
|
|
521
|
+
Log(STARTUP, "initAsSharedGateway(): AutoReconnect disabled\n");
|
|
522
|
+
#else
|
|
523
|
+
WiFi.setAutoConnect(false);
|
|
524
|
+
Log(STARTUP, "initAsSharedGateway(): AutoConnect disabled\n");
|
|
525
|
+
#endif
|
|
526
|
+
WiFi.persistent(false);
|
|
527
|
+
WiFi.mode(WIFI_STA);
|
|
528
|
+
|
|
529
|
+
// Connect to router to detect channel
|
|
530
|
+
WiFi.begin(routerSSID.c_str(), routerPassword.c_str());
|
|
531
|
+
|
|
532
|
+
// Wait for connection with timeout (using constant for configurability)
|
|
533
|
+
int timeout = ROUTER_CONNECTION_TIMEOUT_SECONDS;
|
|
534
|
+
while (WiFi.status() != WL_CONNECTED && timeout > 0) {
|
|
535
|
+
delay(1000);
|
|
536
|
+
timeout--;
|
|
537
|
+
Log(STARTUP, ".");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
uint8_t detectedChannel = 1; // Default fallback
|
|
541
|
+
|
|
542
|
+
if (WiFi.status() == WL_CONNECTED) {
|
|
543
|
+
detectedChannel = WiFi.channel();
|
|
544
|
+
// Validate channel is in valid range (1-14 for 2.4GHz, region-dependent)
|
|
545
|
+
if (detectedChannel < MIN_WIFI_CHANNEL || detectedChannel > MAX_WIFI_CHANNEL) {
|
|
546
|
+
Log(ERROR, "\n✗ Invalid channel detected: %d, falling back to channel 1\n", detectedChannel);
|
|
547
|
+
detectedChannel = 1;
|
|
548
|
+
} else {
|
|
549
|
+
Log(STARTUP, "\n✓ Router connected on channel %d\n", detectedChannel);
|
|
550
|
+
Log(STARTUP, "✓ Router IP: %s\n", WiFi.localIP().toString().c_str());
|
|
551
|
+
}
|
|
552
|
+
} else {
|
|
553
|
+
Log(ERROR, "\n✗ Failed to connect to router during channel detection\n");
|
|
554
|
+
Log(ERROR, "Continuing with default channel 1, will retry router connection later\n");
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Disconnect from router, we'll reconnect after mesh init
|
|
558
|
+
WiFi.disconnect();
|
|
559
|
+
delay(100);
|
|
560
|
+
|
|
561
|
+
Log(STARTUP, "Step 2: Initializing mesh on channel %d...\n", detectedChannel);
|
|
562
|
+
|
|
563
|
+
// Step 2: Initialize mesh on detected channel with AP+STA mode
|
|
564
|
+
// Set scheduler before init
|
|
565
|
+
this->setScheduler(userScheduler);
|
|
566
|
+
init(meshPrefix, meshPassword, port, WIFI_AP_STA, detectedChannel, 0, MAX_CONN);
|
|
567
|
+
|
|
568
|
+
Log(STARTUP, "Step 3: Establishing router connection in shared gateway mode...\n");
|
|
569
|
+
|
|
570
|
+
// Step 3: Establish router connection using stationManual
|
|
571
|
+
// Port 0 means we don't expect TCP mesh connection to the router
|
|
572
|
+
stationManual(routerSSID, routerPassword, 0);
|
|
573
|
+
|
|
574
|
+
// Step 4: Setup router connection monitoring and reconnection logic
|
|
575
|
+
initSharedGatewayMonitoring();
|
|
576
|
+
|
|
577
|
+
// Store router credentials for reconnection
|
|
578
|
+
setRouterCredentials(routerSSID, routerPassword);
|
|
579
|
+
|
|
580
|
+
Log(STARTUP, "=== Shared Gateway Mode Active ===\n");
|
|
581
|
+
Log(STARTUP, " Mesh Prefix: %s\n", meshPrefix.c_str());
|
|
582
|
+
Log(STARTUP, " Mesh Channel: %d (synced with router)\n", detectedChannel);
|
|
583
|
+
Log(STARTUP, " Router: %s\n", routerSSID.c_str());
|
|
584
|
+
Log(STARTUP, " Port: %d\n", port);
|
|
585
|
+
Log(STARTUP, " Mode: AP+STA (all nodes can connect to router)\n");
|
|
586
|
+
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Check if shared gateway mode is enabled
|
|
592
|
+
*
|
|
593
|
+
* @return true if node is operating in shared gateway mode
|
|
594
|
+
*/
|
|
595
|
+
bool isSharedGatewayMode() const {
|
|
596
|
+
return _sharedGatewayMode;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Get the shared gateway configuration
|
|
601
|
+
*
|
|
602
|
+
* @return const reference to the SharedGatewayConfig
|
|
603
|
+
*/
|
|
604
|
+
const gateway::SharedGatewayConfig& getSharedGatewayConfig() const {
|
|
605
|
+
return _sharedGatewayConfig;
|
|
606
|
+
}
|
|
607
|
+
|
|
387
608
|
/**
|
|
388
609
|
* Connect (as a station) to a specified network and ip
|
|
389
610
|
*
|
|
@@ -555,6 +776,37 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
555
776
|
minimumBridgeRSSI = minRSSI;
|
|
556
777
|
}
|
|
557
778
|
|
|
779
|
+
/**
|
|
780
|
+
* Set the startup delay before first bridge election check
|
|
781
|
+
*
|
|
782
|
+
* Allows time for mesh network formation before starting bridge elections.
|
|
783
|
+
* Longer delays reduce the risk of split-brain scenarios when multiple nodes
|
|
784
|
+
* start simultaneously, ensuring nodes discover each other before elections.
|
|
785
|
+
*
|
|
786
|
+
* @param delayMs Startup delay in milliseconds (default: 60000 = 60 seconds, min: 10000)
|
|
787
|
+
*/
|
|
788
|
+
void setElectionStartupDelay(uint32_t delayMs) {
|
|
789
|
+
if (delayMs < 10000) delayMs = 10000; // Minimum 10 seconds
|
|
790
|
+
electionStartupDelayMs = delayMs;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Set the random delay range for bridge elections
|
|
795
|
+
*
|
|
796
|
+
* When multiple nodes detect missing bridge simultaneously, randomized delays
|
|
797
|
+
* prevent all nodes from starting elections at the same instant. Longer delays
|
|
798
|
+
* provide more time for mesh discovery and reduce split-brain risk.
|
|
799
|
+
*
|
|
800
|
+
* @param minMs Minimum random delay in milliseconds (default: 1000 = 1 second)
|
|
801
|
+
* @param maxMs Maximum random delay in milliseconds (default: 3000 = 3 seconds)
|
|
802
|
+
*/
|
|
803
|
+
void setElectionRandomDelay(uint32_t minMs, uint32_t maxMs) {
|
|
804
|
+
if (minMs < 100) minMs = 100; // Minimum 100ms
|
|
805
|
+
if (maxMs < minMs) maxMs = minMs + 1000; // Ensure max > min
|
|
806
|
+
electionRandomDelayMinMs = minMs;
|
|
807
|
+
electionRandomDelayMaxMs = maxMs;
|
|
808
|
+
}
|
|
809
|
+
|
|
558
810
|
/**
|
|
559
811
|
* Set callback for when this node's bridge role changes
|
|
560
812
|
*
|
|
@@ -1342,6 +1594,95 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1342
1594
|
});
|
|
1343
1595
|
}
|
|
1344
1596
|
|
|
1597
|
+
/**
|
|
1598
|
+
* Attempt to promote an isolated node to bridge
|
|
1599
|
+
*
|
|
1600
|
+
* This method handles the case where a node is isolated (no mesh connections)
|
|
1601
|
+
* but has router credentials. Unlike the election-based promotion, this
|
|
1602
|
+
* directly attempts to connect to the router without requiring mesh connectivity.
|
|
1603
|
+
*
|
|
1604
|
+
* This is useful for:
|
|
1605
|
+
* - Nodes that failed initial bridge setup and need to retry
|
|
1606
|
+
* - Nodes that are the first to start and no mesh exists yet
|
|
1607
|
+
* - Recovery scenarios where mesh network is unavailable
|
|
1608
|
+
*
|
|
1609
|
+
* @return true if promotion was attempted (regardless of success), false if skipped
|
|
1610
|
+
*/
|
|
1611
|
+
bool attemptIsolatedBridgePromotion() {
|
|
1612
|
+
using namespace logger;
|
|
1613
|
+
|
|
1614
|
+
Log(CONNECTION, "=== Isolated Bridge Promotion Attempt ===\n");
|
|
1615
|
+
Log(CONNECTION, "Attempt %d of %d\n", _isolatedBridgeRetryAttempts + 1, MAX_ISOLATED_BRIDGE_RETRY_ATTEMPTS);
|
|
1616
|
+
|
|
1617
|
+
// First, scan for router to check if it's visible
|
|
1618
|
+
int8_t routerRSSI = scanRouterSignalStrength(routerSSID);
|
|
1619
|
+
|
|
1620
|
+
if (routerRSSI == 0) {
|
|
1621
|
+
Log(CONNECTION, "attemptIsolatedBridgePromotion(): Router %s not visible\n", routerSSID.c_str());
|
|
1622
|
+
return false; // Don't count as an attempt - router not visible
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// Check minimum RSSI threshold for isolated promotion
|
|
1626
|
+
if (routerRSSI < minimumBridgeRSSI) {
|
|
1627
|
+
Log(CONNECTION, "attemptIsolatedBridgePromotion(): Router RSSI %d dBm below threshold %d dBm\n",
|
|
1628
|
+
routerRSSI, minimumBridgeRSSI);
|
|
1629
|
+
return false; // Don't count as an attempt - signal too weak
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
Log(CONNECTION, "attemptIsolatedBridgePromotion(): Router visible with RSSI %d dBm\n", routerRSSI);
|
|
1633
|
+
Log(CONNECTION, "Attempting direct bridge promotion (bypassing election)\n");
|
|
1634
|
+
|
|
1635
|
+
// Save current mesh configuration
|
|
1636
|
+
uint8_t savedChannel = _meshChannel;
|
|
1637
|
+
|
|
1638
|
+
// Stop current mesh operations
|
|
1639
|
+
this->stop();
|
|
1640
|
+
delay(1000);
|
|
1641
|
+
|
|
1642
|
+
// Attempt to initialize as bridge
|
|
1643
|
+
bool bridgeInitSuccess = this->initAsBridge(_meshSSID, _meshPassword, routerSSID, routerPassword,
|
|
1644
|
+
mScheduler, _meshPort);
|
|
1645
|
+
|
|
1646
|
+
if (!bridgeInitSuccess) {
|
|
1647
|
+
Log(ERROR, "✗ Isolated bridge promotion failed - router unreachable\n");
|
|
1648
|
+
Log(ERROR, "Reverting to regular node on channel %d\n", savedChannel);
|
|
1649
|
+
|
|
1650
|
+
// Re-initialize as regular node on the original channel
|
|
1651
|
+
this->init(_meshSSID, _meshPassword, mScheduler, _meshPort, WIFI_AP_STA,
|
|
1652
|
+
savedChannel, _meshHidden, MAX_CONN);
|
|
1653
|
+
|
|
1654
|
+
// Re-configure router credentials for future retry attempts
|
|
1655
|
+
this->setRouterCredentials(routerSSID, routerPassword);
|
|
1656
|
+
this->enableBridgeFailover(true);
|
|
1657
|
+
|
|
1658
|
+
// Notify via callback
|
|
1659
|
+
if (bridgeRoleChangedCallback) {
|
|
1660
|
+
bridgeRoleChangedCallback(false, "Isolated bridge promotion failed - router unreachable");
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
return true; // Count as an attempt - we tried but failed
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Success! Reset retry counter
|
|
1667
|
+
_isolatedBridgeRetryAttempts = 0;
|
|
1668
|
+
lastRoleChangeTime = millis();
|
|
1669
|
+
|
|
1670
|
+
Log(STARTUP, "✓ Isolated bridge promotion complete on channel %d\n", _meshChannel);
|
|
1671
|
+
|
|
1672
|
+
// Notify via callback
|
|
1673
|
+
if (bridgeRoleChangedCallback) {
|
|
1674
|
+
bridgeRoleChangedCallback(true, "Isolated node promoted to bridge");
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// Send bridge status announcement to attract other nodes
|
|
1678
|
+
this->addTask(3000, TASK_ONCE, [this]() {
|
|
1679
|
+
Log(STARTUP, "Sending bridge status announcement on channel %d\n", _meshChannel);
|
|
1680
|
+
this->sendBridgeStatus();
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
return true; // Count as an attempt - we succeeded
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1345
1686
|
/**
|
|
1346
1687
|
* Handle received bridge election package
|
|
1347
1688
|
* Called by package handler when election message arrives
|
|
@@ -1564,12 +1905,23 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1564
1905
|
TSTRING routerPassword = "";
|
|
1565
1906
|
uint32_t electionTimeoutMs = 5000; // Default 5 seconds
|
|
1566
1907
|
int8_t minimumBridgeRSSI = -80; // Default -80 dBm minimum for isolated elections
|
|
1908
|
+
uint32_t electionStartupDelayMs = 60000; // Default 60 seconds before first election check
|
|
1909
|
+
uint32_t electionRandomDelayMinMs = 1000; // Default min 1 second random delay
|
|
1910
|
+
uint32_t electionRandomDelayMaxMs = 3000; // Default max 3 seconds random delay
|
|
1567
1911
|
uint32_t lastRoleChangeTime = 0;
|
|
1568
1912
|
ElectionState electionState = ELECTION_IDLE;
|
|
1569
1913
|
uint32_t electionDeadline = 0;
|
|
1570
1914
|
std::vector<BridgeCandidate> electionCandidates;
|
|
1571
1915
|
std::function<void(bool isBridge, TSTRING reason)> bridgeRoleChangedCallback;
|
|
1572
1916
|
|
|
1917
|
+
// Isolated bridge retry state and configuration
|
|
1918
|
+
uint8_t _isolatedBridgeRetryAttempts = 0;
|
|
1919
|
+
uint32_t _isolatedBridgeRetryResetTime = 0; // Time when retry counter can be reset
|
|
1920
|
+
static const uint8_t MAX_ISOLATED_BRIDGE_RETRY_ATTEMPTS = 5; // Max retry attempts before waiting
|
|
1921
|
+
static const uint32_t isolatedBridgeRetryIntervalMs = 60000; // Retry every 60 seconds
|
|
1922
|
+
static const uint32_t isolatedBridgeRetryResetIntervalMs = 300000; // Reset counter after 5 minutes
|
|
1923
|
+
static const uint16_t ISOLATED_BRIDGE_RETRY_SCAN_THRESHOLD = 6; // Require 6 empty scans before retrying
|
|
1924
|
+
|
|
1573
1925
|
// Multi-bridge coordination state and configuration
|
|
1574
1926
|
protected:
|
|
1575
1927
|
bool multiBridgeEnabled = false;
|
|
@@ -1582,6 +1934,154 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1582
1934
|
std::vector<uint32_t> knownBridgePeers; // List of peer bridge node IDs
|
|
1583
1935
|
uint32_t selectedBridgeOverride = 0; // Manual bridge selection override
|
|
1584
1936
|
size_t lastSelectedBridgeIndex = 0; // For round-robin selection
|
|
1937
|
+
|
|
1938
|
+
// Shared gateway mode state and configuration
|
|
1939
|
+
bool _sharedGatewayMode = false;
|
|
1940
|
+
gateway::SharedGatewayConfig _sharedGatewayConfig;
|
|
1941
|
+
std::shared_ptr<Task> _sharedGatewayMonitorTask;
|
|
1942
|
+
uint32_t _lastRouterReconnectAttempt = 0;
|
|
1943
|
+
uint8_t _routerReconnectAttempts = 0;
|
|
1944
|
+
static const uint8_t MAX_ROUTER_RECONNECT_ATTEMPTS = 10;
|
|
1945
|
+
static const uint32_t ROUTER_RECONNECT_BASE_INTERVAL = 5000; // 5 seconds base interval
|
|
1946
|
+
static const uint32_t ROUTER_RECONNECT_MAX_INTERVAL = 300000; // 5 minutes max interval
|
|
1947
|
+
static const int ROUTER_CONNECTION_TIMEOUT_SECONDS = 30; // Router connection timeout
|
|
1948
|
+
static const uint8_t MIN_WIFI_CHANNEL = 1;
|
|
1949
|
+
static const uint8_t MAX_WIFI_CHANNEL = 14; // Support channels 1-14 for regions that allow it
|
|
1950
|
+
|
|
1951
|
+
/**
|
|
1952
|
+
* Initialize shared gateway monitoring
|
|
1953
|
+
*
|
|
1954
|
+
* Sets up periodic monitoring of router connection and automatic
|
|
1955
|
+
* reconnection logic for shared gateway mode.
|
|
1956
|
+
*/
|
|
1957
|
+
void initSharedGatewayMonitoring() {
|
|
1958
|
+
using namespace logger;
|
|
1959
|
+
|
|
1960
|
+
if (!_sharedGatewayMode) {
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
Log(STARTUP, "initSharedGatewayMonitoring(): Setting up router connection monitoring\n");
|
|
1965
|
+
|
|
1966
|
+
// Add callback for router disconnection in shared gateway mode
|
|
1967
|
+
this->droppedConnectionCallbacks.push_back(
|
|
1968
|
+
[this](uint32_t nodeId, bool station) {
|
|
1969
|
+
if (station && _sharedGatewayMode) {
|
|
1970
|
+
Log(CONNECTION, "Router disconnected in shared gateway mode, scheduling reconnection\n");
|
|
1971
|
+
scheduleRouterReconnect();
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
// Create periodic monitoring task
|
|
1976
|
+
_sharedGatewayMonitorTask = this->addTask(
|
|
1977
|
+
_sharedGatewayConfig.internetCheckInterval,
|
|
1978
|
+
TASK_FOREVER,
|
|
1979
|
+
[this]() {
|
|
1980
|
+
monitorRouterConnection();
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
Log(STARTUP, "Router connection monitoring enabled (interval: %u ms)\n",
|
|
1984
|
+
_sharedGatewayConfig.internetCheckInterval);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/**
|
|
1988
|
+
* Monitor router connection in shared gateway mode
|
|
1989
|
+
*
|
|
1990
|
+
* Checks router connectivity and triggers reconnection if needed.
|
|
1991
|
+
*/
|
|
1992
|
+
void monitorRouterConnection() {
|
|
1993
|
+
using namespace logger;
|
|
1994
|
+
|
|
1995
|
+
if (!_sharedGatewayMode) {
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
bool isConnected = (WiFi.status() == WL_CONNECTED) &&
|
|
2000
|
+
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
2001
|
+
|
|
2002
|
+
if (!isConnected) {
|
|
2003
|
+
Log(CONNECTION, "monitorRouterConnection(): Router connection lost, triggering reconnect\n");
|
|
2004
|
+
scheduleRouterReconnect();
|
|
2005
|
+
} else {
|
|
2006
|
+
// Connection is healthy, reset reconnect attempts
|
|
2007
|
+
_routerReconnectAttempts = 0;
|
|
2008
|
+
|
|
2009
|
+
// Log periodic status
|
|
2010
|
+
Log(GENERAL, "monitorRouterConnection(): Router connected (RSSI: %d dBm, IP: %s)\n",
|
|
2011
|
+
WiFi.RSSI(), WiFi.localIP().toString().c_str());
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
/**
|
|
2016
|
+
* Schedule router reconnection with exponential backoff
|
|
2017
|
+
*/
|
|
2018
|
+
void scheduleRouterReconnect() {
|
|
2019
|
+
using namespace logger;
|
|
2020
|
+
|
|
2021
|
+
if (!_sharedGatewayMode) {
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// Don't schedule if already connected
|
|
2026
|
+
if (WiFi.status() == WL_CONNECTED) {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// Limit reconnection attempts
|
|
2031
|
+
if (_routerReconnectAttempts >= MAX_ROUTER_RECONNECT_ATTEMPTS) {
|
|
2032
|
+
Log(ERROR, "scheduleRouterReconnect(): Max reconnection attempts reached (%d)\n",
|
|
2033
|
+
MAX_ROUTER_RECONNECT_ATTEMPTS);
|
|
2034
|
+
Log(ERROR, "Router reconnection suspended. Manual intervention may be required.\n");
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Calculate delay with exponential backoff, preventing overflow
|
|
2039
|
+
// Limit shift amount to prevent overflow (5000 * 2^6 = 320000 is safe)
|
|
2040
|
+
uint8_t shiftAmount = (_routerReconnectAttempts > 6) ? 6 : _routerReconnectAttempts;
|
|
2041
|
+
uint32_t delay = ROUTER_RECONNECT_BASE_INTERVAL * (1UL << shiftAmount);
|
|
2042
|
+
if (delay > ROUTER_RECONNECT_MAX_INTERVAL) delay = ROUTER_RECONNECT_MAX_INTERVAL;
|
|
2043
|
+
|
|
2044
|
+
// Don't reconnect too frequently
|
|
2045
|
+
uint32_t now = millis();
|
|
2046
|
+
if (now - _lastRouterReconnectAttempt < delay) {
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
_routerReconnectAttempts++;
|
|
2051
|
+
_lastRouterReconnectAttempt = now;
|
|
2052
|
+
|
|
2053
|
+
Log(CONNECTION, "scheduleRouterReconnect(): Attempting reconnection (attempt %d/%d, delay %u ms)\n",
|
|
2054
|
+
_routerReconnectAttempts, MAX_ROUTER_RECONNECT_ATTEMPTS, delay);
|
|
2055
|
+
|
|
2056
|
+
// Schedule reconnection
|
|
2057
|
+
this->addTask(delay, TASK_ONCE, [this]() {
|
|
2058
|
+
attemptRouterReconnect();
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
/**
|
|
2063
|
+
* Attempt to reconnect to the router
|
|
2064
|
+
*/
|
|
2065
|
+
void attemptRouterReconnect() {
|
|
2066
|
+
using namespace logger;
|
|
2067
|
+
|
|
2068
|
+
if (!_sharedGatewayMode) {
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// Check if already connected
|
|
2073
|
+
if (WiFi.status() == WL_CONNECTED) {
|
|
2074
|
+
Log(CONNECTION, "attemptRouterReconnect(): Already connected to router\n");
|
|
2075
|
+
_routerReconnectAttempts = 0;
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
Log(CONNECTION, "attemptRouterReconnect(): Reconnecting to router %s...\n",
|
|
2080
|
+
_sharedGatewayConfig.routerSSID.c_str());
|
|
2081
|
+
|
|
2082
|
+
// Use stationManual to reconnect (port 0 means no TCP mesh connection to router)
|
|
2083
|
+
stationManual(_sharedGatewayConfig.routerSSID, _sharedGatewayConfig.routerPassword, 0);
|
|
2084
|
+
}
|
|
1585
2085
|
};
|
|
1586
2086
|
} // namespace wifi
|
|
1587
2087
|
}; // namespace painlessmesh
|