@alteriom/painlessmesh 1.8.8 → 1.8.10
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 +64 -0
- package/docs/releases/BRIDGE_STATUS_SELF_REGISTRATION_FIX.md +221 -0
- package/examples/bridge_failover/README.md +50 -2
- package/examples/bridge_failover/bridge_failover.ino +4 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/AlteriomPainlessMesh.h +2 -2
- package/src/arduino/wifi.hpp +119 -7
- package/src/boost/asynctcp.hpp +5 -0
- package/src/painlessMesh.h +2 -2
- package/src/painlessmesh/logger.hpp +9 -4
- package/src/painlessmesh/mesh.hpp +1 -1
- package/src/painlessmesh/protocol.hpp +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,70 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.8.10] - 2025-11-18
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Bridge Status Discovery - Direct Messaging** - Fixed newly connected nodes not receiving bridge status
|
|
15
|
+
- **Root Cause**: Broadcast messages were not reaching newly connected nodes reliably
|
|
16
|
+
- Time sync (NTP) was interfering with bridge discovery
|
|
17
|
+
- Broadcast routing may not be fully established immediately after connection
|
|
18
|
+
- **Solution**: Send bridge status directly to new node using `sendSingle()`
|
|
19
|
+
- Changed from broadcast (`routing=2`) to single (`routing=1`)
|
|
20
|
+
- Minimal 500ms delay (just for connection stability)
|
|
21
|
+
- Direct targeted delivery ensures message reaches the new node
|
|
22
|
+
- Time sync no longer affects bridge discovery
|
|
23
|
+
- Location: `src/arduino/wifi.hpp` line ~809 in `initBridgeStatusBroadcast()`
|
|
24
|
+
- Impact: Nodes discover bridges immediately (within 500ms) after connecting
|
|
25
|
+
- Backward compatible: No API changes, internal delivery mechanism improved
|
|
26
|
+
- Resolves GitHub issue #135 "The latest fix does not work"
|
|
27
|
+
|
|
28
|
+
## [1.8.9] - 2025-11-12
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **Bridge Self-Registration (Type 610 & 613)** - Bridge nodes now properly track themselves in status and coordination
|
|
33
|
+
- **Bridge Status Broadcasting (Type 610)**: Fixed bridge nodes reporting "Known bridges: 0" despite being active
|
|
34
|
+
- Added immediate self-registration task in `initBridgeStatusBroadcast()` (line ~746)
|
|
35
|
+
- Bridge now calls `updateBridgeStatus()` with own nodeId immediately after initialization
|
|
36
|
+
- Added self-update in `sendBridgeStatus()` (line ~1192) before broadcasting
|
|
37
|
+
- Ensures bridge appears in its own `knownBridges` list from the start
|
|
38
|
+
- **Bridge Coordination (Type 613)**: Fixed multi-bridge priority tracking
|
|
39
|
+
- Added self-registration in `initBridgeCoordination()` (line ~803)
|
|
40
|
+
- Bridge now adds own priority to `bridgePriorities` map: `bridgePriorities[this->nodeId] = bridgePriority`
|
|
41
|
+
- Added priority self-update in `sendBridgeCoordination()` (line ~869) before broadcasting
|
|
42
|
+
- Ensures primary bridge selection works correctly with multiple bridges
|
|
43
|
+
- **Root Cause**: Mesh networks don't loop broadcasts back to sender by design
|
|
44
|
+
- Nodes receive broadcasts from other nodes but not their own messages
|
|
45
|
+
- Requires explicit local state management for any tracking data
|
|
46
|
+
- **Before Fix**:
|
|
47
|
+
- Bridge reports "Known bridges: 0" and "No primary bridge available!"
|
|
48
|
+
- Multi-bridge setups fail to select primary bridge (missing own priority)
|
|
49
|
+
- Bridge failover unreliable due to incomplete bridge tracking
|
|
50
|
+
- **After Fix**:
|
|
51
|
+
- Bridge correctly reports "Known bridges: 1" (or more in multi-bridge setups)
|
|
52
|
+
- Primary bridge selection works properly with all bridge priorities present
|
|
53
|
+
- Self-tracking pattern now consistent across all periodic broadcast types
|
|
54
|
+
- Core fixes in `src/arduino/wifi.hpp`
|
|
55
|
+
- Resolves @woodlist GitHub issue - bridge showing "Known bridges: 0"
|
|
56
|
+
- Comprehensive analysis documented in COMPREHENSIVE_BROADCAST_ANALYSIS.md
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **Build System** - Switched Docker compiler from clang++ to g++
|
|
61
|
+
- Changed ENV CXX in Dockerfile from clang++ to g++
|
|
62
|
+
- Resolves template instantiation crashes during Docker builds
|
|
63
|
+
- Build verification confirms successful compilation with g++
|
|
64
|
+
|
|
65
|
+
### Documentation
|
|
66
|
+
|
|
67
|
+
- **Broadcast Message Analysis** - Added comprehensive review documentation
|
|
68
|
+
- Created COMPREHENSIVE_BROADCAST_ANALYSIS.md with full analysis of all 4 broadcast types
|
|
69
|
+
- Documents self-tracking requirements for Type 610 (STATUS) and 613 (COORDINATION)
|
|
70
|
+
- Confirms Type 611 (ELECTION) already implements correct self-registration
|
|
71
|
+
- Confirms Type 612 (TAKEOVER) doesn't require self-tracking (notification only)
|
|
72
|
+
- Establishes pattern guidelines for future broadcast implementations
|
|
73
|
+
|
|
10
74
|
## [1.8.8] - 2025-11-12
|
|
11
75
|
|
|
12
76
|
### Fixed
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Bridge Status Self-Registration Fix
|
|
2
|
+
|
|
3
|
+
## Issue Reported by @woodlist
|
|
4
|
+
|
|
5
|
+
**Problem**: After successfully promoting to bridge via election, the node reports:
|
|
6
|
+
```
|
|
7
|
+
I am bridge: YES
|
|
8
|
+
Internet available: NO
|
|
9
|
+
Known bridges: 0
|
|
10
|
+
No primary bridge available! ❌
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Root Cause
|
|
14
|
+
|
|
15
|
+
When a node becomes a bridge (either via `initAsBridge()` or election promotion), it broadcasts bridge status messages to the mesh network, but **it does not add itself to its own `knownBridges` list**.
|
|
16
|
+
|
|
17
|
+
### Why This Happens
|
|
18
|
+
|
|
19
|
+
1. Node wins election and promotes to bridge ✅
|
|
20
|
+
2. Node calls `initBridgeStatusBroadcast()` ✅
|
|
21
|
+
3. Node sends bridge status broadcasts ✅
|
|
22
|
+
4. **Other nodes receive broadcasts and update their `knownBridges` ✅**
|
|
23
|
+
5. **Bridge node itself never receives its own broadcast ❌**
|
|
24
|
+
6. Bridge node's `knownBridges` remains empty ❌
|
|
25
|
+
7. `getPrimaryBridge()` returns `nullptr` because list is empty ❌
|
|
26
|
+
|
|
27
|
+
### Code Flow
|
|
28
|
+
|
|
29
|
+
```cpp
|
|
30
|
+
// When bridge promotes
|
|
31
|
+
promoteToBridge()
|
|
32
|
+
→ initAsBridge()
|
|
33
|
+
→ initBridgeStatusBroadcast()
|
|
34
|
+
→ sendBridgeStatus() // Broadcasts to network
|
|
35
|
+
→ sendBroadcast(msg) // Bridge doesn't receive its own broadcasts
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## The Fix
|
|
39
|
+
|
|
40
|
+
### Changes to `src/arduino/wifi.hpp`
|
|
41
|
+
|
|
42
|
+
#### 1. Modified `initBridgeStatusBroadcast()`
|
|
43
|
+
|
|
44
|
+
Added self-registration during bridge initialization:
|
|
45
|
+
|
|
46
|
+
```cpp
|
|
47
|
+
void initBridgeStatusBroadcast() {
|
|
48
|
+
using namespace logger;
|
|
49
|
+
|
|
50
|
+
if (!this->isBridge() || !this->bridgeStatusBroadcastEnabled) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Log(STARTUP, "initBridgeStatusBroadcast(): Setting up bridge status broadcast\n");
|
|
55
|
+
|
|
56
|
+
// NEW: Register ourselves as a bridge in the knownBridges list
|
|
57
|
+
// This ensures the bridge knows about itself and reports correct status
|
|
58
|
+
this->addTask([this]() {
|
|
59
|
+
bool hasInternet = (WiFi.status() == WL_CONNECTED) &&
|
|
60
|
+
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
61
|
+
|
|
62
|
+
this->updateBridgeStatus(
|
|
63
|
+
this->nodeId, // bridgeNodeId
|
|
64
|
+
hasInternet, // internetConnected
|
|
65
|
+
WiFi.RSSI(), // routerRSSI
|
|
66
|
+
WiFi.channel(), // routerChannel
|
|
67
|
+
millis(), // uptime
|
|
68
|
+
WiFi.gatewayIP().toString(),// gatewayIP
|
|
69
|
+
this->getNodeTime() // timestamp
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
Log(STARTUP, "initBridgeStatusBroadcast(): Registered self as bridge (nodeId: %u)\n",
|
|
73
|
+
this->nodeId);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// ... rest of method unchanged
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### 2. Modified `sendBridgeStatus()`
|
|
81
|
+
|
|
82
|
+
Added self-update before broadcasting:
|
|
83
|
+
|
|
84
|
+
```cpp
|
|
85
|
+
void sendBridgeStatus() {
|
|
86
|
+
using namespace logger;
|
|
87
|
+
|
|
88
|
+
if (!this->bridgeStatusBroadcastEnabled) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ... create JSON message ...
|
|
93
|
+
|
|
94
|
+
bool hasInternet = (WiFi.status() == WL_CONNECTED) &&
|
|
95
|
+
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
96
|
+
|
|
97
|
+
int8_t rssi = WiFi.RSSI();
|
|
98
|
+
uint8_t channel = WiFi.channel();
|
|
99
|
+
uint32_t uptime = millis();
|
|
100
|
+
TSTRING gatewayIP = WiFi.gatewayIP().toString();
|
|
101
|
+
|
|
102
|
+
// ... add to JSON ...
|
|
103
|
+
|
|
104
|
+
Log(GENERAL, "sendBridgeStatus(): Broadcasting status (Internet: %s)\n",
|
|
105
|
+
hasInternet ? "Connected" : "Disconnected");
|
|
106
|
+
Log(GENERAL, "sendBridgeStatus(): WiFi status=%d, localIP=%s, gatewayIP=%s\n",
|
|
107
|
+
WiFi.status(), WiFi.localIP().toString().c_str(), WiFi.gatewayIP().toString().c_str());
|
|
108
|
+
|
|
109
|
+
// NEW: Update our own bridge status in knownBridges list
|
|
110
|
+
// This ensures the bridge reports itself correctly when queried
|
|
111
|
+
this->updateBridgeStatus(this->nodeId, hasInternet, rssi, channel,
|
|
112
|
+
uptime, gatewayIP, this->getNodeTime());
|
|
113
|
+
|
|
114
|
+
this->sendBroadcast(msg);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Expected Behavior After Fix
|
|
119
|
+
|
|
120
|
+
### Before Fix
|
|
121
|
+
```
|
|
122
|
+
--- Bridge Status ---
|
|
123
|
+
I am bridge: YES
|
|
124
|
+
Internet available: NO
|
|
125
|
+
Known bridges: 0
|
|
126
|
+
No primary bridge available! ❌
|
|
127
|
+
--------------------
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### After Fix
|
|
131
|
+
```
|
|
132
|
+
--- Bridge Status ---
|
|
133
|
+
I am bridge: YES
|
|
134
|
+
Internet available: NO ⚠️ (May be YES if router has internet)
|
|
135
|
+
Known bridges: 1
|
|
136
|
+
Primary bridge: 3394043125 (RSSI: -36 dBm) ✅
|
|
137
|
+
--------------------
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Technical Details
|
|
141
|
+
|
|
142
|
+
### Why Two Registration Points?
|
|
143
|
+
|
|
144
|
+
1. **`initBridgeStatusBroadcast()`** - Initial registration when bridge first starts
|
|
145
|
+
- Ensures bridge is in `knownBridges` immediately after promotion
|
|
146
|
+
- Provides accurate status for early queries
|
|
147
|
+
|
|
148
|
+
2. **`sendBridgeStatus()`** - Periodic updates
|
|
149
|
+
- Keeps bridge info current in `knownBridges`
|
|
150
|
+
- Updates RSSI, uptime, internet connectivity dynamically
|
|
151
|
+
- Ensures consistency between broadcast and local state
|
|
152
|
+
|
|
153
|
+
### Internet Connectivity
|
|
154
|
+
|
|
155
|
+
The "Internet available: NO" in @woodlist's log may be accurate depending on:
|
|
156
|
+
- Router has active internet connection
|
|
157
|
+
- DHCP has assigned valid IP address (not 0.0.0.0)
|
|
158
|
+
- `WiFi.localIP()` returns valid address
|
|
159
|
+
|
|
160
|
+
Check with:
|
|
161
|
+
```cpp
|
|
162
|
+
Log(GENERAL, "sendBridgeStatus(): WiFi status=%d, localIP=%s, gatewayIP=%s\n",
|
|
163
|
+
WiFi.status(), WiFi.localIP().toString().c_str(), WiFi.gatewayIP().toString().c_str());
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This log line was present in the code but not visible in the serial output @woodlist provided.
|
|
167
|
+
|
|
168
|
+
## Impact
|
|
169
|
+
|
|
170
|
+
### Fixed Issues
|
|
171
|
+
✅ Bridge nodes now correctly report themselves in `knownBridges`
|
|
172
|
+
✅ `getPrimaryBridge()` returns valid bridge pointer for bridge nodes
|
|
173
|
+
✅ Bridge status displays accurately show "Known bridges: 1" minimum
|
|
174
|
+
✅ Regular nodes can immediately discover newly promoted bridges
|
|
175
|
+
|
|
176
|
+
### Backward Compatibility
|
|
177
|
+
✅ No breaking changes to existing API
|
|
178
|
+
✅ Compatible with existing bridge setups
|
|
179
|
+
✅ Works with both manual `initAsBridge()` and election-based promotion
|
|
180
|
+
✅ Multi-bridge configurations continue to function normally
|
|
181
|
+
|
|
182
|
+
## Testing
|
|
183
|
+
|
|
184
|
+
### Manual Test Procedure
|
|
185
|
+
1. Flash bridge_failover example to ESP32/ESP8266
|
|
186
|
+
2. Start all nodes without pre-configured bridge
|
|
187
|
+
3. Wait for 60-second grace period
|
|
188
|
+
4. Observe election and promotion
|
|
189
|
+
5. Check bridge status output
|
|
190
|
+
|
|
191
|
+
Expected to see:
|
|
192
|
+
- "Known bridges: 1" or more
|
|
193
|
+
- Valid primary bridge with node ID and RSSI
|
|
194
|
+
- No "No primary bridge available!" error
|
|
195
|
+
|
|
196
|
+
### Unit Tests
|
|
197
|
+
Existing tests in `test/catch/catch_diagnostics_api.cpp` verify:
|
|
198
|
+
- `updateBridgeStatus()` adds bridges correctly
|
|
199
|
+
- `getPrimaryBridge()` returns correct bridge based on RSSI/health
|
|
200
|
+
- Bridge tracking and health monitoring work as expected
|
|
201
|
+
|
|
202
|
+
## Files Modified
|
|
203
|
+
- `src/arduino/wifi.hpp`
|
|
204
|
+
- `initBridgeStatusBroadcast()` - Added self-registration
|
|
205
|
+
- `sendBridgeStatus()` - Added periodic self-update
|
|
206
|
+
|
|
207
|
+
## Related Issues
|
|
208
|
+
- Fixes "No primary bridge available" on bridge nodes
|
|
209
|
+
- Related to bridge election feature (v1.8.6)
|
|
210
|
+
- Complements bridge failover implementation
|
|
211
|
+
|
|
212
|
+
## Credits
|
|
213
|
+
**Reported by**: @woodlist
|
|
214
|
+
**Analysis**: GitHub Copilot
|
|
215
|
+
**Fix**: Self-registration pattern for bridge tracking
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
**Version**: To be included in v1.8.7+
|
|
220
|
+
**Date**: November 12, 2025
|
|
221
|
+
**Status**: Implementation complete, pending testing
|
|
@@ -233,6 +233,10 @@ mesh.enableBridgeFailover(true);
|
|
|
233
233
|
// Set election timeout (milliseconds)
|
|
234
234
|
mesh.setElectionTimeout(5000);
|
|
235
235
|
|
|
236
|
+
// Set minimum RSSI for isolated bridge elections (default: -80 dBm)
|
|
237
|
+
// Prevents nodes with poor signal from becoming bridges when isolated
|
|
238
|
+
mesh.setMinimumBridgeRSSI(-80);
|
|
239
|
+
|
|
236
240
|
// Set bridge status broadcast interval
|
|
237
241
|
mesh.setBridgeStatusInterval(30000);
|
|
238
242
|
|
|
@@ -357,6 +361,37 @@ bool amBridge = mesh.isBridge();
|
|
|
357
361
|
- Works reliably with all network types including mobile hotspots and tethering
|
|
358
362
|
- Local IP check is more reliable than gateway IP, which may not be available on all network types
|
|
359
363
|
|
|
364
|
+
### Isolated Node with Poor Signal Attempting Bridge
|
|
365
|
+
|
|
366
|
+
**Symptoms**: Node logs "Election Failed: Insufficient Signal Quality" or has -85+ dBm RSSI
|
|
367
|
+
|
|
368
|
+
**Root Cause**: Node cannot see the mesh (isolated) and has poor router signal below minimum threshold
|
|
369
|
+
|
|
370
|
+
**What's Happening**:
|
|
371
|
+
1. Node comes online and doesn't detect existing bridge
|
|
372
|
+
2. Triggers election where it's the only candidate
|
|
373
|
+
3. Has poor router RSSI (e.g., -87 dBm, below -80 dBm threshold)
|
|
374
|
+
4. Election automatically rejected to prevent unreliable bridge
|
|
375
|
+
5. Node remains as regular node until mesh connection or better signal
|
|
376
|
+
|
|
377
|
+
**Expected Behavior** (v1.8.10+):
|
|
378
|
+
```
|
|
379
|
+
=== Evaluating Election ===
|
|
380
|
+
evaluateElection(): 1 candidates
|
|
381
|
+
=== Election Failed: Insufficient Signal Quality ===
|
|
382
|
+
Single candidate with RSSI -87 dBm (minimum required: -80 dBm)
|
|
383
|
+
Node is isolated from mesh with poor router signal
|
|
384
|
+
Rejecting election to prevent unreliable bridge
|
|
385
|
+
Recommendation: Move closer to router or wait for mesh connection
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Solutions**:
|
|
389
|
+
- **Best**: Move node closer to router for better signal strength
|
|
390
|
+
- **Wait**: Node will join existing bridge once mesh connection established
|
|
391
|
+
- **Adjust threshold**: `mesh.setMinimumBridgeRSSI(-85)` to relax requirement (not recommended)
|
|
392
|
+
- **Check existing bridge**: Ensure another node with good signal is already bridge
|
|
393
|
+
- **Verify mesh**: Ensure nodes can communicate with each other
|
|
394
|
+
|
|
360
395
|
### Election Doesn't Start
|
|
361
396
|
|
|
362
397
|
**Symptoms**: Bridge fails but no election occurs
|
|
@@ -381,10 +416,23 @@ bool amBridge = mesh.isBridge();
|
|
|
381
416
|
|
|
382
417
|
**Symptoms**: Node with weak signal becomes bridge
|
|
383
418
|
|
|
384
|
-
**
|
|
419
|
+
**Root Cause (Fixed in v1.8.10+)**:
|
|
420
|
+
When a node with poor router signal (e.g., -87 dBm) comes online and cannot see the existing bridge, it may trigger an election where it's the only candidate. Previously, it could win by default despite inadequate signal strength.
|
|
421
|
+
|
|
422
|
+
**Solution (Automatic)**:
|
|
423
|
+
Starting in v1.8.10, the election system enforces a minimum RSSI threshold (-80 dBm by default) for single-candidate elections. This prevents isolated nodes with poor signal from becoming bridges.
|
|
424
|
+
|
|
425
|
+
**Behavior**:
|
|
426
|
+
- **Single candidate with poor RSSI**: Election fails with warning message
|
|
427
|
+
- **Multiple candidates**: Best RSSI wins regardless of threshold (mesh is connected)
|
|
428
|
+
- **Single candidate with good RSSI**: Election proceeds normally
|
|
429
|
+
|
|
430
|
+
**Manual Solutions**:
|
|
431
|
+
- Update to painlessMesh v1.8.10 or later
|
|
432
|
+
- Adjust minimum RSSI threshold: `mesh.setMinimumBridgeRSSI(-75)` (stricter)
|
|
385
433
|
- Verify RSSI values in election logs
|
|
386
434
|
- Check router positioning and interference
|
|
387
|
-
-
|
|
435
|
+
- Move nodes closer to router for better signal
|
|
388
436
|
- Review signal strength readings
|
|
389
437
|
|
|
390
438
|
### Frequent Re-elections
|
|
@@ -136,6 +136,10 @@ void setup() {
|
|
|
136
136
|
mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
|
|
137
137
|
mesh.enableBridgeFailover(true);
|
|
138
138
|
mesh.setElectionTimeout(5000); // 5 second election window
|
|
139
|
+
|
|
140
|
+
// Optional: Set minimum RSSI for isolated bridge elections (default: -80 dBm)
|
|
141
|
+
// This prevents nodes with poor signal from becoming bridges when isolated
|
|
142
|
+
// mesh.setMinimumBridgeRSSI(-80); // Uncomment to customize threshold
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
// Register callbacks
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=Alteriom PainlessMesh
|
|
2
|
-
version=1.8.
|
|
2
|
+
version=1.8.10
|
|
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.
|
|
3
|
+
"version": "1.8.10",
|
|
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",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
/**
|
|
30
30
|
* @brief AlteriomPainlessMesh library version information
|
|
31
31
|
*/
|
|
32
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION "1.8.
|
|
32
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION "1.8.10"
|
|
33
33
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MAJOR 1
|
|
34
34
|
#define ALTERIOM_PAINLESS_MESH_VERSION_MINOR 8
|
|
35
|
-
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH
|
|
35
|
+
#define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 10
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @brief Library description and usage information
|
package/src/arduino/wifi.hpp
CHANGED
|
@@ -534,6 +534,21 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
534
534
|
electionTimeoutMs = timeoutMs;
|
|
535
535
|
}
|
|
536
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Set the minimum RSSI required for bridge election
|
|
539
|
+
*
|
|
540
|
+
* Prevents nodes with poor router signal from becoming bridges in isolated
|
|
541
|
+
* elections. When a node is the only candidate, it must meet this threshold.
|
|
542
|
+
* When multiple candidates exist, the best RSSI wins regardless of threshold.
|
|
543
|
+
*
|
|
544
|
+
* @param minRSSI Minimum RSSI in dBm (default: -80 dBm, range: -100 to -30)
|
|
545
|
+
*/
|
|
546
|
+
void setMinimumBridgeRSSI(int8_t minRSSI) {
|
|
547
|
+
if (minRSSI < -100) minRSSI = -100;
|
|
548
|
+
if (minRSSI > -30) minRSSI = -30;
|
|
549
|
+
minimumBridgeRSSI = minRSSI;
|
|
550
|
+
}
|
|
551
|
+
|
|
537
552
|
/**
|
|
538
553
|
* Set callback for when this node's bridge role changes
|
|
539
554
|
*
|
|
@@ -752,6 +767,27 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
752
767
|
|
|
753
768
|
Log(STARTUP, "initBridgeStatusBroadcast(): Setting up bridge status broadcast\n");
|
|
754
769
|
|
|
770
|
+
// Register ourselves as a bridge in the knownBridges list
|
|
771
|
+
// This ensures the bridge knows about itself and reports correct status
|
|
772
|
+
this->addTask([this]() {
|
|
773
|
+
// Check Internet connectivity: WiFi connected AND valid IP address
|
|
774
|
+
bool hasInternet = (WiFi.status() == WL_CONNECTED) &&
|
|
775
|
+
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
776
|
+
|
|
777
|
+
this->updateBridgeStatus(
|
|
778
|
+
this->nodeId, // bridgeNodeId
|
|
779
|
+
hasInternet, // internetConnected
|
|
780
|
+
WiFi.RSSI(), // routerRSSI
|
|
781
|
+
WiFi.channel(), // routerChannel
|
|
782
|
+
millis(), // uptime
|
|
783
|
+
WiFi.gatewayIP().toString(),// gatewayIP
|
|
784
|
+
this->getNodeTime() // timestamp
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
Log(STARTUP, "initBridgeStatusBroadcast(): Registered self as bridge (nodeId: %u)\n",
|
|
788
|
+
this->nodeId);
|
|
789
|
+
});
|
|
790
|
+
|
|
755
791
|
// Create periodic task to broadcast bridge status
|
|
756
792
|
bridgeStatusTask = this->addTask(
|
|
757
793
|
this->bridgeStatusIntervalMs,
|
|
@@ -768,10 +804,42 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
768
804
|
this->sendBridgeStatus();
|
|
769
805
|
});
|
|
770
806
|
|
|
771
|
-
// Also
|
|
807
|
+
// Also send bridge status when new nodes connect so they can discover the bridge immediately
|
|
808
|
+
// Send directly to the new node to ensure delivery, independent of time sync
|
|
772
809
|
this->newConnectionCallbacks.push_back([this](uint32_t nodeId) {
|
|
773
|
-
Log(CONNECTION, "New node %u connected, sending bridge status\n", nodeId);
|
|
774
|
-
|
|
810
|
+
Log(CONNECTION, "New node %u connected, sending bridge status directly\n", nodeId);
|
|
811
|
+
|
|
812
|
+
// Small delay to ensure connection is ready, then send directly to the new node
|
|
813
|
+
// This avoids issues with time sync blocking broadcast messages
|
|
814
|
+
this->addTask(500, TASK_ONCE, [this, nodeId]() {
|
|
815
|
+
// Create bridge status message
|
|
816
|
+
JsonDocument doc;
|
|
817
|
+
JsonObject obj = doc.to<JsonObject>();
|
|
818
|
+
|
|
819
|
+
obj["type"] = 610; // BRIDGE_STATUS type
|
|
820
|
+
obj["from"] = this->nodeId;
|
|
821
|
+
obj["routing"] = 1; // SINGLE routing (direct to node)
|
|
822
|
+
obj["dest"] = nodeId;
|
|
823
|
+
obj["timestamp"] = this->getNodeTime();
|
|
824
|
+
|
|
825
|
+
bool hasInternet = (WiFi.status() == WL_CONNECTED) &&
|
|
826
|
+
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
827
|
+
obj["internetConnected"] = hasInternet;
|
|
828
|
+
obj["routerRSSI"] = WiFi.RSSI();
|
|
829
|
+
obj["routerChannel"] = WiFi.channel();
|
|
830
|
+
obj["uptime"] = millis();
|
|
831
|
+
obj["gatewayIP"] = WiFi.gatewayIP().toString();
|
|
832
|
+
obj["message_type"] = 610;
|
|
833
|
+
|
|
834
|
+
String msg;
|
|
835
|
+
serializeJson(doc, msg);
|
|
836
|
+
|
|
837
|
+
Log(CONNECTION, "Sending bridge status directly to node %u (Internet: %s)\n",
|
|
838
|
+
nodeId, hasInternet ? "YES" : "NO");
|
|
839
|
+
|
|
840
|
+
// Send directly to the new node, bypassing broadcast routing
|
|
841
|
+
this->sendSingle(nodeId, msg);
|
|
842
|
+
});
|
|
775
843
|
});
|
|
776
844
|
|
|
777
845
|
Log(STARTUP, "Bridge status broadcast enabled (interval: %d ms)\n",
|
|
@@ -791,6 +859,12 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
791
859
|
|
|
792
860
|
Log(STARTUP, "initBridgeCoordination(): Setting up multi-bridge coordination\n");
|
|
793
861
|
|
|
862
|
+
// Register our own priority in the bridgePriorities map
|
|
863
|
+
// This ensures getRecommendedBridge() with PRIORITY_BASED strategy works correctly
|
|
864
|
+
bridgePriorities[this->nodeId] = bridgePriority;
|
|
865
|
+
Log(STARTUP, "initBridgeCoordination(): Registered self priority (nodeId: %u, priority: %d)\n",
|
|
866
|
+
this->nodeId, bridgePriority);
|
|
867
|
+
|
|
794
868
|
// Register handler for incoming coordination messages (Type 613)
|
|
795
869
|
this->callbackList.onPackage(
|
|
796
870
|
613, // BRIDGE_COORDINATION type
|
|
@@ -881,6 +955,11 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
881
955
|
|
|
882
956
|
String msg;
|
|
883
957
|
serializeJson(doc, msg);
|
|
958
|
+
|
|
959
|
+
// Update our own priority in bridgePriorities map
|
|
960
|
+
// This ensures priority-based selection always has current data
|
|
961
|
+
bridgePriorities[this->nodeId] = bridgePriority;
|
|
962
|
+
|
|
884
963
|
this->sendBroadcast(msg);
|
|
885
964
|
|
|
886
965
|
Log(CONNECTION, "Bridge coordination sent: priority=%d, role=%s, load=%d%%\n",
|
|
@@ -1045,6 +1124,28 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1045
1124
|
return;
|
|
1046
1125
|
}
|
|
1047
1126
|
|
|
1127
|
+
// Validate RSSI threshold for single-candidate elections
|
|
1128
|
+
// When only one candidate exists, it indicates the node is isolated from the mesh.
|
|
1129
|
+
// In this case, require minimum signal quality to prevent poor connections.
|
|
1130
|
+
// When multiple candidates exist, the mesh is connected and best RSSI wins.
|
|
1131
|
+
if (electionCandidates.size() == 1 && winner->routerRSSI < minimumBridgeRSSI) {
|
|
1132
|
+
Log(CONNECTION, "=== Election Failed: Insufficient Signal Quality ===\n");
|
|
1133
|
+
Log(CONNECTION, " Single candidate with RSSI %d dBm (minimum required: %d dBm)\n",
|
|
1134
|
+
winner->routerRSSI, minimumBridgeRSSI);
|
|
1135
|
+
Log(CONNECTION, " Node is isolated from mesh with poor router signal\n");
|
|
1136
|
+
Log(CONNECTION, " Rejecting election to prevent unreliable bridge\n");
|
|
1137
|
+
Log(CONNECTION, " Recommendation: Move closer to router or wait for mesh connection\n");
|
|
1138
|
+
|
|
1139
|
+
electionState = ELECTION_IDLE;
|
|
1140
|
+
electionCandidates.clear();
|
|
1141
|
+
|
|
1142
|
+
// Notify via callback that election failed
|
|
1143
|
+
if (bridgeRoleChangedCallback) {
|
|
1144
|
+
bridgeRoleChangedCallback(false, "Insufficient signal quality for isolated bridge");
|
|
1145
|
+
}
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1048
1149
|
Log(CONNECTION, "=== Election Winner: Node %u ===\n", winner->nodeId);
|
|
1049
1150
|
Log(CONNECTION, " Router RSSI: %d dBm\n", winner->routerRSSI);
|
|
1050
1151
|
Log(CONNECTION, " Uptime: %u ms\n", winner->uptime);
|
|
@@ -1195,10 +1296,15 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1195
1296
|
(WiFi.localIP() != IPAddress(0, 0, 0, 0));
|
|
1196
1297
|
obj["internetConnected"] = hasInternet;
|
|
1197
1298
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1299
|
+
int8_t rssi = WiFi.RSSI();
|
|
1300
|
+
uint8_t channel = WiFi.channel();
|
|
1301
|
+
uint32_t uptime = millis();
|
|
1302
|
+
TSTRING gatewayIP = WiFi.gatewayIP().toString();
|
|
1303
|
+
|
|
1304
|
+
obj["routerRSSI"] = rssi;
|
|
1305
|
+
obj["routerChannel"] = channel;
|
|
1306
|
+
obj["uptime"] = uptime;
|
|
1307
|
+
obj["gatewayIP"] = gatewayIP;
|
|
1202
1308
|
obj["message_type"] = 610;
|
|
1203
1309
|
|
|
1204
1310
|
String msg;
|
|
@@ -1209,6 +1315,11 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1209
1315
|
Log(GENERAL, "sendBridgeStatus(): WiFi status=%d, localIP=%s, gatewayIP=%s\n",
|
|
1210
1316
|
WiFi.status(), WiFi.localIP().toString().c_str(), WiFi.gatewayIP().toString().c_str());
|
|
1211
1317
|
|
|
1318
|
+
// Update our own bridge status in knownBridges list
|
|
1319
|
+
// This ensures the bridge reports itself correctly when queried
|
|
1320
|
+
this->updateBridgeStatus(this->nodeId, hasInternet, rssi, channel,
|
|
1321
|
+
uptime, gatewayIP, this->getNodeTime());
|
|
1322
|
+
|
|
1212
1323
|
this->sendBroadcast(msg);
|
|
1213
1324
|
}
|
|
1214
1325
|
void eventHandleInit() {
|
|
@@ -1339,6 +1450,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1339
1450
|
TSTRING routerSSID = "";
|
|
1340
1451
|
TSTRING routerPassword = "";
|
|
1341
1452
|
uint32_t electionTimeoutMs = 5000; // Default 5 seconds
|
|
1453
|
+
int8_t minimumBridgeRSSI = -80; // Default -80 dBm minimum for isolated elections
|
|
1342
1454
|
uint32_t lastRoleChangeTime = 0;
|
|
1343
1455
|
ElectionState electionState = ELECTION_IDLE;
|
|
1344
1456
|
uint32_t electionDeadline = 0;
|
package/src/boost/asynctcp.hpp
CHANGED
|
@@ -233,8 +233,13 @@ class AsyncServer {
|
|
|
233
233
|
void begin() {
|
|
234
234
|
mAcceptor.open(tcp::v4());
|
|
235
235
|
int one = 1;
|
|
236
|
+
#ifdef _WIN32
|
|
237
|
+
setsockopt(mAcceptor.native_handle(), SOL_SOCKET,
|
|
238
|
+
SO_REUSEADDR | SO_REUSEPORT, reinterpret_cast<const char*>(&one), sizeof(one));
|
|
239
|
+
#else
|
|
236
240
|
setsockopt(mAcceptor.native_handle(), SOL_SOCKET,
|
|
237
241
|
SO_REUSEADDR | SO_REUSEPORT, &one, sizeof(one));
|
|
242
|
+
#endif
|
|
238
243
|
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), _port);
|
|
239
244
|
mAcceptor.set_option(boost::asio::socket_base::reuse_address(true));
|
|
240
245
|
mAcceptor.set_option(boost::asio::ip::tcp::no_delay(true));
|
package/src/painlessMesh.h
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* @file painlessMesh.h
|
|
6
6
|
* @brief Main header file for Alteriom painlessMesh library
|
|
7
7
|
*
|
|
8
|
-
* @version 1.8.
|
|
9
|
-
* @date 2025-11-
|
|
8
|
+
* @version 1.8.10
|
|
9
|
+
* @date 2025-11-18
|
|
10
10
|
*
|
|
11
11
|
* painlessMesh is a user-friendly library for creating mesh networks with
|
|
12
12
|
* ESP8266 and ESP32 devices. This Alteriom fork includes additional packages
|
|
@@ -10,6 +10,11 @@ namespace logger {
|
|
|
10
10
|
|
|
11
11
|
#define REMOTE_QUEUE_SIZE 10
|
|
12
12
|
|
|
13
|
+
// Windows defines ERROR as a macro, undefine it to avoid conflicts
|
|
14
|
+
#ifdef ERROR
|
|
15
|
+
#undef ERROR
|
|
16
|
+
#endif
|
|
17
|
+
|
|
13
18
|
typedef enum {
|
|
14
19
|
ERROR = 1 << 0,
|
|
15
20
|
STARTUP = 1 << 1,
|
|
@@ -132,7 +137,7 @@ class LogClass {
|
|
|
132
137
|
|
|
133
138
|
vsnprintf(str, 200, format, args);
|
|
134
139
|
|
|
135
|
-
remote_queue.push_back(std::pair<
|
|
140
|
+
remote_queue.push_back(std::pair<uint32_t, TSTRING>(remote_uuid, str));
|
|
136
141
|
++remote_uuid;
|
|
137
142
|
if (remote_queue.size() > REMOTE_QUEUE_SIZE) {
|
|
138
143
|
// No place to store the reason, but do signify the queue is full,
|
|
@@ -142,15 +147,15 @@ class LogClass {
|
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
149
|
|
|
145
|
-
std::list<std::pair<
|
|
150
|
+
std::list<std::pair<uint32_t, TSTRING>> &get_remote_queue() {
|
|
146
151
|
return remote_queue;
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
private:
|
|
150
155
|
uint16_t types = 0;
|
|
151
156
|
char str[200];
|
|
152
|
-
std::list<std::pair<
|
|
153
|
-
|
|
157
|
+
std::list<std::pair<uint32_t, TSTRING>> remote_queue;
|
|
158
|
+
uint32_t remote_uuid;
|
|
154
159
|
};
|
|
155
160
|
|
|
156
161
|
} // namespace logger
|
|
@@ -2285,7 +2285,7 @@ class Connection : public painlessmesh::layout::Neighbour,
|
|
|
2285
2285
|
quality -= (80 + rssi); // e.g., -90 dBm = penalty of 10
|
|
2286
2286
|
}
|
|
2287
2287
|
|
|
2288
|
-
return std::max(0, std::min(100, quality));
|
|
2288
|
+
return (std::max)(0, (std::min)(100, quality));
|
|
2289
2289
|
}
|
|
2290
2290
|
|
|
2291
2291
|
/**
|