@alteriom/painlessmesh 1.8.15 → 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.
Files changed (208) hide show
  1. package/BRIDGE_TO_INTERNET.md +229 -0
  2. package/CHANGELOG.md +61 -1
  3. package/CONTRIBUTING.md +79 -0
  4. package/README.md +69 -144
  5. package/docs/README.md +1 -0
  6. package/docs/api/shared-gateway.md +1207 -0
  7. package/examples/bridge_failover/README.md +81 -0
  8. package/examples/bridge_failover/bridge_failover.ino +35 -4
  9. package/examples/sharedGateway/README.md +235 -0
  10. package/examples/{meshCommandNode → sharedGateway}/platformio.ini +3 -2
  11. package/examples/sharedGateway/sharedGateway.ino +303 -0
  12. package/library.json +3 -22
  13. package/library.properties +1 -1
  14. package/package.json +3 -6
  15. package/src/arduino/wifi.hpp +342 -4
  16. package/src/painlessmesh/gateway.hpp +2120 -0
  17. package/src/painlessmesh/mesh.hpp +1034 -6
  18. package/src/painlessmesh/message_tracker.hpp +311 -0
  19. package/src/painlessmesh/protocol.hpp +6 -0
  20. package/DOCUMENTATION_INDEX.md +0 -146
  21. package/RELEASE_NOTES_1.8.15.md +0 -160
  22. package/RELEASE_READINESS_PLAN.md +0 -323
  23. package/TESTING_WITH_SIMULATOR.md +0 -259
  24. package/docs/API_DESIGN_GUIDELINES.md +0 -414
  25. package/docs/ARDUINO_LIBRARY_MANAGER_SUBMISSION.md +0 -331
  26. package/docs/BOOLEAN_NAMING_CONVENTION.md +0 -235
  27. package/docs/BRIDGE_FAILOVER.md +0 -512
  28. package/docs/BRIDGE_HEALTH_MONITORING.md +0 -293
  29. package/docs/BRIDGE_INITIALIZATION_FALLBACK.md +0 -357
  30. package/docs/CHANNEL_SYNCHRONIZATION.md +0 -209
  31. package/docs/CREATE_MISSING_RELEASES.md +0 -321
  32. package/docs/DOCUMENTATION_MIGRATION_PLAN.md +0 -176
  33. package/docs/FAQ_VERSION_NUMBERS.md +0 -152
  34. package/docs/IMPLEMENTATION_PLAN_MESH_TOPOLOGY.md +0 -1062
  35. package/docs/MESH_TOPOLOGY_GUIDE.md +0 -992
  36. package/docs/MESH_TOPOLOGY_PROGRESS.md +0 -422
  37. package/docs/MQTT_BRIDGE_COMMANDS.md +0 -894
  38. package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +0 -324
  39. package/docs/MQTT_COMMAND_SCHEMA_PROPOSAL.md +0 -576
  40. package/docs/MQTT_SCHEMA_COMPLIANCE.md +0 -340
  41. package/docs/MQTT_SCHEMA_PROPOSALS.md +0 -446
  42. package/docs/MQTT_SCHEMA_REVIEW.md +0 -690
  43. package/docs/OTA_COMMANDS_REFERENCE.md +0 -554
  44. package/docs/PHASE1_GUIDE.md +0 -349
  45. package/docs/PHASE2_GUIDE.md +0 -543
  46. package/docs/QUICK_REFERENCE_VERSIONING.md +0 -127
  47. package/docs/RELEASE_AGENT_SUMMARY.md +0 -386
  48. package/docs/SCHEMA_VALIDATION_CHECKLIST.md +0 -222
  49. package/docs/SIMULATOR_TESTING.md +0 -408
  50. package/docs/VERSION_MANAGEMENT.md +0 -213
  51. package/docs/archive/DOCUSAURUS_DEPLOYMENT.md +0 -166
  52. package/docs/archive/FEATURE_PROPOSALS.md +0 -337
  53. package/docs/archive/LIBRARY_JSON_FIX.md +0 -98
  54. package/docs/archive/LIBRARY_STRUCTURE_FIX.md +0 -215
  55. package/docs/archive/PHASE1_IMPLEMENTATION.md +0 -325
  56. package/docs/archive/PHASE2_IMPLEMENTATION.md +0 -567
  57. package/docs/archive/RELEASE_SUMMARY.md +0 -173
  58. package/docs/archive/SCONS_BUILD_FIX.md +0 -313
  59. package/docs/archive/TRIGGER_RELEASE.md +0 -280
  60. package/docs/archive/VECTOR_INCLUDE_FIX.md +0 -129
  61. package/docs/archive/ota-and-status-enhancements.md +0 -911
  62. package/docs/archive/ota-status-architecture-diagrams.md +0 -658
  63. package/docs/archive/ota-status-quick-reference.md +0 -284
  64. package/docs/design/.gitkeep +0 -1
  65. package/docs/design/STATION_CREDENTIALS_DESIGN.md +0 -182
  66. package/docs/development/ARDUINO_COMPLIANCE_SUMMARY.md +0 -71
  67. package/docs/development/CODE_REFACTORING_RECOMMENDATIONS.md +0 -1011
  68. package/docs/development/DOCKER_TESTING.md +0 -196
  69. package/docs/development/PLATFORMIO_USAGE.md +0 -180
  70. package/docs/development/TESTING_SUMMARY.md +0 -126
  71. package/docs/development/contributing.md +0 -301
  72. package/docs/development/documentation.md +0 -583
  73. package/docs/features/DIAGNOSTICS_API.md +0 -534
  74. package/docs/implementation/BRIDGE_ARCHITECTURE_IMPLEMENTATION.md +0 -340
  75. package/docs/implementation/BRIDGE_HEALTH_MONITORING_IMPLEMENTATION.md +0 -213
  76. package/docs/implementation/BRIDGE_STATUS_FEATURE.md +0 -635
  77. package/docs/implementation/DIAGNOSTICS_API_IMPLEMENTATION.md +0 -232
  78. package/docs/implementation/IMPLEMENTATION_COMPLETE.md +0 -228
  79. package/docs/implementation/IMPLEMENTATION_NTP_TIME_SYNC.md +0 -325
  80. package/docs/implementation/IMPLEMENTATION_SUMMARY.md +0 -316
  81. package/docs/implementation/MESSAGE_QUEUE_IMPLEMENTATION.md +0 -405
  82. package/docs/implementation/MULTI_BRIDGE_IMPLEMENTATION.md +0 -520
  83. package/docs/implementation/NTP_TIME_SYNC_FEATURE.md +0 -392
  84. package/docs/improvements/FUTURE_PROPOSALS.md +0 -1016
  85. package/docs/improvements/IMPLEMENTATION_HISTORY.md +0 -1091
  86. package/docs/improvements/OTA_STATUS_ENHANCEMENTS.md +0 -709
  87. package/docs/improvements/README.md +0 -212
  88. package/docs/internal/CUSTOM_AGENT_ANALYSIS.md +0 -391
  89. package/docs/internal/ISSUE_65_VERIFICATION.md +0 -947
  90. package/docs/internal/ISSUE_66_CLOSURE.md +0 -249
  91. package/docs/internal/ISSUE_66_STATUS.md +0 -316
  92. package/docs/internal/PR_SUMMARY.md +0 -315
  93. package/docs/internal/REVIEW_SUMMARY.md +0 -332
  94. package/docs/multi-bridge-setup.md +0 -1025
  95. package/docs/platformio-publishing.md +0 -255
  96. package/docs/platformio-setup-summary.md +0 -121
  97. package/docs/releases/ANNOUNCEMENT_v1.8.6.md +0 -63
  98. package/docs/releases/ANNOUNCEMENT_v1.8.7.md +0 -113
  99. package/docs/releases/BRIDGE_STATUS_SELF_REGISTRATION_FIX.md +0 -221
  100. package/docs/releases/FEATURE_HISTORY.md +0 -543
  101. package/docs/releases/GITHUB_RELEASE_v1.8.6.md +0 -71
  102. package/docs/releases/GITHUB_RELEASE_v1.8.7.md +0 -90
  103. package/docs/releases/PATCH_v1.7.2.md +0 -262
  104. package/docs/releases/PATCH_v1.7.3.md +0 -262
  105. package/docs/releases/PATCH_v1.7.4.md +0 -219
  106. package/docs/releases/PHASE1_SUMMARY.md +0 -246
  107. package/docs/releases/PHASE2_SUMMARY.md +0 -499
  108. package/docs/releases/PUBLISH_v1.8.0_INSTRUCTIONS.md +0 -163
  109. package/docs/releases/QUICK_START_RELEASES.md +0 -113
  110. package/docs/releases/RELEASE_CHECKLIST_1.8.10.md +0 -331
  111. package/docs/releases/RELEASE_CHECKLIST_1.8.9.md +0 -207
  112. package/docs/releases/RELEASE_CHECKLIST_v1.7.4.md +0 -253
  113. package/docs/releases/RELEASE_CHECKLIST_v1.7.5.md +0 -315
  114. package/docs/releases/RELEASE_CHECKLIST_v1.7.6.md +0 -389
  115. package/docs/releases/RELEASE_CHECKLIST_v1.8.0.md +0 -331
  116. package/docs/releases/RELEASE_CHECKLIST_v1.8.2.md +0 -309
  117. package/docs/releases/RELEASE_NOTES_1.7.0.md +0 -539
  118. package/docs/releases/RELEASE_NOTES_1.8.10.md +0 -239
  119. package/docs/releases/RELEASE_NOTES_1.8.9.md +0 -213
  120. package/docs/releases/RELEASE_NOTES_v1.8.0.md +0 -685
  121. package/docs/releases/RELEASE_NOTES_v1.8.1.md +0 -221
  122. package/docs/releases/RELEASE_NOTES_v1.8.2.md +0 -421
  123. package/docs/releases/RELEASE_NOTES_v1.8.3.md +0 -292
  124. package/docs/releases/RELEASE_NOTES_v1.8.4.md +0 -277
  125. package/docs/releases/RELEASE_NOTES_v1.8.6.md +0 -205
  126. package/docs/releases/RELEASE_NOTES_v1.8.7.md +0 -184
  127. package/docs/releases/RELEASE_PLAN_v1.7.6.md +0 -816
  128. package/docs/releases/RELEASE_SUMMARY_1.8.10.md +0 -193
  129. package/docs/releases/RELEASE_SUMMARY_v1.7.4.md +0 -276
  130. package/docs/releases/RELEASE_SUMMARY_v1.7.5.md +0 -322
  131. package/docs/releases/RELEASE_SUMMARY_v1.7.6.md +0 -436
  132. package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +0 -391
  133. package/docs/releases/RELEASE_SUMMARY_v1.7.8.md +0 -523
  134. package/docs/releases/RELEASE_SUMMARY_v1.7.9.md +0 -542
  135. package/docs/troubleshooting/ARDUINO_IDE_VERSION_FIX_SUMMARY.md +0 -229
  136. package/docs/troubleshooting/ARDUINO_LIBRARY_NAME_FIX.md +0 -197
  137. package/docs/troubleshooting/CRASH_QUICK_REF.md +0 -93
  138. package/docs/troubleshooting/ESP32_C6_COMPATIBILITY.md +0 -157
  139. package/docs/troubleshooting/FREERTOS_ASSERTION_FAILURE.md +0 -288
  140. package/docs/troubleshooting/FREERTOS_FIX_IMPLEMENTATION.md +0 -267
  141. package/docs/troubleshooting/NPM_PUBLISHING_ISSUE_SUMMARY.md +0 -110
  142. package/docs/troubleshooting/PAINLESSMESH_V1.7.4_COMPILATION_ISSUES.md +0 -547
  143. package/docs/troubleshooting/QUICK_FIX_FREERTOS.md +0 -164
  144. package/docs/troubleshooting/SENSOR_NODE_CONNECTION_CRASH.md +0 -264
  145. package/docs/troubleshooting/common-architecture-mistakes.md +0 -438
  146. package/docs/troubleshooting/internet-access-faq.md +0 -299
  147. package/docs/troubleshooting/station-reconnection-issues.md +0 -172
  148. package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +0 -794
  149. package/docs/wiki/API-Reference.md +0 -246
  150. package/docs/wiki/Complete-Documentation.md +0 -123
  151. package/examples/alteriomImproved/alteriom_sensor_package.hpp +0 -224
  152. package/examples/alteriomImproved/improved_sensor_node.ino +0 -248
  153. package/examples/alteriomImproved/platformio.ini +0 -32
  154. package/examples/alteriomMetricsHealth/alteriom_sensor_package.hpp +0 -796
  155. package/examples/alteriomMetricsHealth/metrics_health_node.ino +0 -429
  156. package/examples/alteriomMetricsHealth/platformio.ini +0 -26
  157. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +0 -224
  158. package/examples/alteriomPhase1/phase1_features.ino +0 -242
  159. package/examples/alteriomPhase1/platformio.ini +0 -26
  160. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +0 -224
  161. package/examples/alteriomPhase2/phase2_features.ino +0 -186
  162. package/examples/alteriomPhase2/platformio.ini +0 -26
  163. package/examples/alteriomSensorNode/alteriom_sensor_node.ino +0 -186
  164. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +0 -1227
  165. package/examples/alteriomSensorNode/platformio.ini +0 -26
  166. package/examples/bridge/alteriom_sensor_package.hpp +0 -1170
  167. package/examples/bridge/bridge_health_monitoring_example.ino +0 -188
  168. package/examples/bridge/enhanced_mqtt_bridge.hpp +0 -610
  169. package/examples/bridge/enhanced_mqtt_bridge_example.ino +0 -226
  170. package/examples/bridge/mesh_event_publisher.hpp +0 -253
  171. package/examples/bridge/mesh_topology_reporter.hpp +0 -303
  172. package/examples/bridge/mqtt_command_bridge.hpp +0 -459
  173. package/examples/bridge/mqtt_status_bridge.hpp +0 -519
  174. package/examples/bridgeAwareSensorNode/alteriom_sensor_package.hpp +0 -1227
  175. package/examples/bridgeAwareSensorNode/bridgeAwareSensorNode.ino +0 -342
  176. package/examples/bridgeAwareSensorNode/platformio.ini +0 -26
  177. package/examples/diagnosticsExample/diagnosticsExample.ino +0 -171
  178. package/examples/diagnosticsExample/platformio.ini +0 -26
  179. package/examples/echoNode/echoNode.ino +0 -33
  180. package/examples/echoNode/platformio.ini +0 -26
  181. package/examples/meshCommandNode/alteriom_sensor_package.hpp +0 -235
  182. package/examples/meshCommandNode/meshCommandNode.ino +0 -265
  183. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +0 -235
  184. package/examples/mqttCommandBridge/mesh_event_publisher.hpp +0 -253
  185. package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +0 -303
  186. package/examples/mqttCommandBridge/mqttCommandBridge.ino +0 -254
  187. package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +0 -462
  188. package/examples/mqttCommandBridge/platformio.ini +0 -27
  189. package/examples/mqttStatusBridge/mqttStatusBridge.ino +0 -218
  190. package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +0 -522
  191. package/examples/mqttStatusBridge/platformio.ini +0 -27
  192. package/examples/mqttTopologyTest/README.md +0 -467
  193. package/examples/mqttTopologyTest/mqttTopologyTest.ino +0 -754
  194. package/examples/mqttTopologyTest/platformio.ini +0 -27
  195. package/examples/multi_bridge/README.md +0 -346
  196. package/examples/multi_bridge/primary_bridge.ino +0 -108
  197. package/examples/multi_bridge/regular_node.ino +0 -141
  198. package/examples/multi_bridge/secondary_bridge.ino +0 -123
  199. package/examples/ntpTimeSyncBridge/alteriom_sensor_package.hpp +0 -1383
  200. package/examples/ntpTimeSyncBridge/ntpTimeSyncBridge.ino +0 -86
  201. package/examples/ntpTimeSyncNode/alteriom_sensor_package.hpp +0 -1383
  202. package/examples/ntpTimeSyncNode/ntpTimeSyncNode.ino +0 -109
  203. package/examples/queued_alarms/README.md +0 -390
  204. package/examples/queued_alarms/queued_alarms.ino +0 -265
  205. package/examples/routing_demo/README.md +0 -172
  206. package/examples/routing_demo/routing_demo.ino +0 -102
  207. package/examples/rtcIntegration/README.md +0 -294
  208. package/examples/rtcIntegration/rtcIntegration.ino +0 -210
