@alteriom/painlessmesh 1.7.9 → 1.8.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/CHANGELOG.md +94 -2
- package/README.md +108 -1
- package/docs/BRIDGE_FAILOVER.md +512 -0
- package/docs/BRIDGE_HEALTH_MONITORING.md +293 -0
- package/docs/CREATE_MISSING_RELEASES.md +321 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.8.md +523 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.9.md +542 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +213 -0
- package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +1014 -11
- package/examples/basic/basic.ino +6 -2
- package/examples/bridge/bridge.ino +44 -23
- package/examples/bridge/bridge_health_monitoring_example.ino +188 -0
- package/examples/bridgeAwareSensorNode/alteriom_sensor_package.hpp +1227 -0
- package/examples/bridgeAwareSensorNode/bridgeAwareSensorNode.ino +343 -0
- package/examples/bridgeAwareSensorNode/platformio.ini +26 -0
- package/examples/bridge_failover/README.md +358 -0
- package/examples/bridge_failover/bridge_failover.ino +180 -0
- package/examples/bridge_failover/platformio.ini +27 -0
- package/examples/diagnosticsExample/diagnosticsExample.ino +171 -0
- package/examples/diagnosticsExample/platformio.ini +26 -0
- package/examples/ntpTimeSyncBridge/alteriom_sensor_package.hpp +1383 -0
- package/examples/ntpTimeSyncBridge/ntpTimeSyncBridge.ino +81 -0
- package/examples/ntpTimeSyncNode/alteriom_sensor_package.hpp +1383 -0
- package/examples/ntpTimeSyncNode/ntpTimeSyncNode.ino +109 -0
- package/examples/rtcIntegration/README.md +235 -0
- package/examples/rtcIntegration/rtcIntegration.ino +196 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/arduino/wifi.hpp +572 -0
- package/src/painlessMeshSTA.cpp +63 -0
- package/src/painlessMeshSTA.h +3 -0
- package/src/painlessmesh/mesh.hpp +1127 -4
- package/src/painlessmesh/rtc.hpp +203 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# Bridge Failover with RSSI-Based Election
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Bridge failover enables painlessMesh networks to automatically recover from bridge node failures by electing a new bridge based on router signal strength (RSSI). This ensures continuous Internet connectivity for critical applications like fish farm monitoring, industrial IoT, and smart building systems.
|
|
6
|
+
|
|
7
|
+
## Problem Statement
|
|
8
|
+
|
|
9
|
+
In a typical mesh network, the bridge node connecting to the Internet represents a single point of failure:
|
|
10
|
+
|
|
11
|
+
- **Bridge goes offline**: Entire mesh loses Internet access
|
|
12
|
+
- **Bridge loses Internet**: Gateway unavailable for data upload
|
|
13
|
+
- **Manual recovery**: Requires human intervention to restore connectivity
|
|
14
|
+
|
|
15
|
+
Without automatic failover, critical systems can miss alarms, lose sensor data, or fail to respond to urgent conditions.
|
|
16
|
+
|
|
17
|
+
## Solution: Distributed Bridge Election
|
|
18
|
+
|
|
19
|
+
painlessMesh implements a distributed consensus protocol that:
|
|
20
|
+
|
|
21
|
+
1. **Detects failures**: Monitors bridge heartbeats (Type 610 status broadcasts)
|
|
22
|
+
2. **Triggers elections**: Starts election when primary bridge fails
|
|
23
|
+
3. **Selects winner**: Deterministically chooses node with best router signal
|
|
24
|
+
4. **Promotes bridge**: Winner automatically becomes new bridge
|
|
25
|
+
5. **Announces takeover**: Informs mesh of new bridge
|
|
26
|
+
|
|
27
|
+
## Architecture
|
|
28
|
+
|
|
29
|
+
### Message Types
|
|
30
|
+
|
|
31
|
+
#### Type 610: BRIDGE_STATUS (Existing)
|
|
32
|
+
Bridge nodes broadcast their status every 30 seconds:
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"type": 610,
|
|
36
|
+
"from": 1234567890,
|
|
37
|
+
"routing": 2,
|
|
38
|
+
"internetConnected": true,
|
|
39
|
+
"routerRSSI": -42,
|
|
40
|
+
"routerChannel": 6,
|
|
41
|
+
"uptime": 3600000,
|
|
42
|
+
"gatewayIP": "192.168.1.1",
|
|
43
|
+
"timestamp": 1609459200
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Type 611: BRIDGE_ELECTION (New)
|
|
48
|
+
Candidates broadcast their router signal strength:
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"type": 611,
|
|
52
|
+
"from": 2886734890,
|
|
53
|
+
"routing": 2,
|
|
54
|
+
"routerRSSI": -35,
|
|
55
|
+
"uptime": 3600000,
|
|
56
|
+
"freeMemory": 150000,
|
|
57
|
+
"timestamp": 1609459300,
|
|
58
|
+
"routerSSID": "MyRouter"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Type 612: BRIDGE_TAKEOVER (New)
|
|
63
|
+
Winner announces bridge role assumption:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"type": 612,
|
|
67
|
+
"from": 2886734890,
|
|
68
|
+
"routing": 2,
|
|
69
|
+
"previousBridge": 1234567890,
|
|
70
|
+
"reason": "Election winner - best router signal",
|
|
71
|
+
"routerRSSI": -35,
|
|
72
|
+
"timestamp": 1609459400
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Election Protocol
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
┌──────────────────────────────────────────────────────┐
|
|
80
|
+
│ NORMAL OPERATION │
|
|
81
|
+
│ Bridge broadcasts status every 30s (Type 610) │
|
|
82
|
+
└──────────────────────────────────────────────────────┘
|
|
83
|
+
↓
|
|
84
|
+
Bridge fails
|
|
85
|
+
(60s timeout)
|
|
86
|
+
↓
|
|
87
|
+
┌──────────────────────────────────────────────────────┐
|
|
88
|
+
│ ELECTION PHASE (5 seconds) │
|
|
89
|
+
│ 1. Each node scans for router RSSI │
|
|
90
|
+
│ 2. Broadcasts candidacy (Type 611) │
|
|
91
|
+
│ 3. Collects all candidates │
|
|
92
|
+
└──────────────────────────────────────────────────────┘
|
|
93
|
+
↓
|
|
94
|
+
┌──────────────────────────────────────────────────────┐
|
|
95
|
+
│ EVALUATION PHASE (Instant) │
|
|
96
|
+
│ All nodes independently evaluate candidates using: │
|
|
97
|
+
│ 1. Best RSSI wins │
|
|
98
|
+
│ 2. Tiebreaker: Highest uptime │
|
|
99
|
+
│ 3. Tiebreaker: Most free memory │
|
|
100
|
+
│ 4. Tiebreaker: Lowest node ID │
|
|
101
|
+
└──────────────────────────────────────────────────────┘
|
|
102
|
+
↓
|
|
103
|
+
┌──────────────────────────────────────────────────────┐
|
|
104
|
+
│ PROMOTION PHASE (5 seconds) │
|
|
105
|
+
│ Winner: │
|
|
106
|
+
│ 1. Calls initAsBridge() │
|
|
107
|
+
│ 2. Connects to router │
|
|
108
|
+
│ 3. Broadcasts takeover (Type 612) │
|
|
109
|
+
└──────────────────────────────────────────────────────┘
|
|
110
|
+
↓
|
|
111
|
+
┌──────────────────────────────────────────────────────┐
|
|
112
|
+
│ NEW NORMAL OPERATION │
|
|
113
|
+
│ New bridge broadcasts status (Type 610) │
|
|
114
|
+
└──────────────────────────────────────────────────────┘
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Winner Selection Algorithm
|
|
118
|
+
|
|
119
|
+
All nodes execute identical deterministic evaluation:
|
|
120
|
+
|
|
121
|
+
```cpp
|
|
122
|
+
BridgeCandidate* winner = nullptr;
|
|
123
|
+
int8_t bestRSSI = -127;
|
|
124
|
+
|
|
125
|
+
for (auto& candidate : candidates) {
|
|
126
|
+
if (candidate.routerRSSI > bestRSSI) {
|
|
127
|
+
// Better signal strength
|
|
128
|
+
bestRSSI = candidate.routerRSSI;
|
|
129
|
+
winner = &candidate;
|
|
130
|
+
} else if (candidate.routerRSSI == bestRSSI) {
|
|
131
|
+
// Tiebreaker 1: Higher uptime (more stable)
|
|
132
|
+
if (candidate.uptime > winner->uptime) {
|
|
133
|
+
winner = &candidate;
|
|
134
|
+
} else if (candidate.uptime == winner->uptime) {
|
|
135
|
+
// Tiebreaker 2: More memory (more capable)
|
|
136
|
+
if (candidate.freeMemory > winner->freeMemory) {
|
|
137
|
+
winner = &candidate;
|
|
138
|
+
} else if (candidate.freeMemory == winner->freeMemory) {
|
|
139
|
+
// Tiebreaker 3: Lower node ID (deterministic)
|
|
140
|
+
if (candidate.nodeId < winner->nodeId) {
|
|
141
|
+
winner = &candidate;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## API Reference
|
|
150
|
+
|
|
151
|
+
### Configuration
|
|
152
|
+
|
|
153
|
+
```cpp
|
|
154
|
+
// Set router credentials (required for election participation)
|
|
155
|
+
mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
|
|
156
|
+
|
|
157
|
+
// Enable automatic failover (default: enabled)
|
|
158
|
+
mesh.enableBridgeFailover(true);
|
|
159
|
+
|
|
160
|
+
// Set election timeout in milliseconds (default: 5000)
|
|
161
|
+
mesh.setElectionTimeout(5000);
|
|
162
|
+
|
|
163
|
+
// Set bridge timeout for failure detection (default: 60000)
|
|
164
|
+
mesh.setBridgeTimeout(60000);
|
|
165
|
+
|
|
166
|
+
// Set bridge status broadcast interval (default: 30000)
|
|
167
|
+
mesh.setBridgeStatusInterval(30000);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Callbacks
|
|
171
|
+
|
|
172
|
+
```cpp
|
|
173
|
+
// Called when bridge status changes
|
|
174
|
+
void onBridgeStatusChanged(uint32_t bridgeNodeId, bool hasInternet) {
|
|
175
|
+
if (!hasInternet) {
|
|
176
|
+
Serial.println("Bridge lost Internet - election may start");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Called when this node's role changes
|
|
181
|
+
void onBridgeRoleChanged(bool isBridge, String reason) {
|
|
182
|
+
if (isBridge) {
|
|
183
|
+
Serial.printf("Promoted to bridge: %s\n", reason.c_str());
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Register callbacks
|
|
188
|
+
mesh.onBridgeStatusChanged(&onBridgeStatusChanged);
|
|
189
|
+
mesh.onBridgeRoleChanged(&onBridgeRoleChanged);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Status Methods
|
|
193
|
+
|
|
194
|
+
```cpp
|
|
195
|
+
// Check if this node is a bridge
|
|
196
|
+
bool isBridge = mesh.isBridge();
|
|
197
|
+
|
|
198
|
+
// Check if any bridge has Internet connectivity
|
|
199
|
+
bool hasInternet = mesh.hasInternetConnection();
|
|
200
|
+
|
|
201
|
+
// Get primary (best) bridge
|
|
202
|
+
BridgeInfo* primary = mesh.getPrimaryBridge();
|
|
203
|
+
if (primary) {
|
|
204
|
+
Serial.printf("Primary bridge: %u (RSSI: %d dBm)\n",
|
|
205
|
+
primary->nodeId, primary->routerRSSI);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Get all known bridges
|
|
209
|
+
std::vector<BridgeInfo> bridges = mesh.getBridges();
|
|
210
|
+
for (const auto& bridge : bridges) {
|
|
211
|
+
Serial.printf("Bridge %u: Internet=%s, RSSI=%d\n",
|
|
212
|
+
bridge.nodeId,
|
|
213
|
+
bridge.internetConnected ? "YES" : "NO",
|
|
214
|
+
bridge.routerRSSI);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Usage Example
|
|
219
|
+
|
|
220
|
+
### Basic Setup
|
|
221
|
+
|
|
222
|
+
```cpp
|
|
223
|
+
#include "painlessMesh.h"
|
|
224
|
+
|
|
225
|
+
#define MESH_PREFIX "MyMesh"
|
|
226
|
+
#define MESH_PASSWORD "password"
|
|
227
|
+
#define ROUTER_SSID "MyRouter"
|
|
228
|
+
#define ROUTER_PASSWORD "routerpass"
|
|
229
|
+
|
|
230
|
+
painlessMesh mesh;
|
|
231
|
+
|
|
232
|
+
void setup() {
|
|
233
|
+
// Initialize as regular node
|
|
234
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler);
|
|
235
|
+
|
|
236
|
+
// Enable automatic failover
|
|
237
|
+
mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
|
|
238
|
+
mesh.enableBridgeFailover(true);
|
|
239
|
+
|
|
240
|
+
// Register callbacks
|
|
241
|
+
mesh.onBridgeStatusChanged(&bridgeStatusCallback);
|
|
242
|
+
mesh.onBridgeRoleChanged(&bridgeRoleCallback);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Initial Bridge Setup
|
|
247
|
+
|
|
248
|
+
```cpp
|
|
249
|
+
void setup() {
|
|
250
|
+
// Initialize as bridge with automatic channel detection
|
|
251
|
+
mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD,
|
|
252
|
+
ROUTER_SSID, ROUTER_PASSWORD,
|
|
253
|
+
&userScheduler);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
See `examples/bridge_failover/` for complete working example.
|
|
258
|
+
|
|
259
|
+
## Failure Scenarios
|
|
260
|
+
|
|
261
|
+
### Scenario 1: Bridge Goes Offline
|
|
262
|
+
|
|
263
|
+
**Timeline:**
|
|
264
|
+
- T+0s: Bridge node powers off
|
|
265
|
+
- T+30s: Last status broadcast expires
|
|
266
|
+
- T+60s: Nodes detect failure (timeout threshold)
|
|
267
|
+
- T+62s: Election starts (2s coordination delay)
|
|
268
|
+
- T+67s: Election completes (5s collection window)
|
|
269
|
+
- T+72s: Winner promoted to bridge (5s promotion)
|
|
270
|
+
|
|
271
|
+
**Total Failover Time:** ~70 seconds
|
|
272
|
+
|
|
273
|
+
### Scenario 2: Bridge Loses Internet
|
|
274
|
+
|
|
275
|
+
**Timeline:**
|
|
276
|
+
- T+0s: Router Internet connection fails
|
|
277
|
+
- T+30s: Bridge broadcasts `internetConnected: false`
|
|
278
|
+
- T+30s: Nodes receive status, consider election
|
|
279
|
+
- T+32s: Election starts (if no recovery detected)
|
|
280
|
+
- T+37s: Election completes
|
|
281
|
+
- T+42s: Winner promoted to bridge
|
|
282
|
+
|
|
283
|
+
**Total Failover Time:** ~42 seconds
|
|
284
|
+
|
|
285
|
+
### Scenario 3: Sequential Bridge Failures
|
|
286
|
+
|
|
287
|
+
**Timeline:**
|
|
288
|
+
- T+0s: Primary bridge fails, election starts
|
|
289
|
+
- T+70s: Node A wins, becomes bridge
|
|
290
|
+
- T+120s: Node A also fails
|
|
291
|
+
- T+180s: Failure detected (60s timeout)
|
|
292
|
+
- T+250s: New election completes, Node B becomes bridge
|
|
293
|
+
|
|
294
|
+
**Recovery:** Continues indefinitely until stable bridge found
|
|
295
|
+
|
|
296
|
+
## Performance Characteristics
|
|
297
|
+
|
|
298
|
+
### Timing
|
|
299
|
+
- **Failure detection:** 60 seconds (configurable)
|
|
300
|
+
- **Election duration:** 5 seconds (configurable)
|
|
301
|
+
- **Promotion delay:** 5 seconds (WiFi reconnection)
|
|
302
|
+
- **Total failover:** 60-70 seconds typical
|
|
303
|
+
|
|
304
|
+
### Network Overhead
|
|
305
|
+
- **Status broadcasts:** 256 bytes per bridge every 30s
|
|
306
|
+
- **Election messages:** 256 bytes per candidate (one-time)
|
|
307
|
+
- **Takeover announcement:** 256 bytes (one-time)
|
|
308
|
+
|
|
309
|
+
### Memory Usage
|
|
310
|
+
- **Per candidate:** ~12 bytes during election
|
|
311
|
+
- **Bridge tracking:** ~48 bytes per bridge
|
|
312
|
+
- **State machine:** ~100 bytes
|
|
313
|
+
|
|
314
|
+
### Scalability
|
|
315
|
+
- **Tested:** Up to 10 nodes
|
|
316
|
+
- **Theoretical:** 50+ nodes (limited by election timeout)
|
|
317
|
+
- **Recommended:** 5-10 nodes per mesh
|
|
318
|
+
|
|
319
|
+
## Edge Cases and Prevention
|
|
320
|
+
|
|
321
|
+
### Split-Brain Prevention
|
|
322
|
+
|
|
323
|
+
**Problem:** Multiple nodes promote themselves simultaneously
|
|
324
|
+
|
|
325
|
+
**Solution:**
|
|
326
|
+
- State machine prevents concurrent elections
|
|
327
|
+
- Deterministic evaluation ensures consensus
|
|
328
|
+
- All nodes reach same conclusion independently
|
|
329
|
+
|
|
330
|
+
### Rapid Failover Prevention
|
|
331
|
+
|
|
332
|
+
**Problem:** Bridge roles oscillate rapidly
|
|
333
|
+
|
|
334
|
+
**Solution:**
|
|
335
|
+
- Minimum 60 seconds between role changes
|
|
336
|
+
- RSSI hysteresis (winner must be significantly better)
|
|
337
|
+
- Uptime tiebreaker favors stable nodes
|
|
338
|
+
|
|
339
|
+
### Phantom Election Prevention
|
|
340
|
+
|
|
341
|
+
**Problem:** Election starts when bridge is healthy
|
|
342
|
+
|
|
343
|
+
**Solution:**
|
|
344
|
+
- Only trigger on confirmed failure (60s no heartbeat)
|
|
345
|
+
- Check `hasInternetConnection()` before starting
|
|
346
|
+
- Verify router is visible before participating
|
|
347
|
+
|
|
348
|
+
### Router Visibility Issues
|
|
349
|
+
|
|
350
|
+
**Problem:** Candidate can't see router during scan
|
|
351
|
+
|
|
352
|
+
**Solution:**
|
|
353
|
+
- Return RSSI=0 if router not found
|
|
354
|
+
- Candidate excluded from winner consideration
|
|
355
|
+
- Election continues with remaining candidates
|
|
356
|
+
|
|
357
|
+
## Troubleshooting
|
|
358
|
+
|
|
359
|
+
### Elections Don't Start
|
|
360
|
+
|
|
361
|
+
**Check:**
|
|
362
|
+
- Router credentials configured: `setRouterCredentials()`
|
|
363
|
+
- Failover enabled: `enableBridgeFailover(true)`
|
|
364
|
+
- Bridge timeout exceeded (60 seconds)
|
|
365
|
+
- At least one node can see router
|
|
366
|
+
|
|
367
|
+
**Debug:**
|
|
368
|
+
```cpp
|
|
369
|
+
Serial.printf("Credentials: %s\n", routerCredentialsConfigured ? "YES" : "NO");
|
|
370
|
+
Serial.printf("Failover: %s\n", bridgeFailoverEnabled ? "ON" : "OFF");
|
|
371
|
+
Serial.printf("Last bridge seen: %u ms ago\n", millis() - lastBridgeSeen);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Wrong Node Wins Election
|
|
375
|
+
|
|
376
|
+
**Check:**
|
|
377
|
+
- RSSI measurement accuracy (WiFi scan)
|
|
378
|
+
- Router placement and interference
|
|
379
|
+
- Tiebreaker criteria (uptime, memory, node ID)
|
|
380
|
+
|
|
381
|
+
**Debug:**
|
|
382
|
+
```cpp
|
|
383
|
+
Serial.printf("My RSSI: %d dBm\n", scanRouterSignalStrength(ROUTER_SSID));
|
|
384
|
+
Serial.printf("My uptime: %u ms\n", millis());
|
|
385
|
+
Serial.printf("My free memory: %u bytes\n", ESP.getFreeHeap());
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Bridge Promotion Fails
|
|
389
|
+
|
|
390
|
+
**Check:**
|
|
391
|
+
- Router password correct
|
|
392
|
+
- Router channel compatible
|
|
393
|
+
- Node can reach router physically
|
|
394
|
+
- Sufficient memory for bridge mode
|
|
395
|
+
|
|
396
|
+
**Debug:**
|
|
397
|
+
```cpp
|
|
398
|
+
Serial.printf("WiFi status: %d\n", WiFi.status());
|
|
399
|
+
Serial.printf("Router channel: %d\n", WiFi.channel());
|
|
400
|
+
Serial.printf("Free heap: %u bytes\n", ESP.getFreeHeap());
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Security Considerations
|
|
404
|
+
|
|
405
|
+
### Router Credentials
|
|
406
|
+
|
|
407
|
+
**Risk:** Router password stored in node memory
|
|
408
|
+
|
|
409
|
+
**Mitigation:**
|
|
410
|
+
- Use separate guest network for mesh bridges
|
|
411
|
+
- Limit router permissions (no admin access)
|
|
412
|
+
- Consider WPA2-Enterprise for stronger security
|
|
413
|
+
|
|
414
|
+
### Rogue Bridge Prevention
|
|
415
|
+
|
|
416
|
+
**Risk:** Malicious node claims bridge role with fake RSSI
|
|
417
|
+
|
|
418
|
+
**Mitigation:**
|
|
419
|
+
- Physical security of mesh nodes
|
|
420
|
+
- Verify bridge Internet connectivity post-election
|
|
421
|
+
- Monitor bridge status broadcasts for anomalies
|
|
422
|
+
|
|
423
|
+
### Denial of Service
|
|
424
|
+
|
|
425
|
+
**Risk:** Attacker triggers repeated elections
|
|
426
|
+
|
|
427
|
+
**Mitigation:**
|
|
428
|
+
- Minimum 60s between role changes
|
|
429
|
+
- Rate limiting on election start triggers
|
|
430
|
+
- Monitor for excessive election activity
|
|
431
|
+
|
|
432
|
+
## Best Practices
|
|
433
|
+
|
|
434
|
+
### 1. Configure Redundancy
|
|
435
|
+
```cpp
|
|
436
|
+
// At least 2 nodes with router credentials
|
|
437
|
+
mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### 2. Monitor Bridge Health
|
|
441
|
+
```cpp
|
|
442
|
+
void loop() {
|
|
443
|
+
if (!mesh.hasInternetConnection()) {
|
|
444
|
+
// Queue critical data locally
|
|
445
|
+
queueMessage(msg);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### 3. Log Election Activity
|
|
451
|
+
```cpp
|
|
452
|
+
void onBridgeRoleChanged(bool isBridge, String reason) {
|
|
453
|
+
logToSD(millis(), isBridge, reason); // Audit trail
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 4. Test Failover Regularly
|
|
458
|
+
```cpp
|
|
459
|
+
// Scheduled failover test (monthly)
|
|
460
|
+
if (shouldTestFailover()) {
|
|
461
|
+
// Temporarily disable primary bridge
|
|
462
|
+
testBridgeFailover();
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### 5. Optimize Router Placement
|
|
467
|
+
- Position router centrally in mesh coverage area
|
|
468
|
+
- Minimize physical obstructions
|
|
469
|
+
- Avoid interference from other 2.4GHz devices
|
|
470
|
+
|
|
471
|
+
## Comparison with Alternatives
|
|
472
|
+
|
|
473
|
+
### Manual Failover
|
|
474
|
+
- **Pro:** Full control, predictable behavior
|
|
475
|
+
- **Con:** Requires human intervention, slow recovery
|
|
476
|
+
|
|
477
|
+
### Pre-designated Backup
|
|
478
|
+
- **Pro:** Fast failover to known secondary
|
|
479
|
+
- **Con:** Backup may have poor signal, not optimal
|
|
480
|
+
|
|
481
|
+
### RSSI-Based Election (painlessMesh)
|
|
482
|
+
- **Pro:** Automatic, optimal selection, distributed
|
|
483
|
+
- **Con:** 60-70s failover time, requires router visibility
|
|
484
|
+
|
|
485
|
+
## Future Enhancements
|
|
486
|
+
|
|
487
|
+
### Potential Improvements
|
|
488
|
+
1. **Faster failover:** Reduce detection timeout to 30s
|
|
489
|
+
2. **Predictive failover:** Detect degrading bridges before failure
|
|
490
|
+
3. **Multi-router support:** Failover between different routers
|
|
491
|
+
4. **Quality of Service:** Prioritize critical traffic during failover
|
|
492
|
+
5. **Geographic awareness:** Consider physical location in selection
|
|
493
|
+
|
|
494
|
+
### Experimental Features
|
|
495
|
+
- Load-based selection (choose least-loaded node)
|
|
496
|
+
- Battery-aware (exclude low-battery nodes)
|
|
497
|
+
- Historical reliability (favor nodes with uptime track record)
|
|
498
|
+
|
|
499
|
+
## References
|
|
500
|
+
|
|
501
|
+
- Issue: Alteriom/painlessMesh#XX (Bridge Failover Request)
|
|
502
|
+
- Related: Alteriom/painlessMesh#63 (Bridge Status Broadcast)
|
|
503
|
+
- Related: Alteriom/painlessMesh#59 (initAsBridge Method)
|
|
504
|
+
- Example: `examples/bridge_failover/`
|
|
505
|
+
- Test: `test/catch/catch_alteriom_packages.cpp`
|
|
506
|
+
|
|
507
|
+
## Credits
|
|
508
|
+
|
|
509
|
+
- **Requested by:** @woodlist (fish farm alarm system)
|
|
510
|
+
- **Implemented:** painlessMesh v1.8.0
|
|
511
|
+
- **Consensus model:** Based on Raft algorithm principles
|
|
512
|
+
- **RSSI selection:** Adapted from WiFi mesh best practices
|