@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 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
- **Solutions**:
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
- - Consider adjusting tiebreaker weightings
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
@@ -6,7 +6,7 @@
6
6
  "type": "git",
7
7
  "url": "https://github.com/Alteriom/painlessMesh"
8
8
  },
9
- "version": "1.8.8",
9
+ "version": "1.8.10",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
1
  name=Alteriom PainlessMesh
2
- version=1.8.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.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.7"
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 7
35
+ #define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 10
36
36
 
37
37
  /**
38
38
  * @brief Library description and usage information
@@ -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 broadcast when new nodes connect so they can discover the bridge immediately
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
- this->sendBridgeStatus();
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
- obj["routerRSSI"] = WiFi.RSSI();
1199
- obj["routerChannel"] = WiFi.channel();
1200
- obj["uptime"] = millis();
1201
- obj["gatewayIP"] = WiFi.gatewayIP().toString();
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;
@@ -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));
@@ -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.7
9
- * @date 2025-11-12
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<uint, TSTRING>(remote_uuid, str));
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<uint, TSTRING>> &get_remote_queue() {
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<uint, TSTRING>> remote_queue;
153
- uint remote_uuid;
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
  /**
@@ -380,7 +380,7 @@ class TimeSync : public PackageInterface {
380
380
  /**
381
381
  * Create a reply to the current message with the new time set
382
382
  */
383
- void reply(uint newT0) {
383
+ void reply(uint32_t newT0) {
384
384
  msg.t0 = newT0;
385
385
  ++msg.type;
386
386
  std::swap(from, dest);