@alteriom/painlessmesh 1.8.9 → 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 +18 -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 +73 -3
- 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,24 @@ 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
|
+
|
|
10
28
|
## [1.8.9] - 2025-11-12
|
|
11
29
|
|
|
12
30
|
### Fixed
|
|
@@ -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
|
*
|
|
@@ -789,10 +804,42 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
789
804
|
this->sendBridgeStatus();
|
|
790
805
|
});
|
|
791
806
|
|
|
792
|
-
// 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
|
|
793
809
|
this->newConnectionCallbacks.push_back([this](uint32_t nodeId) {
|
|
794
|
-
Log(CONNECTION, "New node %u connected, sending bridge status\n", nodeId);
|
|
795
|
-
|
|
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
|
+
});
|
|
796
843
|
});
|
|
797
844
|
|
|
798
845
|
Log(STARTUP, "Bridge status broadcast enabled (interval: %d ms)\n",
|
|
@@ -1077,6 +1124,28 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1077
1124
|
return;
|
|
1078
1125
|
}
|
|
1079
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
|
+
|
|
1080
1149
|
Log(CONNECTION, "=== Election Winner: Node %u ===\n", winner->nodeId);
|
|
1081
1150
|
Log(CONNECTION, " Router RSSI: %d dBm\n", winner->routerRSSI);
|
|
1082
1151
|
Log(CONNECTION, " Uptime: %u ms\n", winner->uptime);
|
|
@@ -1381,6 +1450,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
|
|
|
1381
1450
|
TSTRING routerSSID = "";
|
|
1382
1451
|
TSTRING routerPassword = "";
|
|
1383
1452
|
uint32_t electionTimeoutMs = 5000; // Default 5 seconds
|
|
1453
|
+
int8_t minimumBridgeRSSI = -80; // Default -80 dBm minimum for isolated elections
|
|
1384
1454
|
uint32_t lastRoleChangeTime = 0;
|
|
1385
1455
|
ElectionState electionState = ELECTION_IDLE;
|
|
1386
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
|
/**
|