@alteriom/painlessmesh 1.9.9 → 1.9.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
@@ -19,6 +19,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
19
 
20
20
  - TBD
21
21
 
22
+ ## [1.9.10] - 2025-12-15
23
+
24
+ ### Fixed
25
+
26
+ - **TCP Connection Retry Immediate Execution** - Fixed mesh connection failures where TCP retries executed immediately instead of with exponential backoff delays
27
+ - **Root Cause**: `PackageHandler::addTask()` was calling `task->enable()` instead of `task->enableDelayed()` for one-shot delayed tasks, causing immediate execution
28
+ - **Symptom**: Nodes unable to establish mesh connection - TCP error -14 (ERR_CONN) with all retry attempts happening immediately rather than with 1s, 2s, 4s, 8s, 8s delays
29
+ - **Solution**: Modified `PackageHandler::addTask()` to use `enableDelayed()` for `TASK_ONCE` tasks with intervals > 0
30
+ - Retry tasks now properly wait for their scheduled delays before executing
31
+ - Exponential backoff mechanism now works as designed (total ~23s before WiFi reconnection)
32
+ - Reduces network congestion from multiple simultaneously retrying nodes
33
+ - **Impact**: Enables successful mesh connection establishment with proper retry timing
34
+ - **Files Modified**:
35
+ - `src/painlessmesh/plugin.hpp` (lines 231, 239, 245-251)
36
+ - `docs/troubleshooting/common-issues.md` (lines 206-212)
37
+ - `test/catch/catch_delayed_task_execution.cpp` (new test file)
38
+ - `ISSUE_TCP_RETRY_FIX.md` (new documentation)
39
+
40
+ - **Hard Reset on Bridge Failover Election Winner** - Fixed ESP32 hard reset (Guru Meditation Error: Load access fault) when node becomes bridge after election
41
+ - **Root Cause**: The `bridgeRoleChangedCallback` signature used pass-by-value for the String parameter (`TSTRING reason`), causing temporary String object creation from const char* literals. On memory-constrained ESP32/ESP8266, this could trigger heap allocation failures and memory access faults
42
+ - **Symptom**: Device crashes with "Guru Meditation Error: Core 0 panic'ed (Load access fault)" immediately after logging "🎯 PROMOTED TO BRIDGE: Election winner - best router signal"
43
+ - **Solution**: Changed callback signature to use const reference (`const TSTRING& reason`) instead of pass-by-value
44
+ - Eliminates unnecessary String object copying and temporary creation
45
+ - Reduces memory pressure during callback invocation
46
+ - String literals are now directly bound to const references without heap allocation
47
+ - **Impact**: Eliminates hard resets during bridge promotion, allows stable failover operation
48
+ - **Breaking Change**: Users must update their callback function signatures from `void callback(bool, String)` to `void callback(bool, const String&)`
49
+ - **Files Modified**:
50
+ - `src/arduino/wifi.hpp` (lines 970, 2326)
51
+ - `examples/bridge_failover/bridge_failover.ino` (line 147)
52
+ - `README.md` (line 171)
53
+ - `USER_GUIDE.md` (line 731)
54
+
22
55
  ## [1.9.9] - 2025-12-14
23
56
 
24
57
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <div align="center">
6
6
 
7
- **Version 1.9.9** - Latest release with AsyncClient cleanup fixes for improved stability
7
+ **Version 1.9.10** - Latest release with critical bug fixes for TCP retry and bridge failover
8
8
 
