@alteriom/painlessmesh 1.9.19 → 1.10.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 +168 -0
- package/README.md +102 -63
- package/RELEASE_GUIDE.md +147 -8
- package/examples/alteriom/README.md +4 -4
- package/examples/alteriom/alteriom_custom_package_template.hpp +320 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +1 -1
- package/examples/alteriom/mppt_example/alteriom_mppt_example.ino +208 -0
- package/examples/bridge_failover/bridge_failover.ino +17 -0
- package/examples/sendToInternet/CMakeLists.txt +54 -0
- package/examples/sendToInternet/PC_NODE_README.md +517 -0
- package/examples/sendToInternet/README.md +39 -1
- package/examples/sendToInternet/build.sh +153 -0
- package/examples/sendToInternet/mock_server_test.ino +361 -0
- package/examples/sendToInternet/pc_mesh_node.cpp +361 -0
- package/examples/tcpRetryConfig/README.md +110 -0
- package/examples/tcpRetryConfig/platformio.ini +26 -0
- package/examples/tcpRetryConfig/tcpRetryConfig.ino +154 -0
- package/keywords.txt +3 -0
- package/library.json +4 -1
- package/library.properties +1 -1
- package/package.json +3 -3
- package/src/AlteriomPainlessMesh.h +6 -14
- package/src/arduino/wifi.hpp +352 -114
- package/src/connection.cpp +10 -0
- package/src/painlessMesh.h +2 -15
- package/src/painlessTaskOptions.h +9 -0
- package/src/painlessmesh/buffer.hpp +4 -1
- package/src/painlessmesh/configuration.hpp +13 -2
- package/src/painlessmesh/connection.hpp +36 -21
- package/src/painlessmesh/gateway.hpp +0 -1061
- package/src/painlessmesh/mesh.hpp +102 -107
- package/src/painlessmesh/message_queue.hpp +25 -15
- package/src/painlessmesh/metrics.hpp +2 -262
- package/src/painlessmesh/plugin.hpp +27 -5
- package/src/painlessmesh/tcp.hpp +158 -29
- package/src/painlessmesh/validation.hpp +0 -143
- package/docs/README.md +0 -132
- package/docs/alteriom/overview.md +0 -531
- package/docs/api/core-api.md +0 -607
- package/docs/api/shared-gateway.md +0 -1207
- package/docs/architecture/mesh-architecture.md +0 -399
- package/docs/architecture/plugin-system.md +0 -517
- package/docs/getting-started/arduino-manual-install.md +0 -313
- package/docs/getting-started/first-mesh.md +0 -410
- package/docs/getting-started/installation.md +0 -275
- package/docs/getting-started/quickstart.md +0 -158
- package/docs/troubleshooting/common-issues.md +0 -679
- package/docs/troubleshooting/debugging.md +0 -455
- package/docs/troubleshooting/external-device-connection.md +0 -283
- package/docs/troubleshooting/faq.md +0 -574
- package/docs/tutorials/basic-examples.md +0 -718
package/CHANGELOG.md
CHANGED
|
@@ -7,12 +7,180 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.10.0] - 2026-08-12
|
|
11
|
+
|
|
12
|
+
Feature release making the TCP connect-retry envelope tunable per mesh instance
|
|
13
|
+
(#378), alongside two documentation corrections that retire long-standing claims
|
|
14
|
+
the library never actually implemented (#385) and an example build fix (#360).
|
|
15
|
+
|
|
16
|
+
**Upgrading is behaviour-neutral.** Every new setting defaults to the value that
|
|
17
|
+
was previously hardcoded, so a sketch that does not call `setTcpRetryConfig()`
|
|
18
|
+
behaves exactly as it did on 1.9.21. Nothing was removed: the deprecated queue
|
|
19
|
+
macros keep their historical values for source compatibility.
|
|
20
|
+
|
|
21
|
+
> **Note for npm users:** v1.9.21 was never published to npm — the `NPM_TOKEN`
|
|
22
|
+
> used by CI had expired (#381), which failed the npm publish job while the
|
|
23
|
+
> GitHub Release, GitHub Packages, PlatformIO and Arduino channels all succeeded.
|
|
24
|
+
> npm's previous version is therefore **1.9.20**, and upgrading from npm brings
|
|
25
|
+
> in both 1.9.21 and 1.10.0. See the 1.9.21 entry below for what that release
|
|
26
|
+
> contained — it was a crash-fix release, and npm users have been missing it.
|
|
27
|
+
|
|
10
28
|
### Added
|
|
11
29
|
|
|
30
|
+
- **User-configurable TCP retry parameters (#378)** — the five TCP connect
|
|
31
|
+
retry values that were hardcoded as `static const` in
|
|
32
|
+
`src/painlessmesh/tcp.hpp` are now tunable per mesh instance via
|
|
33
|
+
`mesh.setTcpRetryConfig()` / `mesh.getTcpRetryConfig()`, using the new
|
|
34
|
+
`painlessmesh::tcp::TcpRetryConfig` struct (`maxRetries`, `retryDelayMs`,
|
|
35
|
+
`stabilizationDelayMs`, `exhaustionReconnectDelayMs`,
|
|
36
|
+
`failureBlockDurationMs`). This lets latency-sensitive meshes (see
|
|
37
|
+
discussion #368), high-reliability industrial deployments and
|
|
38
|
+
battery-powered nodes each pick their own retry envelope without forking
|
|
39
|
+
the library.
|
|
40
|
+
|
|
41
|
+
The struct's defaults are spelled as the existing constants, so **behaviour
|
|
42
|
+
is unchanged for any sketch that does not call the new setter**, and the
|
|
43
|
+
constants themselves remain in place. `maxRetries` is clamped to 10 and
|
|
44
|
+
`retryDelayMs` to 50–60000 ms, since an unbounded retry count is a
|
|
45
|
+
heap/recursion hazard and a zero delay produces a hot reconnect loop; the
|
|
46
|
+
remaining fields accept 0 as a meaningful "disable this step" value.
|
|
47
|
+
New `examples/tcpRetryConfig/` demonstrates real-time, high-reliability and
|
|
48
|
+
battery-saver profiles.
|
|
49
|
+
|
|
12
50
|
### Changed
|
|
13
51
|
|
|
52
|
+
- **`MessageQueue` documented honestly as a manual buffer (#385)** —
|
|
53
|
+
removed the "messages are automatically delivered when connection is
|
|
54
|
+
restored" claim from `MessageQueue` and the `mesh.enableMessageQueue`
|
|
55
|
+
/ `queueMessage` / `flushMessageQueue` doc comments. Nothing in the
|
|
56
|
+
library ever transmitted queued messages or observed connectivity
|
|
57
|
+
changes; the app has always owned the send loop. The docs now say so,
|
|
58
|
+
and the `flushMessageQueue` example shows the intended pattern of
|
|
59
|
+
wiring the drain into `onLocalInternetChanged`.
|
|
60
|
+
|
|
61
|
+
### Deprecated
|
|
62
|
+
|
|
63
|
+
- **Compatibility queue macros kept as ignored no-ops (#385)** —
|
|
64
|
+
`MIN_FREE_MEMORY` and `MAX_MESSAGE_QUEUE` remain defined in
|
|
65
|
+
`painlessmesh/configuration.hpp` (and `test/boost/Arduino.h`) for
|
|
66
|
+
source compatibility, but nothing in the library reads them. They were
|
|
67
|
+
placeholders for the auto-flush behavior that never landed.
|
|
68
|
+
`MessageQueue` has always taken its own per-instance `maxSize`
|
|
69
|
+
constructor argument. Their historical default values
|
|
70
|
+
(`MIN_FREE_MEMORY 4000`, `MAX_MESSAGE_QUEUE 50`) are preserved so any
|
|
71
|
+
downstream code that referenced the macros keeps its prior behavior.
|
|
72
|
+
|
|
73
|
+
### Fixed
|
|
74
|
+
|
|
75
|
+
- **`bridge_failover` example failed to compile (#360)** — the two
|
|
76
|
+
`mesh.onBridgeCoordination*` lambdas referenced
|
|
77
|
+
`plugin::BridgeCoordinationPackage` with a bare `plugin::` prefix, but
|
|
78
|
+
`painlessMesh.h` only lifts `painlessmesh::logger` to global scope, so
|
|
79
|
+
the type did not resolve (`'plugin' does not name a type`). Both lambda
|
|
80
|
+
parameters are now fully qualified as
|
|
81
|
+
`painlessmesh::plugin::BridgeCoordinationPackage`, matching the
|
|
82
|
+
convention used across every other example (otaSender, namedMesh,
|
|
83
|
+
alteriom_*).
|
|
84
|
+
|
|
85
|
+
## [1.9.21] - 2026-08-04
|
|
86
|
+
|
|
87
|
+
Crash-fix release resolving a family of use-after-free bugs in the task and
|
|
88
|
+
TCP-connection lifecycle. Root-caused and fixed by @vaz82 (PR #376) with
|
|
89
|
+
reports and field testing from @fidla73 and @miloshev (issue #373); finalized
|
|
90
|
+
with TaskScheduler's native self-destruct mechanism and regression coverage.
|
|
91
|
+
|
|
14
92
|
### Fixed
|
|
15
93
|
|
|
94
|
+
- **Use-after-free in `Task::disable()` on connection teardown (#373)** —
|
|
95
|
+
`scheduleAsyncClientDeletion()`'s cleanup task deleted itself inside its
|
|
96
|
+
own `onDisable` callback, but TaskScheduler's `Task::disable()` writes to
|
|
97
|
+
the task object after `onDisable` returns. Crashed nodes (StoreProhibited,
|
|
98
|
+
`EXCVADDR 0x8`) on every peer disconnect. The cleanup task now uses
|
|
99
|
+
TaskScheduler's `_TASK_SELF_DESTRUCT` support (enabled in
|
|
100
|
+
`painlessTaskOptions.h`): the Scheduler deletes the task from within
|
|
101
|
+
`execute()`, safely outside the `disable()` call stack.
|
|
102
|
+
- **`PackageHandler::stop()` destroying the currently-executing task** —
|
|
103
|
+
when `stop()` runs from within a task's own callback (bridge promotion
|
|
104
|
+
path), it destroyed that task's closure mid-execution via
|
|
105
|
+
`setCallback(NULL)`/`shared_ptr` release. `stop()` now accepts the
|
|
106
|
+
scheduler, detects the running task via `getCurrentTask()`, and leaves it
|
|
107
|
+
in `taskList` for safe reuse by `addTask()`.
|
|
108
|
+
- **Stale `_pcb` window in `~BufferedConnection()`** — `client->close()` was
|
|
109
|
+
skipped when `freeable()` returned true, leaving a non-null-but-stale pcb
|
|
110
|
+
that lwIP's timers could recycle during the deferred-deletion window
|
|
111
|
+
(`heap_caps_free`/`memp_free` assertion failures, `tcp_arg()` wild-pointer
|
|
112
|
+
stores). `close()` is now called unconditionally at destruction.
|
|
113
|
+
- **`onError`/`onConnect` double-handling race in `tcp::connect()`** — if
|
|
114
|
+
WiFi dropped as the TCP handshake completed, AsyncTCP could fire both
|
|
115
|
+
callbacks for the same `AsyncClient`, handing it to two owners and
|
|
116
|
+
scheduling its deletion twice. A shared claim guard now ensures exactly
|
|
117
|
+
one callback processes the client.
|
|
118
|
+
- **Bridge promotion state capture** — the deferred stop/reinit lambda in
|
|
119
|
+
`promoteToBridge()` (and the isolated-node variant) now captures mesh
|
|
120
|
+
credentials, scheduler, and callback by value so `stop()` cannot mutate
|
|
121
|
+
them before the reinit reads them.
|
|
122
|
+
- **Off-by-one buffer overflow in `ReceiveBuffer::push()`** — when a
|
|
123
|
+
received chunk was ≥ `TCP_MSS`, the null terminator was written one byte
|
|
124
|
+
past the end of the shared temp buffer, corrupting adjacent memory on
|
|
125
|
+
every large read. Found by the new AddressSanitizer CI job on its first
|
|
126
|
+
run; `read_len` now reserves one byte for the terminator.
|
|
127
|
+
|
|
128
|
+
### Added
|
|
129
|
+
|
|
130
|
+
- Regression test `catch_connection_cleanup.cpp` covering the #373
|
|
131
|
+
schedule → fire → self-destruct cleanup lifecycle and
|
|
132
|
+
`~BufferedConnection` churn.
|
|
133
|
+
- AddressSanitizer job in CI (gcc + `-fsanitize=address`) so use-after-free
|
|
134
|
+
and double-free regressions in the task/connection lifecycle fail the
|
|
135
|
+
build instead of crashing devices in the field.
|
|
136
|
+
|
|
137
|
+
## [1.9.20] - 2026-03-27
|
|
138
|
+
|
|
139
|
+
### Added
|
|
140
|
+
|
|
141
|
+
- **Bridge Coordination Monitoring Callbacks** - User-facing API for observing bridge coordination events
|
|
142
|
+
- `mesh.onBridgeCoordination(cb)` — fires on every coordination message (~30s per bridge)
|
|
143
|
+
- `mesh.onBridgeCoordinationChanged(cb)` — fires on state changes: "new", "updated", "lost"
|
|
144
|
+
- Works on both bridge and non-bridge nodes (monitoring is not bridge-only)
|
|
145
|
+
- Lost bridge detection with 60-second timeout
|
|
146
|
+
- 5 new test scenarios with 18 assertions for change detection logic
|
|
147
|
+
|
|
148
|
+
### Fixed
|
|
149
|
+
|
|
150
|
+
- **Full Repo Cleanup** - Comprehensive audit and cleanup of the entire codebase (#357)
|
|
151
|
+
- Fix double-free of mScheduler in ~Mesh() destructor
|
|
152
|
+
- Fix int8_t RSSI aggregation overflow (widened to int32_t)
|
|
153
|
+
- Fix Task object memory leak in scheduleAsyncClientDeletion
|
|
154
|
+
- Replace blocking delay(1000) with yield-based approach in bridge init
|
|
155
|
+
- Add max-iteration guard to getPathToNode (prevents infinite loop)
|
|
156
|
+
- Move static vars from connection.hpp to connection.cpp (ODR violation fix)
|
|
157
|
+
- Initialize shouldContainRoot to false (undefined behavior fix)
|
|
158
|
+
- Fix getAccurateTime() to always return seconds
|
|
159
|
+
- Fix cancelInternetRequest reentrancy via scheduled callback
|
|
160
|
+
- Widen BFS hop counter from uint8_t to uint16_t
|
|
161
|
+
- Remove dead revert path in promoteToBridge
|
|
162
|
+
- Cap knownBridgePeers at 32 entries
|
|
163
|
+
- Increase WiFi event semaphore timeout from 100 to 1000 ticks
|
|
164
|
+
- Inline getGatewayCount() to avoid temporary vector allocation
|
|
165
|
+
- Remove MessageQueue::reserve(1000) (wasted heap on ESP8266)
|
|
166
|
+
- Fix Timer::elapsed_us() to return microseconds on ESP32
|
|
167
|
+
- Cache hasActualInternetAccess() with 60s TTL
|
|
168
|
+
- Remove ~3,600 lines of dead code (unused classes, stubs, AI-generated shelf code)
|
|
169
|
+
- Remove 40 AI-generated markdown files from root directory
|
|
170
|
+
- Delete abandoned documentation systems (docs-website/, website/)
|
|
171
|
+
- Fix all CI/CD workflow action versions
|
|
172
|
+
- Remove 15 fake test files (only REQUIRE(true) assertions)
|
|
173
|
+
- Fix millis()/micros() test mocks to prevent uint32_t overflow
|
|
174
|
+
- **Documentation Consistency** - Feature audit and doc fixes (#358)
|
|
175
|
+
- Fix README version (1.9.17 -> 1.9.20)
|
|
176
|
+
- Fix 6 broken documentation links
|
|
177
|
+
- Fix Message Queue API examples to use real enableMessageQueue/queueMessage API
|
|
178
|
+
- Remove non-existent onBridgeCoordination callback from examples
|
|
179
|
+
- Fix EnhancedStatusPackage type ID comment (203 -> 604)
|
|
180
|
+
- Rewrite Quick Start example to use TaskScheduler instead of delay()
|
|
181
|
+
- Remove dead conditional includes from painlessMesh.h
|
|
182
|
+
- Add missing examples to library.json
|
|
183
|
+
|
|
16
184
|
## [1.9.19] - 2025-12-21
|
|
17
185
|
|
|
18
186
|
### Fixed
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
|
|
7
|
-
**Version 1.
|
|
7
|
+
**Version 1.10.0** - Tunable TCP connect-retry behaviour via `setTcpRetryConfig()` (#378)
|
|
8
8
|
|
|
9
9
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/ci.yml)
|
|
10
10
|
[](https://github.com/Alteriom/painlessMesh/actions/workflows/docs.yml)
|
|
@@ -209,11 +209,25 @@ mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD,
|
|
|
209
209
|
ROUTER_SSID, ROUTER_PASSWORD,
|
|
210
210
|
&userScheduler, MESH_PORT, 10);
|
|
211
211
|
|
|
212
|
-
//
|
|
213
|
-
mesh.
|
|
212
|
+
// Enable bridge failover for high availability
|
|
213
|
+
mesh.enableBridgeFailover(true);
|
|
214
|
+
|
|
215
|
+
// Monitor bridge coordination (fires every ~30s per bridge)
|
|
216
|
+
mesh.onBridgeCoordination(
|
|
217
|
+
[](const plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode) {
|
|
218
|
+
Serial.printf("Bridge %u: priority=%d, load=%d%%\n",
|
|
219
|
+
fromNode, pkg.priority, pkg.load);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
214
222
|
|
|
215
|
-
//
|
|
216
|
-
mesh.
|
|
223
|
+
// Get notified when bridge state changes
|
|
224
|
+
mesh.onBridgeCoordinationChanged(
|
|
225
|
+
[](const plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode,
|
|
226
|
+
TSTRING changeType) {
|
|
227
|
+
Serial.printf("Bridge %s: %u (role=%s)\n",
|
|
228
|
+
changeType.c_str(), fromNode, pkg.role.c_str());
|
|
229
|
+
}
|
|
230
|
+
);
|
|
217
231
|
```
|
|
218
232
|
|
|
219
233
|
See [BRIDGE_TO_INTERNET.md](BRIDGE_TO_INTERNET.md) for multi-bridge documentation.
|
|
@@ -239,16 +253,11 @@ See [BRIDGE_TO_INTERNET.md](BRIDGE_TO_INTERNET.md) for multi-bridge documentatio
|
|
|
239
253
|
**Example:**
|
|
240
254
|
```cpp
|
|
241
255
|
// Enable message queue with max 100 messages
|
|
242
|
-
mesh.enableMessageQueue(true);
|
|
243
|
-
mesh.setMaxQueueSize(100);
|
|
256
|
+
mesh.enableMessageQueue(true, 100);
|
|
244
257
|
|
|
245
258
|
// Queue critical alarm message
|
|
246
259
|
String criticalAlarm = "{\"sensor\":\"O2\",\"value\":2.5,\"alarm\":true}";
|
|
247
|
-
mesh.queueMessage(
|
|
248
|
-
|
|
249
|
-
// Set callbacks
|
|
250
|
-
mesh.onQueueFull(&queueFullCallback);
|
|
251
|
-
mesh.onQueueFlushed(&queueFlushedCallback);
|
|
260
|
+
mesh.queueMessage(bridgeNodeId, criticalAlarm);
|
|
252
261
|
```
|
|
253
262
|
|
|
254
263
|
See [BRIDGE_TO_INTERNET.md](BRIDGE_TO_INTERNET.md) for message queue documentation.
|
|
@@ -393,46 +402,34 @@ If platformio is used to install the library, then the dependencies will be inst
|
|
|
393
402
|
|
|
394
403
|
```cpp
|
|
395
404
|
#include "painlessMesh.h"
|
|
396
|
-
#include "examples/alteriom/alteriom_sensor_package.hpp"
|
|
397
|
-
|
|
398
|
-
using namespace alteriom;
|
|
399
405
|
|
|
400
|
-
#define MESH_PREFIX
|
|
401
|
-
#define MESH_PASSWORD
|
|
402
|
-
#define MESH_PORT
|
|
406
|
+
#define MESH_PREFIX "whateverYouLike"
|
|
407
|
+
#define MESH_PASSWORD "somethingSneaky"
|
|
408
|
+
#define MESH_PORT 5555
|
|
403
409
|
|
|
404
410
|
Scheduler userScheduler;
|
|
405
411
|
painlessMesh mesh;
|
|
406
412
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
413
|
+
Task taskSendMessage(TASK_SECOND * 30, TASK_FOREVER, []() {
|
|
414
|
+
String msg = "Hello from node ";
|
|
415
|
+
msg += mesh.getNodeId();
|
|
416
|
+
mesh.sendBroadcast(msg);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
void receivedCallback(uint32_t from, String &msg) {
|
|
420
|
+
Serial.printf("Received from %u: %s\n", from, msg.c_str());
|
|
411
421
|
}
|
|
412
422
|
|
|
413
|
-
void
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
sensor.humidity = 60.0;
|
|
420
|
-
sensor.sensorId = mesh.getNodeId();
|
|
421
|
-
sensor.timestamp = mesh.getNodeTime();
|
|
422
|
-
|
|
423
|
-
mesh.sendBroadcast(sensor.toJsonString());
|
|
424
|
-
delay(30000); // Send every 30 seconds
|
|
423
|
+
void setup() {
|
|
424
|
+
Serial.begin(115200);
|
|
425
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
426
|
+
mesh.onReceive(&receivedCallback);
|
|
427
|
+
userScheduler.addTask(taskSendMessage);
|
|
428
|
+
taskSendMessage.enable();
|
|
425
429
|
}
|
|
426
430
|
|
|
427
|
-
void
|
|
428
|
-
|
|
429
|
-
deserializeJson(doc, msg);
|
|
430
|
-
|
|
431
|
-
if (doc["type"] == 200) { // SensorPackage
|
|
432
|
-
SensorPackage sensor(doc.as<JsonObject>());
|
|
433
|
-
Serial.printf("Sensor %u: %.1f°C, %.1f%% RH\n",
|
|
434
|
-
sensor.sensorId, sensor.temperature, sensor.humidity);
|
|
435
|
-
}
|
|
431
|
+
void loop() {
|
|
432
|
+
mesh.update();
|
|
436
433
|
}
|
|
437
434
|
```
|
|
438
435
|
|
|
@@ -563,14 +560,36 @@ These are the message types used by applications built on painlessMesh:
|
|
|
563
560
|
- **Event Coordination** - Synchronized displays, distributed processing
|
|
564
561
|
- **Bridge Networks** - Connect mesh to WiFi/Internet/MQTT - [📖 Bridge Guide](BRIDGE_TO_INTERNET.md)
|
|
565
562
|
|
|
566
|
-
## Latest Release: v1.
|
|
563
|
+
## Latest Release: v1.10.0 (August 12, 2026)
|
|
564
|
+
|
|
565
|
+
**Tunable TCP Connect-Retry Behaviour (issue #378)**
|
|
566
|
+
|
|
567
|
+
- The five TCP connect-retry values that were hardcoded in `tcp.hpp` are now tunable per mesh instance via `mesh.setTcpRetryConfig()` / `mesh.getTcpRetryConfig()` (#378, PR #395)
|
|
568
|
+
- Defaults match the previous constants exactly — **no behaviour change unless you call the setter**
|
|
569
|
+
- New `examples/tcpRetryConfig/` with real-time, high-reliability and battery-saver profiles
|
|
570
|
+
- `MessageQueue` documentation corrected: it is a manual buffer, never an auto-flush queue (#385)
|
|
571
|
+
- `MIN_FREE_MEMORY` / `MAX_MESSAGE_QUEUE` deprecated as ignored no-ops, values preserved for source compatibility (#385)
|
|
572
|
+
- Fixed `bridge_failover` example failing to compile on an unqualified `plugin::` type (#360)
|
|
573
|
+
|
|
574
|
+
> **npm users:** v1.9.21 was never published to npm ([#381](https://github.com/Alteriom/painlessMesh/issues/381) — expired token). npm's previous version is 1.9.20, so upgrading from npm picks up both releases. GitHub, PlatformIO and Arduino were unaffected.
|
|
575
|
+
|
|
576
|
+
**Previous Release: v1.9.21 (August 4, 2026)**
|
|
577
|
+
|
|
578
|
+
**Crash Fixes: Task & TCP Connection Lifecycle (issue #373)**
|
|
579
|
+
|
|
580
|
+
- Fixed use-after-free in `Task::disable()` that crashed nodes on every peer disconnect (#373, PR #376 by @vaz82)
|
|
581
|
+
- Fixed `PackageHandler::stop()` destroying the currently-executing task during bridge promotion
|
|
582
|
+
- Fixed stale-pcb heap corruption window in `~BufferedConnection()` and an `onError`/`onConnect` double-handling race
|
|
583
|
+
- New AddressSanitizer CI job and regression test for the task/connection cleanup lifecycle
|
|
567
584
|
|
|
568
|
-
**
|
|
585
|
+
**Previous Release: v1.9.20 (March 27, 2026)** — Full repo cleanup, bug fixes & bridge coordination callbacks:
|
|
569
586
|
|
|
570
|
-
-
|
|
571
|
-
-
|
|
572
|
-
-
|
|
573
|
-
-
|
|
587
|
+
- New `onBridgeCoordination()` and `onBridgeCoordinationChanged()` monitoring callbacks
|
|
588
|
+
- Fixed 13 critical/high/medium bugs (double-free, RSSI overflow, memory leaks, blocking delays)
|
|
589
|
+
- Removed ~3,600 lines of dead code and 40 AI-generated docs
|
|
590
|
+
- Fixed all CI/CD workflows (corrected action versions)
|
|
591
|
+
- Removed 15 fake test files, fixed test infrastructure
|
|
592
|
+
- Documentation consistency audit — fixed broken links, API examples, version references
|
|
574
593
|
|
|
575
594
|
**Recent Key Features (v1.9.0 - v1.9.16):**
|
|
576
595
|
|
|
@@ -578,16 +597,16 @@ These are the message types used by applications built on painlessMesh:
|
|
|
578
597
|
- 🌉 **Improved Bridge Detection** - `getPrimaryBridge()` returns last known bridge when disconnected
|
|
579
598
|
- ⚡ **Enhanced TCP Reliability** - Exponential backoff and increased retries for mesh connections
|
|
580
599
|
- 🛡️ **Race Condition Fixes** - Improved bridge status and connection validation
|
|
581
|
-
- 📦 **Consolidated Examples** - Streamlined to
|
|
600
|
+
- 📦 **Consolidated Examples** - Streamlined to 15 essential examples
|
|
582
601
|
- ⚙️ **Configurable Election Timing** - Prevent split-brain with `setElectionStartupDelay()` and `setElectionRandomDelay()`
|
|
583
602
|
|
|
584
603
|
**[📋 Full CHANGELOG](CHANGELOG.md)**
|
|
585
604
|
|
|
586
605
|
## Getting Help
|
|
587
606
|
|
|
588
|
-
- **[FAQ](
|
|
589
|
-
- **[Common Issues](
|
|
590
|
-
- **[GitHub Issues](https://github.com/Alteriom/painlessMesh/issues)** - Bug reports and feature requests
|
|
607
|
+
- **[FAQ](USER_GUIDE.md)** - Common questions and solutions
|
|
608
|
+
- **[Common Issues](USER_GUIDE.md)** - Troubleshooting guide
|
|
609
|
+
- **[GitHub Issues](https://github.com/Alteriom/painlessMesh/issues)** - Bug reports and feature requests
|
|
591
610
|
- **[Community Forum](https://groups.google.com/forum/#!forum/painlessmesh-user)** - Community support
|
|
592
611
|
- **[API Documentation](https://alteriom.github.io/painlessMesh/#/api/doxygen)** - Generated API docs
|
|
593
612
|
|
|
@@ -610,6 +629,28 @@ run-parts --regex catch_ bin/ # Run tests
|
|
|
610
629
|
- **Dependencies**: ArduinoJson 7.x, TaskScheduler 4.x
|
|
611
630
|
- **Development**: CMake, Ninja, Boost (for desktop testing)
|
|
612
631
|
|
|
632
|
+
### Testing Bridge/Internet Functionality
|
|
633
|
+
|
|
634
|
+
painlessMesh includes a **Mock HTTP Server** for testing `sendToInternet()` functionality without requiring actual Internet connectivity. This enables:
|
|
635
|
+
|
|
636
|
+
- 🚀 **Fast testing cycles** - Instant responses instead of waiting for external APIs
|
|
637
|
+
- 🔧 **Offline development** - No Internet connection required
|
|
638
|
+
- ✅ **Reproducible scenarios** - Control all test conditions precisely
|
|
639
|
+
- 🤖 **CI/CD automation** - Automated testing in pipelines
|
|
640
|
+
|
|
641
|
+
```bash
|
|
642
|
+
# Start mock server
|
|
643
|
+
cd test/mock-http-server
|
|
644
|
+
python3 server.py
|
|
645
|
+
|
|
646
|
+
# Test various HTTP scenarios
|
|
647
|
+
curl http://localhost:8080/status/200 # Success
|
|
648
|
+
curl http://localhost:8080/status/404 # Not Found
|
|
649
|
+
curl http://localhost:8080/whatsapp?... # WhatsApp API simulation
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
See [Mock HTTP Server Documentation](test/mock-http-server/README.md) for complete usage guide.
|
|
653
|
+
|
|
613
654
|
### CI/CD Pipeline
|
|
614
655
|
|
|
615
656
|
painlessMesh features a state-of-the-art automated CI/CD pipeline:
|
|
@@ -651,8 +692,6 @@ We try to follow the [git flow](https://www.atlassian.com/git/tutorials/comparin
|
|
|
651
692
|
|
|
652
693
|
If you like the library please consider supporting its development. Your contributions help me spend more time improving painlessMesh.
|
|
653
694
|
|
|
654
|
-
[](https://www.paypal.com/paypalme/domlavoie)
|
|
655
|
-
|
|
656
695
|
**[Donate via PayPal](https://www.paypal.com/paypalme/domlavoie)** • [dominic.lavoie@gmail.com](mailto:dominic.lavoie@gmail.com)
|
|
657
696
|
|
|
658
697
|
## 📚 Documentation
|
|
@@ -669,18 +708,18 @@ If you like the library please consider supporting its development. Your contrib
|
|
|
669
708
|
### 🚀 Quick Links
|
|
670
709
|
|
|
671
710
|
**New to AlteriomPainlessMesh?**
|
|
672
|
-
- [Quick Start](
|
|
673
|
-
- [Installation](
|
|
674
|
-
- [First Mesh](
|
|
711
|
+
- [Quick Start](USER_GUIDE.md) - Get your first mesh running in 5 minutes
|
|
712
|
+
- [Installation](USER_GUIDE.md) - Arduino IDE and PlatformIO setup
|
|
713
|
+
- [First Mesh](USER_GUIDE.md) - Build a multi-node network
|
|
675
714
|
|
|
676
715
|
**Reference Documentation:**
|
|
677
|
-
- [Core API](
|
|
678
|
-
- [Alteriom Extensions](
|
|
679
|
-
- [Examples](examples/) -
|
|
716
|
+
- [Core API](USER_GUIDE.md) - painlessMesh class methods
|
|
717
|
+
- [Alteriom Extensions](examples/alteriom/README.md) - SensorPackage, CommandPackage, StatusPackage
|
|
718
|
+
- [Examples](examples/) - 15 working examples for common scenarios
|
|
680
719
|
|
|
681
720
|
**Need Help?**
|
|
682
|
-
- [FAQ](
|
|
683
|
-
- [Common Issues](
|
|
721
|
+
- [FAQ](USER_GUIDE.md) - Frequently asked questions
|
|
722
|
+
- [Common Issues](USER_GUIDE.md) - Troubleshooting guide
|
|
684
723
|
- [GitHub Issues](https://github.com/Alteriom/painlessMesh/issues) - Bug reports and support
|
|
685
724
|
|
|
686
725
|
## 🔧 Quick API Reference
|
package/RELEASE_GUIDE.md
CHANGED
|
@@ -209,11 +209,22 @@ Each release triggers the **PlatformIO Library Publishing** workflow:
|
|
|
209
209
|
|
|
210
210
|
#### Automatic Workflow Trigger
|
|
211
211
|
|
|
212
|
-
The PlatformIO workflow
|
|
212
|
+
The PlatformIO workflow is started by:
|
|
213
213
|
|
|
214
|
-
-
|
|
214
|
+
- The `platformio-dispatch` job in **Automated Release**, which calls
|
|
215
|
+
`gh workflow run platformio-publish.yml --ref v<version> -f version=<version>`
|
|
216
|
+
right after the release is created
|
|
217
|
+
- A GitHub release published by a human (via the UI or a PAT)
|
|
215
218
|
- Manual workflow dispatch for testing
|
|
216
219
|
|
|
220
|
+
> **Why the explicit dispatch?** `platformio-publish.yml` also listens for
|
|
221
|
+
> `release: published`, but that event never fires for releases created by
|
|
222
|
+
> `release.yml`: GitHub suppresses events raised by the built-in `GITHUB_TOKEN`.
|
|
223
|
+
> `workflow_dispatch` is one of the two documented exceptions to that rule, so
|
|
224
|
+
> the release workflow dispatches the publish explicitly and then verifies a run
|
|
225
|
+
> actually appeared. Before this was added, PlatformIO publication silently did
|
|
226
|
+
> not happen and had to be dispatched by hand (v1.9.21).
|
|
227
|
+
|
|
217
228
|
### PlatformIO Package Contents
|
|
218
229
|
|
|
219
230
|
Published package includes:
|
|
@@ -454,13 +465,127 @@ npm run build
|
|
|
454
465
|
npm run test
|
|
455
466
|
```
|
|
456
467
|
|
|
457
|
-
**NPM Token Invalid**
|
|
468
|
+
**NPM Token Expired / Invalid (`E401 Unauthorized`)**
|
|
469
|
+
|
|
470
|
+
Symptom: the `npm-publish` job fails at *Verify NPM authentication* with
|
|
471
|
+
`401 Unauthorized - GET https://registry.npmjs.org/-/whoami`. npm tokens
|
|
472
|
+
expire; everything else in the release (tag, GitHub Release, zip asset,
|
|
473
|
+
GitHub Packages, PlatformIO) succeeds independently, so **the release can look
|
|
474
|
+
green-ish while npmjs.org is missing the version**. Always confirm with
|
|
475
|
+
`npm view @alteriom/painlessmesh version`.
|
|
476
|
+
|
|
477
|
+
Rotating the token is operator-only — it cannot be automated from CI:
|
|
478
|
+
|
|
458
479
|
```bash
|
|
459
|
-
#
|
|
460
|
-
|
|
461
|
-
#
|
|
480
|
+
# 1. Mint a fresh granular token, scoped to @alteriom/painlessmesh, with
|
|
481
|
+
# "Read and write" AND the "Bypass 2FA" option enabled <-- see EOTP below
|
|
482
|
+
# https://www.npmjs.com/settings/tokens
|
|
483
|
+
# 2. Verify the new token before saving it (recommended).
|
|
484
|
+
# Ask the registry directly — do NOT use a bare `npm whoami`, which answers
|
|
485
|
+
# for whatever credential your local ~/.npmrc already holds and will happily
|
|
486
|
+
# pass while the new token is bad:
|
|
487
|
+
curl -sS -H "Authorization: Bearer <new-token>" \
|
|
488
|
+
https://registry.npmjs.org/-/whoami # -> {"username":"..."} , not 401
|
|
489
|
+
|
|
490
|
+
# 3. Update the NPM_TOKEN *organisation* secret — NOT a repository secret.
|
|
491
|
+
# painlessMesh has no repo-level NPM_TOKEN and must not gain one; see
|
|
492
|
+
# "Where NPM_TOKEN actually lives" below.
|
|
493
|
+
# https://github.com/organizations/Alteriom/settings/secrets/actions
|
|
494
|
+
|
|
495
|
+
# 4a. Re-run the failed release job (keeps the original run's context)
|
|
496
|
+
gh run rerun <run-id> --failed --repo Alteriom/painlessMesh
|
|
497
|
+
|
|
498
|
+
# 4b. …or republish a missed version out-of-band. Pass the TAG as ref:
|
|
499
|
+
# without it the workflow builds the default branch, and if main has moved
|
|
500
|
+
# on since the tag it would upload today's code under the old version
|
|
501
|
+
# number. The workflow now refuses that outright — pass ref so you never
|
|
502
|
+
# have to rely on the guard catching it.
|
|
503
|
+
gh workflow run manual-publish.yml --repo Alteriom/painlessMesh \
|
|
504
|
+
-f ref=v1.9.21 -f publish_npm=true -f publish_github=false
|
|
505
|
+
|
|
506
|
+
# 5. Confirm the version actually landed
|
|
507
|
+
npm view @alteriom/painlessmesh version
|
|
462
508
|
```
|
|
463
509
|
|
|
510
|
+
#### Where NPM_TOKEN actually lives
|
|
511
|
+
|
|
512
|
+
`NPM_TOKEN` is an **organisation** secret on `Alteriom`, shared by every repo
|
|
513
|
+
that publishes to npm. painlessMesh has **no repository-level copy**, and adding
|
|
514
|
+
one is a trap rather than a tightening:
|
|
515
|
+
|
|
516
|
+
> A repository secret silently takes precedence over an organisation secret of
|
|
517
|
+
> the same name. The repo then stops seeing org-wide rotations and keeps using
|
|
518
|
+
> its own copy until that copy expires — which is invisible until a release day
|
|
519
|
+
> fails.
|
|
520
|
+
|
|
521
|
+
Two sibling repos already sit in that state, with repo-level `NPM_TOKEN` copies
|
|
522
|
+
that shadow the org secret (`webhook-client`, `repository-metadata-manager`).
|
|
523
|
+
Rotate the org secret and those two are still broken; delete the repo-level copy
|
|
524
|
+
and they inherit the fresh one. Check before assuming a rotation reached a repo:
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
# Empty output = good (inherits the org secret)
|
|
528
|
+
gh api repos/Alteriom/<repo>/actions/secrets \
|
|
529
|
+
--jq '.secrets[] | select(.name=="NPM_TOKEN") | "SHADOWED, updated \(.updated_at)"'
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
While rotating `NPM_TOKEN`, check `PLATFORMIO_AUTH_TOKEN` too — it expires the
|
|
533
|
+
same way and `platformio-publish.yml` hard-fails on an invalid one.
|
|
534
|
+
|
|
535
|
+
**NPM asks for a one-time password (`EOTP`)**
|
|
536
|
+
|
|
537
|
+
Symptom: authentication *succeeds* — `npm whoami` prints the username — and then
|
|
538
|
+
`npm publish` fails with:
|
|
539
|
+
|
|
540
|
+
```
|
|
541
|
+
npm error code EOTP
|
|
542
|
+
npm error This operation requires a one-time password from your authenticator.
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
The token is valid but is not allowed to bypass 2FA, and CI has no authenticator
|
|
546
|
+
to answer the challenge with. **A rotation that fixes `E401` lands here if the
|
|
547
|
+
replacement token is minted without the bypass option** — which is what happened
|
|
548
|
+
on the second rotation attempt for #381.
|
|
549
|
+
|
|
550
|
+
npm removed the legacy token types (`read-only` / `automation` / `publish`) in
|
|
551
|
+
**November 2025**; only granular access tokens exist now. The old *Automation*
|
|
552
|
+
token bypassed 2FA by virtue of its type, so this was never a decision anyone had
|
|
553
|
+
to make. On a granular token it is an explicit checkbox, and a token minted from
|
|
554
|
+
muscle memory does not have it:
|
|
555
|
+
|
|
556
|
+
> **Bypass 2FA** — required. Takes precedence over account-level and
|
|
557
|
+
> package-level 2FA settings for publishing.
|
|
558
|
+
|
|
559
|
+
Re-mint at <https://www.npmjs.com/settings/tokens> with *Read and write* on
|
|
560
|
+
`@alteriom/painlessmesh` **and Bypass 2FA enabled**, update the secret, re-run.
|
|
561
|
+
`npm whoami` cannot detect this ahead of time — it passes for both token kinds,
|
|
562
|
+
so the failure necessarily surfaces at the publish call.
|
|
563
|
+
|
|
564
|
+
### Trusted publishing (OIDC) — the way out of token rotation
|
|
565
|
+
|
|
566
|
+
Both failures above are symptoms of the same thing: a long-lived credential that
|
|
567
|
+
expires silently and is only exercised on release day. npm's replacement is
|
|
568
|
+
**trusted publishing** — the workflow authenticates to npm over OIDC, and
|
|
569
|
+
`NPM_TOKEN` stops existing.
|
|
570
|
+
|
|
571
|
+
This is on a clock rather than merely being nicer: as of **2026-07-31** bypass-2FA
|
|
572
|
+
tokens can no longer manage tokens, package access, or trusted-publishing config,
|
|
573
|
+
and npm has targeted **January 2027** for removing *direct publish* from them —
|
|
574
|
+
after which they can only stage a publish for a maintainer to approve with 2FA.
|
|
575
|
+
The current setup stops working at that point.
|
|
576
|
+
|
|
577
|
+
Requirements, none of which this repo blocks on today:
|
|
578
|
+
|
|
579
|
+
| Requirement | Status here |
|
|
580
|
+
|---|---|
|
|
581
|
+
| `id-token: write` permission | ✅ already set in `release.yml` and `manual-publish.yml` |
|
|
582
|
+
| npm CLI ≥ 11.5.1, Node ≥ 22.14.0 | ❌ workflows pin `node-version: '18'` — needs a bump |
|
|
583
|
+
| Trusted publisher registered on npmjs.com | ❌ operator, one-time, per workflow file |
|
|
584
|
+
|
|
585
|
+
The npmjs.com side is under *Package settings → Trusted publisher*: org
|
|
586
|
+
`Alteriom`, repository `painlessMesh`, workflow filename `release.yml` (add a
|
|
587
|
+
second entry for `manual-publish.yml` if that path should keep working).
|
|
588
|
+
|
|
464
589
|
**GitHub Packages Authentication**
|
|
465
590
|
```bash
|
|
466
591
|
# Check if GITHUB_TOKEN has packages:write permission
|
|
@@ -508,11 +633,18 @@ If this happens, you can manually publish packages:
|
|
|
508
633
|
4. Click **Run workflow**
|
|
509
634
|
|
|
510
635
|
The manual workflow will:
|
|
511
|
-
- Read the current version from `library.properties`
|
|
636
|
+
- Read the current version from `library.properties` and refuse to run if it
|
|
637
|
+
disagrees with `package.json` (npm publishes the `package.json` version)
|
|
638
|
+
- Validate `NPM_TOKEN` against the registry before attempting to publish, so an
|
|
639
|
+
expired token fails immediately with rotation instructions
|
|
512
640
|
- Publish to NPM (if selected)
|
|
513
641
|
- Publish to GitHub Packages (if selected)
|
|
514
642
|
- Show success/failure status for each
|
|
515
643
|
|
|
644
|
+
It does **not** publish to the PlatformIO registry — use
|
|
645
|
+
`gh workflow run platformio-publish.yml --ref v<version> -f version=<version>`
|
|
646
|
+
for that.
|
|
647
|
+
|
|
516
648
|
Alternatively, from command line:
|
|
517
649
|
```bash
|
|
518
650
|
# Trigger via GitHub CLI
|
|
@@ -647,9 +779,16 @@ Monitor your releases:
|
|
|
647
779
|
### Required GitHub Secrets
|
|
648
780
|
|
|
649
781
|
- `GITHUB_TOKEN`: Automatically provided by GitHub Actions
|
|
650
|
-
- `NPM_TOKEN`: Required for NPM publishing
|
|
782
|
+
- `NPM_TOKEN`: Required for NPM publishing. Lives in the **Alteriom
|
|
783
|
+
organisation** secrets and is inherited — do not add a repository-level copy,
|
|
784
|
+
which would shadow it (see [Where NPM_TOKEN actually lives](#where-npm_token-actually-lives))
|
|
651
785
|
- `PLATFORMIO_AUTH_TOKEN`: Required for PlatformIO Library Registry publishing
|
|
652
786
|
|
|
787
|
+
Both `NPM_TOKEN` and `PLATFORMIO_AUTH_TOKEN` are user-minted tokens that
|
|
788
|
+
**expire**. Their expiry is invisible until a release fails, so rotate them
|
|
789
|
+
together and re-check after any expiry date you set. See
|
|
790
|
+
[NPM Token Expired / Invalid](#-troubleshooting) for the rotation runbook.
|
|
791
|
+
|
|
653
792
|
### Repository Settings
|
|
654
793
|
- **Actions**: Enabled with write permissions
|
|
655
794
|
- **Packages**: Enabled for GitHub Packages publication
|
|
@@ -34,7 +34,7 @@ Broadcast package for sharing device health and status information.
|
|
|
34
34
|
- `wifiStrength` - WiFi signal strength (0-100)
|
|
35
35
|
- `firmwareVersion` - Current firmware version string
|
|
36
36
|
|
|
37
|
-
### EnhancedStatusPackage (Type
|
|
37
|
+
### EnhancedStatusPackage (Type 604)
|
|
38
38
|
Extended status package with comprehensive health metrics (18 fields).
|
|
39
39
|
|
|
40
40
|
**Additional Fields:**
|
|
@@ -177,12 +177,12 @@ Complete Arduino sketch demonstrating:
|
|
|
177
177
|
- Message type discrimination
|
|
178
178
|
- Integration with painlessMesh
|
|
179
179
|
|
|
180
|
-
### `phase1_features.ino`
|
|
181
|
-
|
|
180
|
+
### `phase1_features.ino`
|
|
181
|
+
OTA enhancement example demonstrating:
|
|
182
182
|
- Compressed OTA transfer infrastructure
|
|
183
183
|
- Enhanced status reporting with comprehensive metrics
|
|
184
184
|
- Alert system implementation
|
|
185
|
-
- Usage patterns for
|
|
185
|
+
- Usage patterns for advanced features
|
|
186
186
|
|
|
187
187
|
### `metrics_health_node.ino` (NEW in v1.7.7)
|
|
188
188
|
Comprehensive monitoring node example demonstrating:
|