@@ -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 (60 seconds) to allow initial bridge discovery
172
- if (millis() < 60000) {
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,8 +196,9 @@ 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
- // Small delay to randomize election start across nodes
189
- uint32_t randomDelay = random(1000, 3000);
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
  });
@@ -384,6 +396,150 @@ class Mesh : public painlessmesh::Mesh<Connection> {
384
396
  return success;
385
397
  }
386
398
 
399
+ /**
400
+ * Initialize mesh as a shared gateway node
401
+ *
402
+ * This method initializes all mesh nodes in AP+STA mode with router
403
+ * connectivity. Unlike initAsBridge() which creates a single bridge node,
404
+ * initAsSharedGateway() allows all nodes to connect to the router while
405
+ * maintaining mesh communication.
406
+ *
407
+ * Key features:
408
+ * - All nodes operate in AP+STA mode
409
+ * - All nodes connect to the same router
410
+ * - Mesh and router operate on the same channel for reliability
411
+ * - Automatic router reconnection on disconnect
412
+ * - Channel synchronization between mesh and router
413
+ *
414
+ * @param meshPrefix The name prefix for the mesh network
415
+ * @param meshPassword WiFi password for the mesh network
416
+ * @param routerSSID SSID of the router to connect to
417
+ * @param routerPassword Password for the router
418
+ * @param userScheduler Task scheduler for mesh operations
419
+ * @param port TCP port for mesh communication (default: 5555)
420
+ * @param config SharedGatewayConfig with advanced settings (optional)
421
+ * @return true if initialization succeeded, false otherwise
422
+ */
423
+ bool initAsSharedGateway(TSTRING meshPrefix, TSTRING meshPassword,
424
+ TSTRING routerSSID, TSTRING routerPassword,
425
+ Scheduler *userScheduler, uint16_t port = 5555,
426
+ gateway::SharedGatewayConfig config = gateway::SharedGatewayConfig()) {
427
+ using namespace logger;
428
+
429
+ Log(STARTUP, "=== Shared Gateway Mode Initialization ===\n");
430
+
431
+ // Validate configuration if enabled
432
+ if (config.enabled) {
433
+ auto result = config.validate();
434
+ if (!result.valid) {
435
+ Log(ERROR, "initAsSharedGateway(): Config validation failed: %s\n",
436
+ result.errorMessage.c_str());
437
+ return false;
438
+ }
439
+ }
440
+
441
+ // Store shared gateway configuration
442
+ _sharedGatewayConfig = config;
443
+ _sharedGatewayConfig.routerSSID = routerSSID;
444
+ _sharedGatewayConfig.routerPassword = routerPassword;
445
+ _sharedGatewayConfig.enabled = true;
446
+ _sharedGatewayMode = true;
447
+
448
+ Log(STARTUP, "Step 1: Scanning for router %s to detect channel...\n", routerSSID.c_str());
449
+
450
+ // Step 1: Scan for router to detect its channel
451
+ // We need to ensure mesh and router operate on the same channel
452
+ if (WiFi.status() != WL_DISCONNECTED) WiFi.disconnect();
453
+
454
+ #if ESP_ARDUINO_VERSION_MAJOR >= 3
455
+ WiFi.setAutoReconnect(false);
456
+ Log(STARTUP, "initAsSharedGateway(): AutoReconnect disabled\n");
457
+ #else
458
+ WiFi.setAutoConnect(false);
459
+ Log(STARTUP, "initAsSharedGateway(): AutoConnect disabled\n");
460
+ #endif
461
+ WiFi.persistent(false);
462
+ WiFi.mode(WIFI_STA);
463
+
464
+ // Connect to router to detect channel
465
+ WiFi.begin(routerSSID.c_str(), routerPassword.c_str());
466
+
467
+ // Wait for connection with timeout (using constant for configurability)
468
+ int timeout = ROUTER_CONNECTION_TIMEOUT_SECONDS;
469
+ while (WiFi.status() != WL_CONNECTED && timeout > 0) {
470
+ delay(1000);
471
+ timeout--;
472
+ Log(STARTUP, ".");
473
+ }
474
+
475
+ uint8_t detectedChannel = 1; // Default fallback
476
+
477
+ if (WiFi.status() == WL_CONNECTED) {
478
+ detectedChannel = WiFi.channel();
479
+ // Validate channel is in valid range (1-14 for 2.4GHz, region-dependent)
480
+ if (detectedChannel < MIN_WIFI_CHANNEL || detectedChannel > MAX_WIFI_CHANNEL) {
481
+ Log(ERROR, "\n✗ Invalid channel detected: %d, falling back to channel 1\n", detectedChannel);
482
+ detectedChannel = 1;
483
+ } else {
484
+ Log(STARTUP, "\n✓ Router connected on channel %d\n", detectedChannel);
485
+ Log(STARTUP, "✓ Router IP: %s\n", WiFi.localIP().toString().c_str());
486
+ }
487
+ } else {
488
+ Log(ERROR, "\n✗ Failed to connect to router during channel detection\n");
489
+ Log(ERROR, "Continuing with default channel 1, will retry router connection later\n");
490
+ }
491
+
492
+ // Disconnect from router, we'll reconnect after mesh init
493
+ WiFi.disconnect();
494
+ delay(100);
495
+
496
+ Log(STARTUP, "Step 2: Initializing mesh on channel %d...\n", detectedChannel);
497
+
498
+ // Step 2: Initialize mesh on detected channel with AP+STA mode
499
+ // Set scheduler before init
500
+ this->setScheduler(userScheduler);
501
+ init(meshPrefix, meshPassword, port, WIFI_AP_STA, detectedChannel, 0, MAX_CONN);
502
+
503
+ Log(STARTUP, "Step 3: Establishing router connection in shared gateway mode...\n");
504
+
505
+ // Step 3: Establish router connection using stationManual
506
+ // Port 0 means we don't expect TCP mesh connection to the router
507
+ stationManual(routerSSID, routerPassword, 0);
508
+
509
+ // Step 4: Setup router connection monitoring and reconnection logic
510
+ initSharedGatewayMonitoring();
511
+
512
+ // Store router credentials for reconnection
513
+ setRouterCredentials(routerSSID, routerPassword);
514
+
515
+ Log(STARTUP, "=== Shared Gateway Mode Active ===\n");
516
+ Log(STARTUP, " Mesh Prefix: %s\n", meshPrefix.c_str());
517
+ Log(STARTUP, " Mesh Channel: %d (synced with router)\n", detectedChannel);
518
+ Log(STARTUP, " Router: %s\n", routerSSID.c_str());
519
+ Log(STARTUP, " Port: %d\n", port);
520
+ Log(STARTUP, " Mode: AP+STA (all nodes can connect to router)\n");
521
+
522
+ return true;
523
+ }
524
+
525
+ /**
526
+ * Check if shared gateway mode is enabled
527
+ *
528
+ * @return true if node is operating in shared gateway mode
529
+ */
530
+ bool isSharedGatewayMode() const {
531
+ return _sharedGatewayMode;
532
+ }
533
+
534
+ /**
535
+ * Get the shared gateway configuration
536
+ *
537
+ * @return const reference to the SharedGatewayConfig
538
+ */
539
+ const gateway::SharedGatewayConfig& getSharedGatewayConfig() const {
540
+ return _sharedGatewayConfig;
541
+ }
542
+
387
543
  /**
388
544
  * Connect (as a station) to a specified network and ip
389
545
  *
@@ -555,6 +711,37 @@ class Mesh : public painlessmesh::Mesh<Connection> {
555
711
  minimumBridgeRSSI = minRSSI;
556
712
  }
557
713
 
714
+ /**
715
+ * Set the startup delay before first bridge election check
716
+ *
717
+ * Allows time for mesh network formation before starting bridge elections.
718
+ * Longer delays reduce the risk of split-brain scenarios when multiple nodes
719
+ * start simultaneously, ensuring nodes discover each other before elections.
720
+ *
721
+ * @param delayMs Startup delay in milliseconds (default: 60000 = 60 seconds, min: 10000)
722
+ */
723
+ void setElectionStartupDelay(uint32_t delayMs) {
724
+ if (delayMs < 10000) delayMs = 10000; // Minimum 10 seconds
725
+ electionStartupDelayMs = delayMs;
726
+ }
727
+
728
+ /**
729
+ * Set the random delay range for bridge elections
730
+ *
731
+ * When multiple nodes detect missing bridge simultaneously, randomized delays
732
+ * prevent all nodes from starting elections at the same instant. Longer delays
733
+ * provide more time for mesh discovery and reduce split-brain risk.
734
+ *
735
+ * @param minMs Minimum random delay in milliseconds (default: 1000 = 1 second)
736
+ * @param maxMs Maximum random delay in milliseconds (default: 3000 = 3 seconds)
737
+ */
738
+ void setElectionRandomDelay(uint32_t minMs, uint32_t maxMs) {
739
+ if (minMs < 100) minMs = 100; // Minimum 100ms
740
+ if (maxMs < minMs) maxMs = minMs + 1000; // Ensure max > min
741
+ electionRandomDelayMinMs = minMs;
742
+ electionRandomDelayMaxMs = maxMs;
743
+ }
744
+
558
745
  /**
559
746
  * Set callback for when this node's bridge role changes
560
747
  *
@@ -1564,6 +1751,9 @@ class Mesh : public painlessmesh::Mesh<Connection> {
1564
1751
  TSTRING routerPassword = "";
1565
1752
  uint32_t electionTimeoutMs = 5000; // Default 5 seconds
1566
1753
  int8_t minimumBridgeRSSI = -80; // Default -80 dBm minimum for isolated elections
1754
+ uint32_t electionStartupDelayMs = 60000; // Default 60 seconds before first election check
1755
+ uint32_t electionRandomDelayMinMs = 1000; // Default min 1 second random delay
1756
+ uint32_t electionRandomDelayMaxMs = 3000; // Default max 3 seconds random delay
1567
1757
  uint32_t lastRoleChangeTime = 0;
1568
1758
  ElectionState electionState = ELECTION_IDLE;
1569
1759
  uint32_t electionDeadline = 0;
@@ -1582,6 +1772,154 @@ class Mesh : public painlessmesh::Mesh<Connection> {
1582
1772
  std::vector<uint32_t> knownBridgePeers; // List of peer bridge node IDs
1583
1773
  uint32_t selectedBridgeOverride = 0; // Manual bridge selection override
1584
1774
  size_t lastSelectedBridgeIndex = 0; // For round-robin selection
1775
+
1776
+ // Shared gateway mode state and configuration
1777
+ bool _sharedGatewayMode = false;
1778
+ gateway::SharedGatewayConfig _sharedGatewayConfig;
1779
+ std::shared_ptr<Task> _sharedGatewayMonitorTask;
1780
+ uint32_t _lastRouterReconnectAttempt = 0;
1781
+ uint8_t _routerReconnectAttempts = 0;
1782
+ static const uint8_t MAX_ROUTER_RECONNECT_ATTEMPTS = 10;
1783
+ static const uint32_t ROUTER_RECONNECT_BASE_INTERVAL = 5000; // 5 seconds base interval
1784
+ static const uint32_t ROUTER_RECONNECT_MAX_INTERVAL = 300000; // 5 minutes max interval
1785
+ static const int ROUTER_CONNECTION_TIMEOUT_SECONDS = 30; // Router connection timeout
1786
+ static const uint8_t MIN_WIFI_CHANNEL = 1;
1787
+ static const uint8_t MAX_WIFI_CHANNEL = 14; // Support channels 1-14 for regions that allow it
1788
+
1789
+ /**
1790
+ * Initialize shared gateway monitoring
1791
+ *
1792
+ * Sets up periodic monitoring of router connection and automatic
1793
+ * reconnection logic for shared gateway mode.
1794
+ */
1795
+ void initSharedGatewayMonitoring() {
1796
+ using namespace logger;
1797
+
1798
+ if (!_sharedGatewayMode) {
1799
+ return;
1800
+ }
1801
+
1802
+ Log(STARTUP, "initSharedGatewayMonitoring(): Setting up router connection monitoring\n");
1803
+
1804
+ // Add callback for router disconnection in shared gateway mode
1805
+ this->droppedConnectionCallbacks.push_back(
1806
+ [this](uint32_t nodeId, bool station) {
1807
+ if (station && _sharedGatewayMode) {
1808
+ Log(CONNECTION, "Router disconnected in shared gateway mode, scheduling reconnection\n");
1809
+ scheduleRouterReconnect();
1810
+ }
1811
+ });
1812
+
1813
+ // Create periodic monitoring task
1814
+ _sharedGatewayMonitorTask = this->addTask(
1815
+ _sharedGatewayConfig.internetCheckInterval,
1816
+ TASK_FOREVER,
1817
+ [this]() {
1818
+ monitorRouterConnection();
1819
+ });
1820
+
1821
+ Log(STARTUP, "Router connection monitoring enabled (interval: %u ms)\n",
1822
+ _sharedGatewayConfig.internetCheckInterval);
1823
+ }
1824
+
1825
+ /**
1826
+ * Monitor router connection in shared gateway mode
1827
+ *
1828
+ * Checks router connectivity and triggers reconnection if needed.
1829
+ */
1830
+ void monitorRouterConnection() {
1831
+ using namespace logger;
1832
+
1833
+ if (!_sharedGatewayMode) {
1834
+ return;
1835
+ }
1836
+
1837
+ bool isConnected = (WiFi.status() == WL_CONNECTED) &&
1838
+ (WiFi.localIP() != IPAddress(0, 0, 0, 0));
1839
+
1840
+ if (!isConnected) {
1841
+ Log(CONNECTION, "monitorRouterConnection(): Router connection lost, triggering reconnect\n");
1842
+ scheduleRouterReconnect();
1843
+ } else {
1844
+ // Connection is healthy, reset reconnect attempts
1845
+ _routerReconnectAttempts = 0;
1846
+
1847
+ // Log periodic status
1848
+ Log(GENERAL, "monitorRouterConnection(): Router connected (RSSI: %d dBm, IP: %s)\n",
1849
+ WiFi.RSSI(), WiFi.localIP().toString().c_str());
1850
+ }
1851
+ }
1852
+
1853
+ /**
1854
+ * Schedule router reconnection with exponential backoff
1855
+ */
1856
+ void scheduleRouterReconnect() {
1857
+ using namespace logger;
1858
+
1859
+ if (!_sharedGatewayMode) {
1860
+ return;
1861
+ }
1862
+
1863
+ // Don't schedule if already connected
1864
+ if (WiFi.status() == WL_CONNECTED) {
1865
+ return;
1866
+ }
1867
+
1868
+ // Limit reconnection attempts
1869
+ if (_routerReconnectAttempts >= MAX_ROUTER_RECONNECT_ATTEMPTS) {
1870
+ Log(ERROR, "scheduleRouterReconnect(): Max reconnection attempts reached (%d)\n",
1871
+ MAX_ROUTER_RECONNECT_ATTEMPTS);
1872
+ Log(ERROR, "Router reconnection suspended. Manual intervention may be required.\n");
1873
+ return;
1874
+ }
1875
+
1876
+ // Calculate delay with exponential backoff, preventing overflow
1877
+ // Limit shift amount to prevent overflow (5000 * 2^6 = 320000 is safe)
1878
+ uint8_t shiftAmount = (_routerReconnectAttempts > 6) ? 6 : _routerReconnectAttempts;
1879
+ uint32_t delay = ROUTER_RECONNECT_BASE_INTERVAL * (1UL << shiftAmount);
1880
+ if (delay > ROUTER_RECONNECT_MAX_INTERVAL) delay = ROUTER_RECONNECT_MAX_INTERVAL;
1881
+
1882
+ // Don't reconnect too frequently
1883
+ uint32_t now = millis();
1884
+ if (now - _lastRouterReconnectAttempt < delay) {
1885
+ return;
1886
+ }
1887
+
1888
+ _routerReconnectAttempts++;
1889
+ _lastRouterReconnectAttempt = now;
1890
+
1891
+ Log(CONNECTION, "scheduleRouterReconnect(): Attempting reconnection (attempt %d/%d, delay %u ms)\n",
1892
+ _routerReconnectAttempts, MAX_ROUTER_RECONNECT_ATTEMPTS, delay);
1893
+
1894
+ // Schedule reconnection
1895
+ this->addTask(delay, TASK_ONCE, [this]() {
1896
+ attemptRouterReconnect();
1897
+ });
1898
+ }
1899
+
1900
+ /**
1901
+ * Attempt to reconnect to the router
1902
+ */
1903
+ void attemptRouterReconnect() {
1904
+ using namespace logger;
1905
+
1906
+ if (!_sharedGatewayMode) {
1907
+ return;
1908
+ }
1909
+
1910
+ // Check if already connected
1911
+ if (WiFi.status() == WL_CONNECTED) {
1912
+ Log(CONNECTION, "attemptRouterReconnect(): Already connected to router\n");
1913
+ _routerReconnectAttempts = 0;
1914
+ return;
1915
+ }
1916
+
1917
+ Log(CONNECTION, "attemptRouterReconnect(): Reconnecting to router %s...\n",
1918
+ _sharedGatewayConfig.routerSSID.c_str());
1919
+
1920
+ // Use stationManual to reconnect (port 0 means no TCP mesh connection to router)
1921
+ stationManual(_sharedGatewayConfig.routerSSID, _sharedGatewayConfig.routerPassword, 0);
1922
+ }
1585
1923
  };
1586
1924
  } // namespace wifi
1587
1925
  }; // namespace painlessmesh