9
9
  [![CI/CD Pipeline](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml/badge.svg)](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
10
10
  [![Documentation](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml/badge.svg)](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml)
@@ -168,7 +168,7 @@ mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
168
168
  mesh.enableBridgeFailover(true);
169
169
  mesh.onBridgeRoleChanged(&bridgeRoleCallback);
170
170
 
171
- void bridgeRoleCallback(bool isBridge, String reason) {
171
+ void bridgeRoleCallback(bool isBridge, const String& reason) {
172
172
  if (isBridge) {
173
173
  Serial.printf("🎯 Promoted to bridge: %s\n", reason.c_str());
174
174
  }
@@ -205,11 +205,12 @@ For Arduino IDE, install manually from: https://github.com/ESP32Async/AsyncTCP
205
205
 
206
206
  #### 2. Built-in Retry Mechanism
207
207
  painlessMesh now includes automatic TCP connection retry with the following behavior:
208
- - Up to 3 retry attempts with 500ms delay between each
209
- - 100ms stabilization delay after IP acquisition before first connection attempt
210
- - Full WiFi reconnection only triggered after all retries are exhausted
208
+ - Up to 6 total connection attempts (initial + 5 retries)
209
+ - Exponential backoff delays between retries: 1s, 2s, 4s, 8s, 8s (total ~23s)
210
+ - 500ms stabilization delay after IP acquisition before first connection attempt
211
+ - Full WiFi reconnection with 10s delay only triggered after all retries are exhausted
211
212
 
212
- This helps handle transient timing issues automatically.
213
+ This exponential backoff helps handle transient timing issues automatically while reducing network congestion from multiple retrying nodes.
213
214
 
214
215
  #### 3. Check Node Resource Usage
215
216
  Monitor memory and ensure nodes aren't overloaded:
@@ -144,7 +144,7 @@ void bridgeStatusCallback(uint32_t bridgeNodeId, bool hasInternet) {
144
144
  }
145
145
  }
146
146
 
147
- void bridgeRoleCallback(bool isBridge, String reason) {
147
+ void bridgeRoleCallback(bool isBridge, const String& reason) {
148
148
  if (isBridge) {
149
149
  Serial.printf("🎯 PROMOTED TO BRIDGE: %s\n", reason.c_str());
150
150
  Serial.println("This node is now the primary bridge!");
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.9.9",
9
+ "version": "1.9.10",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -1,5 +1,5 @@
1
1
  name=Alteriom PainlessMesh
2
- version=1.9.9
2
+ version=1.9.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.9.9",
3
+ "version": "1.9.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",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@alteriom/mqtt-schema": "^0.8.0",
43
- "@eslint/js": "^9.39.1",
43
+ "@eslint/js": "^9.39.2",
44
44
  "ajv": "^8.17.1",
45
45
  "ajv-formats": "^3.0.1",
46
46
  "prettier": "^3.7.4"
@@ -29,10 +29,10 @@
29
29
  /**
30
30
  * @brief AlteriomPainlessMesh library version information
31
31
  */
32
- #define ALTERIOM_PAINLESS_MESH_VERSION "1.9.9"
32
+ #define ALTERIOM_PAINLESS_MESH_VERSION "1.9.10"
33
33
  #define ALTERIOM_PAINLESS_MESH_VERSION_MAJOR 1
34
34
  #define ALTERIOM_PAINLESS_MESH_VERSION_MINOR 9
35
- #define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 9
35
+ #define ALTERIOM_PAINLESS_MESH_VERSION_PATCH 10
36
36
 
37
37
  /**
38
38
  * @brief Library description and usage information
@@ -967,7 +967,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
967
967
  * @param callback Function to call when role changes
968
968
  */
969
969
  void onBridgeRoleChanged(
970
- std::function<void(bool isBridge, TSTRING reason)> callback) {
970
+ std::function<void(bool isBridge, const TSTRING& reason)> callback) {
971
971
  bridgeRoleChangedCallback = callback;
972
972
  }
973
973
 
@@ -2323,7 +2323,7 @@ class Mesh : public painlessmesh::Mesh<Connection> {
2323
2323
  ElectionState electionState = ELECTION_IDLE;
2324
2324
  uint32_t electionDeadline = 0;
2325
2325
  std::vector<BridgeCandidate> electionCandidates;
2326
- std::function<void(bool isBridge, TSTRING reason)> bridgeRoleChangedCallback;
2326
+ std::function<void(bool isBridge, const TSTRING& reason)> bridgeRoleChangedCallback;
2327
2327
 
2328
2328
  // Isolated bridge retry state and configuration
2329
2329
  uint8_t _isolatedBridgeRetryAttempts = 0;
@@ -5,8 +5,8 @@
5
5
  * @file painlessMesh.h
6
6
  * @brief Main header file for Alteriom painlessMesh library
7
7
  *
8
- * @version 1.9.9
9
- * @date 2025-12-14
8
+ * @version 1.9.10
9
+ * @date 2025-12-15
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
@@ -228,7 +228,13 @@ class PackageHandler : public layout::Layout<T> {
228
228
  for (auto&& task : taskList) {
229
229
  if (task.use_count() == 1 && !task->isEnabled()) {
230
230
  task->set(aInterval, aIterations, aCallback, NULL, NULL);
231
- task->enable();
231
+ // Use enableDelayed() for delayed one-shot tasks to prevent immediate execution
232
+ // This ensures tasks with intervals execute after the delay, not immediately
233
+ if (aInterval > 0 && aIterations == TASK_ONCE) {
234
+ task->enableDelayed();
235
+ } else {
236
+ task->enable();
237
+ }
232
238
  return task;
233
239
  }
234
240
  }
@@ -236,7 +242,13 @@ class PackageHandler : public layout::Layout<T> {
236
242
  std::shared_ptr<Task> task =
237
243
  std::make_shared<Task>(aInterval, aIterations, aCallback);
238
244
  scheduler.addTask((*task));
239
- task->enable();
245
+ // Use enableDelayed() for delayed one-shot tasks to prevent immediate execution
246
+ // This ensures tasks with intervals execute after the delay, not immediately
247
+ if (aInterval > 0 && aIterations == TASK_ONCE) {
248
+ task->enableDelayed();
249
+ } else {
250
+ task->enable();
251
+ }
240
252
  taskList.push_front(task);
241
253
  return task;
242
254
  }