@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
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,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
|
-
//
|
|
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
|
});
|
|
@@ -260,7 +272,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
260
272
|
* @param baseScheduler Task scheduler for mesh operations
|
|
261
273
|
* @param port TCP port for mesh communication (default: 5555)
|
|
262
274
|
*/
|
|
263
|
-
|
|
275
|
+
bool initAsBridge(TSTRING meshSSID, TSTRING meshPassword,
|
|
264
276
|
TSTRING routerSSID, TSTRING routerPassword,
|
|
265
277
|
Scheduler *baseScheduler, uint16_t port = 5555) {
|
|
266
278
|
using namespace logger;
|
|
@@ -298,14 +310,17 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
298
310
|
detectedChannel = WiFi.channel();
|
|
299
311
|
// Validate channel is in valid range (1-13 for 2.4GHz)
|
|
300
312
|
if (detectedChannel < 1 || detectedChannel > 13) {
|
|
301
|
-
Log(ERROR, "\n✗ Invalid channel detected: %d,
|
|
313
|
+
Log(ERROR, "\n✗ Invalid channel detected: %d, falling back to channel 1\n", detectedChannel);
|
|
302
314
|
detectedChannel = 1;
|
|
303
315
|
} else {
|
|
304
316
|
Log(STARTUP, "\n✓ Router connected on channel %d\n", detectedChannel);
|
|
305
317
|
Log(STARTUP, "✓ Router IP: %s\n", WiFi.localIP().toString().c_str());
|
|
306
318
|
}
|
|
307
319
|
} else {
|
|
308
|
-
Log(ERROR, "\n✗ Failed to connect to router
|
|
320
|
+
Log(ERROR, "\n✗ Failed to connect to router\n");
|
|
321
|
+
Log(ERROR, "Cannot become bridge without router connection\n");
|
|
322
|
+
Log(ERROR, "Bridge initialization aborted - remaining as regular node\n");
|
|
323
|
+
return false;
|
|
309
324
|
}
|
|
310
325
|
|
|
311
326
|
Log(STARTUP, "Step 2: Initializing mesh on channel %d...\n", detectedChannel);
|
|
@@ -331,6 +346,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
331
346
|
Log(STARTUP, " Mesh Channel: %d (matches router)\n", detectedChannel);
|
|
332
347
|
Log(STARTUP, " Router: %s\n", routerSSID.c_str());
|
|
333
348
|
Log(STARTUP, " Port: %d\n", port);
|
|
349
|
+
return true;
|
|
334
350
|
}
|
|
335
351
|
|
|
336
352
|
/**
|
|
@@ -347,7 +363,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
347
363
|
* @param port TCP port for mesh communication (default: 5555)
|
|
348
364
|
* @param priority Bridge priority: 10=highest (primary), 5=medium (secondary), 1=lowest (default: 5)
|
|
349
365
|
*/
|
|
350
|
-
|
|
366
|
+
bool initAsBridge(TSTRING meshSSID, TSTRING meshPassword,
|
|
351
367
|
TSTRING routerSSID, TSTRING routerPassword,
|
|
352
368
|
Scheduler *baseScheduler, uint16_t port, uint8_t priority) {
|
|
353
369
|
using namespace logger;
|
|
@@ -370,12 +386,158 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
370
386
|
priority, bridgeRole.c_str());
|
|
371
387
|
|
|
372
388
|
// Call the base initAsBridge method
|
|
373
|
-
initAsBridge(meshSSID, meshPassword, routerSSID, routerPassword, baseScheduler, port);
|
|
389
|
+
bool success = initAsBridge(meshSSID, meshPassword, routerSSID, routerPassword, baseScheduler, port);
|
|
374
390
|
|
|
375
|
-
// Setup multi-bridge coordination if enabled
|
|
376
|
-
if (multiBridgeEnabled) {
|
|
391
|
+
// Setup multi-bridge coordination if enabled and bridge init succeeded
|
|
392
|
+
if (success && multiBridgeEnabled) {
|
|
377
393
|
initBridgeCoordination();
|
|
378
394
|
}
|
|
395
|
+
|
|
396
|
+
return success;
|
|
397
|
+
}
|
|
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;
|
|
379
541
|
}
|
|
380
542
|
|
|
381
543
|
/**
|
|
@@ -549,6 +711,37 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
549
711
|
minimumBridgeRSSI = minRSSI;
|
|
550
712
|
}
|
|
551
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
|
+
|
|
552
745
|
/**
|
|
553
746
|
* Set callback for when this node's bridge role changes
|
|
554
747
|
*
|
|
@@ -1270,12 +1463,35 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1270
1463
|
delay(1000);
|
|
1271
1464
|
Log(STARTUP, "✓ Takeover announcement sent on channel %d\n", _meshChannel);
|
|
1272
1465
|
|
|
1466
|
+
// Save current mesh configuration to restore if bridge init fails
|
|
1467
|
+
uint8_t savedChannel = _meshChannel;
|
|
1468
|
+
|
|
1273
1469
|
// Now reconfigure as bridge (this will switch to router's channel)
|
|
1274
1470
|
this->stop();
|
|
1275
1471
|
delay(1000);
|
|
1276
1472
|
|
|
1277
|
-
this->initAsBridge(_meshSSID, _meshPassword, routerSSID, routerPassword,
|
|
1278
|
-
|
|
1473
|
+
bool bridgeInitSuccess = this->initAsBridge(_meshSSID, _meshPassword, routerSSID, routerPassword,
|
|
1474
|
+
mScheduler, _meshPort);
|
|
1475
|
+
|
|
1476
|
+
if (!bridgeInitSuccess) {
|
|
1477
|
+
Log(ERROR, "✗ Bridge promotion failed - router unreachable\n");
|
|
1478
|
+
Log(ERROR, "Reverting to regular node on channel %d\n", savedChannel);
|
|
1479
|
+
|
|
1480
|
+
// Re-initialize as regular node on the original channel
|
|
1481
|
+
this->init(_meshSSID, _meshPassword, mScheduler, _meshPort, WIFI_AP_STA,
|
|
1482
|
+
savedChannel, _meshHidden, MAX_CONN);
|
|
1483
|
+
|
|
1484
|
+
// Reset election state and clear candidates (consistent with normal election completion)
|
|
1485
|
+
electionState = ELECTION_IDLE;
|
|
1486
|
+
electionCandidates.clear();
|
|
1487
|
+
|
|
1488
|
+
// Notify via callback
|
|
1489
|
+
if (bridgeRoleChangedCallback) {
|
|
1490
|
+
bridgeRoleChangedCallback(false, "Bridge promotion failed - router unreachable");
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1279
1495
|
|
|
1280
1496
|
lastRoleChangeTime = millis();
|
|
1281
1497
|
|
|
@@ -1535,6 +1751,9 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1535
1751
|
TSTRING routerPassword = "";
|
|
1536
1752
|
uint32_t electionTimeoutMs = 5000; // Default 5 seconds
|
|
1537
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
|
|
1538
1757
|
uint32_t lastRoleChangeTime = 0;
|
|
1539
1758
|
ElectionState electionState = ELECTION_IDLE;
|
|
1540
1759
|
uint32_t electionDeadline = 0;
|
|
@@ -1553,6 +1772,154 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1553
1772
|
std::vector<uint32_t> knownBridgePeers; // List of peer bridge node IDs
|
|
1554
1773
|
uint32_t selectedBridgeOverride = 0; // Manual bridge selection override
|
|
1555
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
|
+
}
|
|
1556
1923
|
};
|
|
1557
1924
|
} // namespace wifi
|
|
1558
1925
|
}; // namespace painlessmesh
|