@alteriom/painlessmesh 1.8.4 → 1.8.6

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/CHANGELOG.md CHANGED
@@ -7,6 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.8.6] - 2025-11-12
11
+
12
+ ### Fixed
13
+
14
+ - **Bridge Failover Auto-Election (Issue #117)** - Bridge election now triggers when no initial bridge exists
15
+ - Added periodic monitoring task (30s interval) that detects absence of healthy bridge
16
+ - Activates after 60s startup grace period to allow network stabilization
17
+ - Randomized election delay (1-3s) prevents thundering herd problem
18
+ - Respects existing safeguards: election state, 60s cooldown, router visibility
19
+ - **Before**: No initial bridge → no election → mesh stays bridgeless indefinitely
20
+ - **After**: No initial bridge → 60s startup → monitoring detects absence → election triggered → best RSSI node becomes bridge
21
+ - Fully backward compatible: pre-designated bridge mode continues to work as before
22
+ - Core fix in `src/arduino/wifi.hpp`
23
+ - Resolves @woodlist's "Bridge_failover example does not work" issue
24
+
25
+ ### Changed
26
+
27
+ - **bridge_failover Example Documentation** - Clarified two deployment modes
28
+ - Auto-Election Mode: All nodes regular (`INITIAL_BRIDGE=false`), RSSI-based election after 60s
29
+ - Pre-Designated Mode: Traditional single initial bridge setup
30
+ - Updated README.md with comprehensive auto-election documentation
31
+ - Enhanced header comments in bridge_failover.ino
32
+
33
+ ### Housekeeping
34
+
35
+ - Synchronized package-lock.json version to 1.8.5
36
+
37
+ ## [1.8.5] - 2025-11-12
38
+
39
+ ### Fixed
40
+
41
+ - **ntpTimeSyncBridge and ntpTimeSyncNode Compilation (Issue #108)** - Arduino IDE compilation errors fixed
42
+ - Fixed include path in ntpTimeSyncBridge.ino from `"examples/alteriom/alteriom_sensor_package.hpp"` to `"alteriom_sensor_package.hpp"`
43
+ - Fixed include path in ntpTimeSyncNode.ino from `"examples/alteriom/alteriom_sensor_package.hpp"` to `"alteriom_sensor_package.hpp"`
44
+ - Arduino IDE compiles sketches with sketch directory as working directory, requiring local header files
45
+ - Resolves @woodlist's compilation error: "No such file or directory"
46
+
47
+ - **Arduino String Method Compatibility** - Fixed incompatible method call in wifi.hpp
48
+ - Changed `stationSSID.empty()` to `stationSSID.isEmpty()` in src/arduino/wifi.hpp line 171
49
+ - Arduino's String class uses `isEmpty()` method instead of STL's `empty()`
50
+ - Fixes CI build failures for ESP32/ESP8266 examples
51
+ - Related to station credentials feature added in #113
52
+
53
+ ### Documentation
54
+
55
+ - **Example Sketches** - Updated NTP time synchronization examples
56
+ - ntpTimeSyncBridge now compiles correctly in Arduino IDE
57
+ - ntpTimeSyncNode now compiles correctly in Arduino IDE
58
+ - Examples: `examples/ntpTimeSyncBridge/`, `examples/ntpTimeSyncNode/`
59
+
10
60
  ## [1.8.4] - 2025-11-12
11
61
 
12
62
  ### Fixed
@@ -0,0 +1 @@
1
+ Created
@@ -0,0 +1,182 @@
1
+ # Station Credentials Design Rationale
2
+
3
+ ## Question
4
+
5
+ Why does `mesh.init()` require a separate `mesh.stationManual()` call to connect to a router, instead of accepting station credentials directly?
6
+
7
+ ## Answer: Multiple Valid Approaches
8
+
9
+ The library now supports **three approaches** for connecting a bridge node to a router, each with different use cases:
10
+
11
+ ### 1. Separate stationManual() Call (Original Design)
12
+
13
+ ```cpp
14
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 6);
15
+ mesh.stationManual(STATION_SSID, STATION_PASSWORD);
16
+ mesh.setRoot(true);
17
+ mesh.setContainsRoot(true);
18
+ ```
19
+
20
+ **When to use:**
21
+ - Maximum flexibility - can change router connection without reinitializing mesh
22
+ - Dynamic router selection at runtime
23
+ - Need to call `setHostname()` or other WiFi configuration between init and connection
24
+ - Following existing examples or legacy code
25
+
26
+ **Advantages:**
27
+ - Separation of concerns: mesh setup vs router connection
28
+ - Can reconnect to different routers without mesh reinitialization
29
+ - More control over connection timing and error handling
30
+
31
+ ### 2. Optional Parameters in init() (New Convenience Feature)
32
+
33
+ ```cpp
34
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT,
35
+ WIFI_AP_STA, 6, 0, MAX_CONN,
36
+ STATION_SSID, STATION_PASSWORD); // Optional parameters
37
+ mesh.setRoot(true);
38
+ mesh.setContainsRoot(true);
39
+ ```
40
+
41
+ **When to use:**
42
+ - Simple bridge setup with known credentials
43
+ - Static configuration (credentials won't change)
44
+ - Want slightly more concise code
45
+ - Don't need hostname or other WiFi customization
46
+
47
+ **Advantages:**
48
+ - One line instead of two for basic bridge setup
49
+ - All connection parameters in one place
50
+ - Still maintains full flexibility of other options
51
+
52
+ ### 3. initAsBridge() Method (Recommended for New Projects)
53
+
54
+ ```cpp
55
+ mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD,
56
+ STATION_SSID, STATION_PASSWORD,
57
+ &userScheduler, MESH_PORT);
58
+ ```
59
+
60
+ **When to use:**
61
+ - New bridge implementations (recommended)
62
+ - Want automatic channel detection
63
+ - Need simplest possible setup
64
+ - Following modern best practices
65
+
66
+ **Advantages:**
67
+ - **Automatic channel detection** - no manual channel configuration needed
68
+ - Automatically sets node as root
69
+ - Maintains router connection through channel switches
70
+ - Broadcasts bridge status (Type 610) automatically
71
+ - Comprehensive initialization in one call
72
+
73
+ ## Design Rationale for Original Separation
74
+
75
+ The original design separated `init()` and `stationManual()` for good architectural reasons:
76
+
77
+ ### 1. Separation of Concerns
78
+
79
+ **Mesh Setup (`init()`):**
80
+ - Creates mesh network (AP mode)
81
+ - Sets up mesh routing and protocol
82
+ - Configures mesh-specific parameters
83
+ - Lifetime: typically never changes
84
+
85
+ **Router Connection (`stationManual()`):**
86
+ - Connects to external WiFi (STA mode)
87
+ - Different lifecycle - may connect/disconnect/change
88
+ - Network-specific credentials and settings
89
+ - Can be reconfigured at runtime
90
+
91
+ This separation allows clean code organization and different lifecycles for each concern.
92
+
93
+ ### 2. Not All Nodes Need Router Connection
94
+
95
+ In a typical mesh network:
96
+ - **1 bridge node**: Needs router connection (AP+STA mode)
97
+ - **N regular nodes**: Mesh only (AP mode, or AP+STA for mesh connections)
98
+
99
+ Regular nodes use:
100
+ ```cpp
101
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA);
102
+ // No stationManual() call - not a bridge
103
+ ```
104
+
105
+ If `init()` always required station credentials, it would be confusing for regular nodes.
106
+
107
+ ### 3. Dynamic Router Switching
108
+
109
+ Some advanced use cases require changing router connections at runtime:
110
+
111
+ ```cpp
112
+ // Initial setup
113
+ mesh.init(...);
114
+ mesh.stationManual("Router1", "pass1");
115
+
116
+ // Later, switch to different router
117
+ mesh.stationManual("Router2", "pass2");
118
+
119
+ // Or respond to failover
120
+ void onRouterDisconnect() {
121
+ mesh.stationManual(backupSSID, backupPassword);
122
+ }
123
+ ```
124
+
125
+ With station credentials baked into `init()`, this flexibility would be lost.
126
+
127
+ ### 4. Additional WiFi Configuration
128
+
129
+ Many users need to configure WiFi settings between initialization and connection:
130
+
131
+ ```cpp
132
+ mesh.init(...);
133
+ mesh.setHostname("MESH_BRIDGE"); // Must be before stationManual()
134
+ mesh.stationManual(...);
135
+ ```
136
+
137
+ The separation provides a natural place for these configurations.
138
+
139
+ ### 5. Error Handling and Retry Logic
140
+
141
+ Separating the calls allows better error handling:
142
+
143
+ ```cpp
144
+ mesh.init(...); // This typically doesn't fail
145
+
146
+ // Retry router connection with backoff
147
+ for (int retry = 0; retry < 3; retry++) {
148
+ if (tryStationConnect()) break;
149
+ delay(1000 * (retry + 1));
150
+ }
151
+ ```
152
+
153
+ ## Comparison Table
154
+
155
+ | Approach | Setup Complexity | Flexibility | Channel Detection | Best For |
156
+ |----------|-----------------|-------------|-------------------|----------|
157
+ | **stationManual()** | Medium | Highest | Manual | Dynamic configs, legacy code |
158
+ | **init() params** | Low-Medium | High | Manual | Simple static bridges |
159
+ | **initAsBridge()** | Lowest | Medium | Automatic | New projects, recommended |
160
+
161
+ ## Recommendation
162
+
163
+ **For new projects:** Use `initAsBridge()` - it's the modern, recommended approach with automatic channel detection.
164
+
165
+ **For existing projects:** The original `init()` + `stationManual()` pattern remains fully supported and appropriate.
166
+
167
+ **For simple bridges:** The new optional parameters in `init()` provide a middle ground with good flexibility.
168
+
169
+ All three approaches are valid and will continue to be supported. Choose based on your specific needs.
170
+
171
+ ## Implementation Note
172
+
173
+ When station credentials are passed to `init()`, the implementation internally calls `stationManual()` after mesh initialization. This maintains consistency and code reuse while providing convenience.
174
+
175
+ ```cpp
176
+ // Inside init() implementation
177
+ if (!stationSSID.empty() && (connectMode & WIFI_STA)) {
178
+ this->stationManual(stationSSID, stationPassword);
179
+ }
180
+ ```
181
+
182
+ This design ensures all three approaches use the same underlying connection logic.
@@ -30,10 +30,20 @@ Regular nodes track these broadcasts and detect failures when:
30
30
 
31
31
  ### 2. Election Trigger
32
32
 
33
- When the primary bridge fails:
33
+ An election is triggered when:
34
+
35
+ **Scenario 1: No Bridge Exists (Auto-Election Mode)**
36
+ - After 60-second startup period
37
+ - Periodic monitoring (every 30s) detects no healthy bridge
34
38
  - Nodes with router credentials configured start an election
35
39
  - Nodes without credentials remain passive
36
40
 
41
+ **Scenario 2: Bridge Failure**
42
+ - Primary bridge fails or loses Internet connectivity
43
+ - No status received within 60 seconds (configurable timeout)
44
+ - Bridge reports `internetConnected: false`
45
+ - Nodes detect failure and start election
46
+
37
47
  ### 3. Election Protocol
38
48
 
39
49
  **Step 1: Candidacy Broadcast (Type 611)**
@@ -97,34 +107,77 @@ To prevent oscillation:
97
107
 
98
108
  ## Setup Instructions
99
109
 
100
- ### 1. Flash Initial Bridge Node
110
+ You can choose between two deployment modes:
111
+
112
+ ### Option A: Auto-Election Mode (Recommended)
113
+
114
+ **Best for**: Equal peers where any node can become the bridge based on signal strength.
115
+
116
+ 1. Keep `INITIAL_BRIDGE = false` on **ALL nodes**
117
+ 2. Configure mesh credentials (MESH_PREFIX, MESH_PASSWORD)
118
+ 3. Configure router credentials (ROUTER_SSID, ROUTER_PASSWORD)
119
+ 4. Flash the same sketch to all ESP32/ESP8266 devices
120
+ 5. Power on all nodes simultaneously
121
+
122
+ **What happens**:
123
+ - Nodes start as regular mesh nodes
124
+ - After 60-second startup period, automatic monitoring begins
125
+ - If no bridge exists, nodes trigger an election
126
+ - Node with best router RSSI wins and becomes bridge
127
+ - Other nodes remain regular with failover capability
128
+
129
+ **Advantages**:
130
+ - Simpler setup - no need to designate a specific node
131
+ - True dynamic failover - any node can become bridge
132
+ - Best bridge selected based on signal strength
133
+
134
+ ### Option B: Pre-Designated Bridge Mode
135
+
136
+ **Best for**: When you want a specific node to start as the bridge.
137
+
138
+ **1. Flash Initial Bridge Node**
101
139
 
102
140
  1. Set `INITIAL_BRIDGE` to `true`
103
141
  2. Configure router credentials
104
142
  3. Flash to one ESP32/ESP8266
105
143
  4. This node will connect to router and start as bridge
106
144
 
107
- ### 2. Flash Regular Nodes
145
+ **2. Flash Regular Nodes**
108
146
 
109
147
  1. Set `INITIAL_BRIDGE` to `false`
110
148
  2. Configure same mesh and router credentials
111
149
  3. Flash to other ESP32/ESP8266 devices
112
150
  4. These nodes can become bridges via election
113
151
 
114
- ### 3. Test Failover
152
+ **Advantages**:
153
+ - Immediate bridge availability (no 60s wait)
154
+ - Predictable initial bridge selection
155
+ - Good for nodes with fixed locations
156
+
157
+ ### Test Failover Scenarios
158
+
159
+ **Scenario 1: Auto-Election (No Initial Bridge)**
160
+ 1. Flash all nodes with `INITIAL_BRIDGE = false`
161
+ 2. Power on all nodes
162
+ 3. Wait 60 seconds for startup period
163
+ 4. Automatic monitoring detects no bridge
164
+ 5. Election starts automatically within 30 seconds
165
+ 6. Node with best router signal becomes bridge
166
+ 7. Monitor serial output to see election process
115
167
 
116
- **Scenario 1: Bridge Goes Offline**
117
- 1. Power off the initial bridge node
168
+ **Scenario 2: Bridge Goes Offline**
169
+ 1. Power off the current bridge node (initial or elected)
118
170
  2. After 60 seconds, regular nodes detect failure
119
171
  3. Election starts automatically
120
172
  4. Node with best router signal becomes new bridge
121
173
  5. Monitor serial output to see election process
122
174
 
123
- **Scenario 2: Bridge Loses Internet**
124
- 1. Disconnect router from Internet
175
+ **Scenario 3: Bridge Loses Internet**
176
+ 1. Disconnect router from Internet (or block bridge node's Internet)
125
177
  2. Bridge reports `internetConnected: false`
126
- 3. Election may start (nodes decide if failover needed)
127
- 4. New bridge elected if necessary
178
+ 3. Nodes detect loss of Internet via status broadcasts
179
+ 4. Election starts to find a node with working Internet
180
+ 5. New bridge elected if another node has Internet access
128
181
 
129
182
  ## Serial Output
130
183
 
@@ -9,14 +9,26 @@
9
9
  // - ESP32 or ESP8266
10
10
  // - WiFi router with Internet connection
11
11
  //
12
- // Setup:
12
+ // Setup Options:
13
+ //
14
+ // OPTION A - Auto-Election Mode (Recommended):
15
+ // 1. Configure your mesh credentials (MESH_PREFIX, MESH_PASSWORD)
16
+ // 2. Configure your router credentials (ROUTER_SSID, ROUTER_PASSWORD)
17
+ // 3. Keep INITIAL_BRIDGE = false on ALL nodes
18
+ // 4. Flash multiple nodes with this sketch
19
+ // 5. After startup (~60 seconds), nodes will automatically elect a bridge
20
+ // based on best router signal strength (RSSI)
21
+ //
22
+ // OPTION B - Pre-Designated Bridge Mode:
13
23
  // 1. Configure your mesh credentials (MESH_PREFIX, MESH_PASSWORD)
14
24
  // 2. Configure your router credentials (ROUTER_SSID, ROUTER_PASSWORD)
15
- // 3. Flash multiple nodes with this sketch
16
- // 4. Designate one as the initial bridge by calling mesh.initAsBridge()
17
- // 5. Other nodes will automatically participate in elections if bridge fails
25
+ // 3. Set INITIAL_BRIDGE = true on ONE node (your designated bridge)
26
+ // 4. Keep INITIAL_BRIDGE = false on all other nodes
27
+ // 5. Flash the nodes - designated bridge starts immediately
28
+ // 6. If designated bridge fails, others will hold election
18
29
  //
19
30
  // Features Demonstrated:
31
+ // - Automatic bridge election when no bridge exists
20
32
  // - Automatic bridge failure detection via heartbeats
21
33
  // - RSSI-based election protocol
22
34
  // - Deterministic winner selection with tiebreakers
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  #include "painlessMesh.h"
15
- #include "examples/alteriom/alteriom_sensor_package.hpp"
15
+ #include "alteriom_sensor_package.hpp"
16
16
 
17
17
  // Mesh configuration
18
18
  #define MESH_PREFIX "AlteriomMesh"
@@ -12,7 +12,7 @@
12
12
  */
13
13
 
14
14
  #include "painlessMesh.h"
15
- #include "examples/alteriom/alteriom_sensor_package.hpp"
15
+ #include "alteriom_sensor_package.hpp"
16
16
 
17
17
  // Mesh configuration
18
18
  #define MESH_PREFIX "AlteriomMesh"
package/library.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/Alteriom/painlessMesh"
8
8
  },
9
- "version": "1.8.4",
9
+ "version": "1.8.6",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
1
  name=Alteriom PainlessMesh
2
- version=1.8.4
2
+ version=1.8.6
3
3
  author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
4
4
  maintainer=Alteriom
5
5
  sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alteriom/painlessmesh",
3
- "version": "1.8.4",
3
+ "version": "1.8.6",
4
4
  "description": "painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices. This Alteriom fork includes additional packages for sensor data (SensorPackage), device commands (CommandPackage), and status monitoring (StatusPackage). It handles routing and network management automatically, so you can focus on your application. The library uses JSON-based messaging and syncs time across all nodes, making it ideal for coordinated behaviour like synchronized light displays or sensor networks reporting to a central node.",
5
5
  "keywords": [
6
6
  "arduino",
@@ -44,7 +44,8 @@ class Mesh : public painlessmesh::Mesh<Connection> {
44
44
  */
45
45
  void init(TSTRING ssid, TSTRING password, uint16_t port = 5555,
46
46
  WiFiMode_t connectMode = WIFI_AP_STA, uint8_t channel = 1,
47
- uint8_t hidden = 0, uint8_t maxconn = MAX_CONN) {
47
+ uint8_t hidden = 0, uint8_t maxconn = MAX_CONN,
48
+ TSTRING stationSSID = "", TSTRING stationPassword = "") {
48
49
  using namespace logger;
49
50
  // Init random generator seed to generate delay variance
50
51
  randomSeed(millis());
@@ -154,6 +155,44 @@ class Mesh : public painlessmesh::Mesh<Connection> {
154
155
  }
155
156
  });
156
157
 
158
+ // Add periodic monitoring task to detect when no bridge exists
159
+ // This handles the case where no node was initially configured as a bridge
160
+ this->addTask(30000, TASK_FOREVER, [this]() {
161
+ // Only check if failover is enabled and we have credentials
162
+ if (!bridgeFailoverEnabled || !routerCredentialsConfigured) {
163
+ return;
164
+ }
165
+
166
+ // Don't check if we're already a bridge
167
+ if (this->isBridge()) {
168
+ return;
169
+ }
170
+
171
+ // Skip check during startup period (60 seconds) to allow initial bridge discovery
172
+ if (millis() < 60000) {
173
+ return;
174
+ }
175
+
176
+ // Check if there are any healthy bridges
177
+ bool hasHealthyBridge = false;
178
+ for (const auto& bridge : this->getBridges()) {
179
+ if (bridge.isHealthy(bridgeTimeoutMs) && bridge.internetConnected) {
180
+ hasHealthyBridge = true;
181
+ break;
182
+ }
183
+ }
184
+
185
+ // If no healthy bridge exists, trigger an election
186
+ if (!hasHealthyBridge) {
187
+ 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);
190
+ this->addTask(randomDelay, TASK_ONCE, [this]() {
191
+ this->startBridgeElection();
192
+ });
193
+ }
194
+ });
195
+
157
196
  tcpServerInit();
158
197
  eventHandleInit();
159
198
 
@@ -165,6 +204,12 @@ class Mesh : public painlessmesh::Mesh<Connection> {
165
204
  if (connectMode & WIFI_STA) {
166
205
  this->initStation();
167
206
  }
207
+
208
+ // If station credentials provided, connect to router
209
+ if (!stationSSID.isEmpty() && (connectMode & WIFI_STA)) {
210
+ Log(STARTUP, "init(): Connecting to station %s\n", stationSSID.c_str());
211
+ this->stationManual(stationSSID, stationPassword);
212
+ }
168
213
  }
169
214
 
170
215
  /** Initialize the mesh network
@@ -187,9 +232,11 @@ class Mesh : public painlessmesh::Mesh<Connection> {
187
232
  void init(TSTRING ssid, TSTRING password, Scheduler *baseScheduler,
188
233
  uint16_t port = 5555, WiFiMode_t connectMode = WIFI_AP_STA,
189
234
  uint8_t channel = 1, uint8_t hidden = 0,
190
- uint8_t maxconn = MAX_CONN) {
235
+ uint8_t maxconn = MAX_CONN,
236
+ TSTRING stationSSID = "", TSTRING stationPassword = "") {
191
237
  this->setScheduler(baseScheduler);
192
- init(ssid, password, port, connectMode, channel, hidden, maxconn);
238
+ init(ssid, password, port, connectMode, channel, hidden, maxconn,
239
+ stationSSID, stationPassword);
193
240
  }
194
241
 
195
242
  /**