@alteriom/painlessmesh 1.10.0 → 2.0.1

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.
Files changed (55) hide show
  1. package/BRIDGE_TO_INTERNET.md +167 -29
  2. package/CHANGELOG.md +542 -0
  3. package/CONTRIBUTING.md +56 -53
  4. package/README.md +102 -95
  5. package/RELEASE_GUIDE.md +94 -788
  6. package/examples/alteriom/README.md +8 -10
  7. package/examples/alteriom/alteriom.ino +2 -2
  8. package/examples/alteriom/alteriom_sensor_package.hpp +17 -11
  9. package/examples/alteriom/mppt_example/alteriom_custom_package_template.hpp +320 -0
  10. package/examples/alteriom/mppt_example/alteriom_sensor_package.hpp +1389 -0
  11. package/examples/alteriom/mppt_example/{alteriom_mppt_example.ino → mppt_example.ino} +4 -0
  12. package/examples/basic/test/simulator/README.md +3 -3
  13. package/examples/bridge_failover/README.md +51 -14
  14. package/examples/commandControl/commandControl.ino +86 -0
  15. package/examples/commandControl/platformio.ini +26 -0
  16. package/examples/mqttBridge/mqttBridge.ino +4 -0
  17. package/examples/mqttBridge/platformio.ini +1 -1
  18. package/examples/otaSender/otaSender.ino +5 -1
  19. package/examples/priority/README.md +1 -1
  20. package/examples/priority/{priority_basic_example.ino → priority_basic_example/priority_basic_example.ino} +4 -4
  21. package/examples/priority/{priority_with_queue.ino → priority_with_queue/priority_with_queue.ino} +20 -2
  22. package/examples/reliableSensorLogging/platformio.ini +26 -0
  23. package/examples/reliableSensorLogging/reliableSensorLogging.ino +151 -0
  24. package/examples/sendToInternet/README.md +12 -5
  25. package/examples/sendToInternet/{CMakeLists.txt → pc_node/CMakeLists.txt} +7 -7
  26. package/examples/sendToInternet/{PC_NODE_README.md → pc_node/PC_NODE_README.md} +15 -15
  27. package/examples/sendToInternet/{build.sh → pc_node/build.sh} +5 -5
  28. package/examples/sendToInternet/{pc_mesh_node.cpp → pc_node/pc_mesh_node.cpp} +12 -1
  29. package/examples/sendToInternet/platformio.ini +2 -2
  30. package/examples/sharedGateway/README.md +1 -2
  31. package/keywords.txt +50 -1
  32. package/library.json +8 -6
  33. package/library.properties +2 -2
  34. package/package.json +3 -3
  35. package/src/AlteriomPainlessMesh.h +4 -4
  36. package/src/arduino/wifi.hpp +556 -126
  37. package/src/painlessMesh.h +2 -2
  38. package/src/painlessMeshSTA.cpp +607 -87
  39. package/src/painlessMeshSTA.h +135 -3
  40. package/src/painlessmesh/ack.hpp +283 -0
  41. package/src/painlessmesh/buffer.hpp +70 -8
  42. package/src/painlessmesh/callback.hpp +38 -5
  43. package/src/painlessmesh/configuration.hpp +69 -1
  44. package/src/painlessmesh/connection.hpp +12 -5
  45. package/src/painlessmesh/gateway.hpp +270 -5
  46. package/src/painlessmesh/layout.hpp +70 -2
  47. package/src/painlessmesh/logger.hpp +15 -0
  48. package/src/painlessmesh/mesh.hpp +552 -48
  49. package/src/painlessmesh/ntp.hpp +2 -4
  50. package/src/painlessmesh/plugin.hpp +30 -6
  51. package/src/painlessmesh/protocol.hpp +55 -2
  52. package/src/painlessmesh/router.hpp +192 -77
  53. package/src/painlessmesh/tcp.hpp +10 -0
  54. package/src/painlessmesh/message_tracker.hpp +0 -311
  55. /package/examples/sendToInternet/{mock_server_test.ino → mock_server_test/mock_server_test.ino} +0 -0
@@ -6,7 +6,7 @@ You can bridge your mesh network to the Internet by creating a **gateway node**
6
6
 
7
7
  The **bridge-centric approach** automatically detects your router's channel and configures the mesh accordingly. No manual channel configuration required!
8
8
 
9
- ### Resilient Initialization (v1.9.7+)
9
+ ### Resilient Initialization
10
10
 
11
11
  **Power-up order no longer matters!** The bridge will initialize successfully even if:
12
12
  - Router is not yet powered on
@@ -127,15 +127,31 @@ STARTUP: Mesh channel auto-detected: 6
127
127
 
128
128
  ### Automatic Channel Re-synchronization
129
129
 
130
- Nodes automatically follow the mesh if the bridge changes channels:
131
-
132
- - When nodes can't find the mesh on their current channel for ~30 seconds, they trigger a full channel scan
133
- - If the mesh is found on a different channel, nodes automatically switch to that channel
134
- - This ensures the mesh stays connected even if the bridge switches channels (e.g., during bridge election)
135
-
136
- For detailed information about channel synchronization, see [Channel Synchronization Documentation](docs/CHANNEL_SYNCHRONIZATION.md).
137
-
138
- ## Manual Configuration (Legacy Approach)
130
+ A bridge lives on its router's channel, so the mesh has to be there too. The
131
+ library keeps it there without any configuration on the regular nodes:
132
+
133
+ - **A bridge announces its channel.** Every bridge status carries
134
+ `routerChannel`, and an elected bridge's takeover message does too; peers
135
+ that hear a takeover move their AP and station to that channel a second
136
+ later. The channel a node last heard a bridge from is its *home*.
137
+ - **A node that finds nothing looks everywhere.** After two empty scans
138
+ (about 30 s) a node re-detects the mesh channel. A node with nothing under
139
+ its AP scans all channels at once; a node with stations attached scans one
140
+ channel per pass so its children are not dropped.
141
+ - **A node follows the mesh, not a straggler.** A disconnected node joins
142
+ the mesh wherever it is. A connected node leaves its partition only for a
143
+ strictly bigger one, and only after seeing it on two consecutive scans;
144
+ a node at home does not leave for a partition elsewhere, and away from
145
+ home it returns as soon as it sees the mesh there.
146
+ - **An uplink lost at home is rescanned there.** The bridge's AP is on this
147
+ channel; the node scans it again before looking anywhere else.
148
+
149
+ Set `mesh.setContainsRoot(true)` on every regular node of a mesh that has a
150
+ bridge (`initAsBridge()` sets it on the bridge): it is what lets a node that
151
+ is still connected to a partition the bridge has left notice that it has no
152
+ root and go looking for the bridge's channel.
153
+
154
+ ## Manual Channel Configuration
139
155
 
140
156
  If you prefer the traditional approach or need more control, you can still manually configure the channel:
141
157
 
@@ -176,7 +192,7 @@ The new `initAsBridge()` method automatically handles all channel detection and
176
192
 
177
193
  #### With Manual Configuration
178
194
 
179
- When using the legacy `stationManual()` approach, the library will automatically handle channel switching. The ESP32/ESP8266 will:
195
+ When using `stationManual()`, the library automatically handles channel switching. The ESP32/ESP8266 will:
180
196
 
181
197
  1. Initially operate the mesh AP on your specified channel (e.g., channel 6)
182
198
  2. Automatically switch to the router's channel when connecting via `stationManual()`
@@ -199,16 +215,18 @@ When using the legacy `stationManual()` approach, the library will automatically
199
215
  - Call `mesh.setRoot(true)` on the bridge node
200
216
  - Call `mesh.setContainsRoot(true)` on all mesh nodes for optimal routing
201
217
 
202
- 3. **ESP32-C6 Compatibility**: If using ESP32-C6 or experiencing crashes with `tcp_alloc` errors, ensure you have AsyncTCP v3.3.0+ installed. See the [ESP32-C6 Compatibility Guide](docs/troubleshooting/ESP32_C6_COMPATIBILITY.md) for details.
218
+ 3. **ESP32-C5 / ESP32-C6**: these need the ESP32 Arduino core 3.x and AsyncTCP v3.4.7 or later; `tcp_alloc` crashes on the C6 are the sign of an older AsyncTCP. See the [dependencies](README.md#dependencies) in the README.
203
219
 
204
220
  ## Complete Examples
205
221
 
206
222
  We provide several working bridge examples in the repository:
207
223
 
208
- - **Basic Bridge**: `examples/bridge/bridge.ino`
209
- - **MQTT Bridge**: `examples/mqttBridge/mqttBridge.ino` - Bridges mesh to MQTT broker
210
- - **Web Server Bridge**: `examples/webServer/webServer.ino` - Provides web interface
211
- - **Enhanced MQTT Bridge**: `examples/bridge/enhanced_mqtt_bridge_example.ino` - Advanced MQTT integration with metrics and health monitoring
224
+ - **Basic Bridge**: [`examples/bridge/bridge.ino`](examples/bridge/bridge.ino)
225
+ - **Bridge Failover**: [`examples/bridge_failover/`](examples/bridge_failover/) - Automatic election when the bridge goes
226
+ - **Shared Gateway**: [`examples/sharedGateway/`](examples/sharedGateway/) - Every node on the router
227
+ - **Send to Internet**: [`examples/sendToInternet/`](examples/sendToInternet/) - HTTP requests through the gateway, with a mock server for offline testing
228
+ - **MQTT Bridge**: [`examples/mqttBridge/mqttBridge.ino`](examples/mqttBridge/mqttBridge.ino) - Bridges mesh to MQTT broker
229
+ - **Web Server Bridge**: [`examples/webServer/webServer.ino`](examples/webServer/webServer.ino) - Provides web interface
212
230
 
213
231
  ## Forwarding Data to Internet
214
232
 
@@ -244,24 +262,144 @@ Mesh Network
244
262
  Node1 Node2 Node3...
245
263
  ```
246
264
 
265
+ ## Sending Data Through the Gateway
266
+
267
+ Regular nodes have no IP route to the Internet; `HTTPClient` on a regular
268
+ node fails with *connection refused*. They send through the gateway instead:
269
+
270
+ ```cpp
271
+ // On every node, after mesh.init(): enables sending on regular nodes and
272
+ // relaying on the bridge
273
+ mesh.enableSendToInternet();
274
+
275
+ // Later, on a regular node
276
+ if (mesh.hasInternetConnection()) { // a gateway with Internet is known
277
+ mesh.sendToInternet(
278
+ "https://api.example.com/data", jsonPayload,
279
+ [](bool success, uint16_t httpStatus, String error) {
280
+ Serial.printf("Delivery: %s (%u)\n", success ? "OK" : error.c_str(), httpStatus);
281
+ });
282
+ }
283
+ ```
284
+
285
+ The request travels to the gateway as a `GATEWAY_DATA` package, the gateway
286
+ makes the HTTP call and answers with `GATEWAY_ACK`. The gateway's HTTP work
287
+ is bounded by `NODE_TIMEOUT` (2 s per socket wait at the stock 10 s
288
+ watchdog), and its peers' watchdogs are postponed by exactly the time the
289
+ call took, so a slow endpoint cannot partition the mesh around the gateway.
290
+ An endpoint that needs longer needs a larger `NODE_TIMEOUT`; the
291
+ `static_assert` in `painlessmesh/gateway.hpp` says so at compile time.
292
+
293
+ ## Bridge Failover
294
+
295
+ Any node given the router's credentials can take over when the bridge goes:
296
+
297
+ ```cpp
298
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT, WIFI_AP_STA, 0);
299
+ mesh.setContainsRoot(true);
300
+ mesh.setRouterCredentials(ROUTER_SSID, ROUTER_PASSWORD);
301
+ mesh.enableBridgeFailover(true);
302
+ mesh.onBridgeRoleChanged([](bool isBridge, const String& reason) {
303
+ Serial.printf("%s: %s\n", isBridge ? "Promoted to bridge" : "Regular node", reason.c_str());
304
+ });
305
+ ```
306
+
307
+ A bridge that stops cleanly (`mesh.stop()`) broadcasts that it is leaving
308
+ and the candidates elect within seconds; a bridge that loses power is
309
+ noticed when its last status ages out (60 s, plus up to one 30 s monitor
310
+ tick). The winner is the candidate with the best router RSSI, then the
311
+ longest uptime, then the most free memory, then the lowest node ID; it
312
+ promotes itself with `initAsBridge()` and announces its channel. The
313
+ protocol, its tuning (`setElectionStartupDelay()`,
314
+ `setElectionRandomDelay()`, `setBridgeTimeout()`, `setMinimumBridgeRSSI()`)
315
+ and its troubleshooting are in
316
+ [examples/bridge_failover/README.md](examples/bridge_failover/README.md).
317
+
318
+ ## Multi-Bridge Coordination
319
+
320
+ Several bridges can serve one mesh. Each is started with a priority (10 is
321
+ primary, 1 is standby), and the bridges announce themselves to each other
322
+ every 30 s:
323
+
324
+ ```cpp
325
+ mesh.initAsBridge(MESH_PREFIX, MESH_PASSWORD, ROUTER_SSID, ROUTER_PASSWORD,
326
+ &userScheduler, MESH_PORT, 10); // priority 10
327
+ mesh.setBridgeSelectionStrategy(painlessMesh::PRIORITY_BASED); // or ROUND_ROBIN, BEST_SIGNAL
328
+ mesh.onBridgeCoordination(
329
+ [](const painlessmesh::plugin::BridgeCoordinationPackage& pkg, uint32_t from) {
330
+ Serial.printf("Bridge %u: priority %d, load %d%%\n", from, pkg.priority, pkg.load);
331
+ });
332
+ mesh.onBridgeCoordinationChanged(
333
+ [](const painlessmesh::plugin::BridgeCoordinationPackage& pkg, uint32_t from, String change) {
334
+ Serial.printf("Bridge %s: %u (%s)\n", change.c_str(), from, pkg.role.c_str());
335
+ });
336
+ ```
337
+
338
+ `getPrimaryBridge()` returns the bridge the strategy currently selects;
339
+ `getBridges()` lists every known bridge with its Internet state, RSSI and
340
+ last-seen time.
341
+
342
+ ## Message Queue for Offline Periods
343
+
344
+ When no gateway has Internet, a node can hold messages and send them later:
345
+
346
+ ```cpp
347
+ mesh.enableMessageQueue(true, 100); // after mesh.init()
348
+ uint32_t id = mesh.queueMessage(alarmJson, "https://api.example.com/alarm", PRIORITY_CRITICAL);
349
+
350
+ // Drain when connectivity returns: the queue never sends on its own
351
+ if (mesh.hasInternetConnection()) {
352
+ for (auto& queued : mesh.flushMessageQueue()) {
353
+ mesh.sendToInternet(queued.destination, queued.payload, onResult);
354
+ mesh.removeQueuedMessage(queued.id);
355
+ }
356
+ }
357
+ ```
358
+
359
+ Priorities are `PRIORITY_CRITICAL`, `PRIORITY_HIGH`, `PRIORITY_NORMAL` and
360
+ `PRIORITY_LOW`; when the queue is full the lowest priority is evicted first
361
+ and a critical message is never evicted. `getQueueStats()` reports queued,
362
+ dropped and flushed counts.
363
+
364
+ ## Shared Gateway Mode
365
+
366
+ When every node is within reach of the router, every node can be its own
367
+ gateway and the mesh is the fallback:
368
+
369
+ ```cpp
370
+ mesh.initAsSharedGateway(MESH_PREFIX, MESH_PASSWORD, ROUTER_SSID, ROUTER_PASSWORD,
371
+ &userScheduler, MESH_PORT);
372
+ mesh.sendToInternet(url, payload, onResult); // local uplink if healthy, else via the mesh
373
+ mesh.onGatewayChanged([](uint32_t oldGateway, uint32_t newGateway) {
374
+ Serial.printf("Primary gateway %u -> %u\n", oldGateway, newGateway);
375
+ });
376
+ ```
377
+
378
+ A node's own uplink is health-checked periodically (`hasLocalInternet()`),
379
+ and a request is served locally when it is healthy. See
380
+ [examples/sharedGateway](examples/sharedGateway/).
381
+
247
382
  ## Frequently Asked Questions
248
383
 
249
- ### Why does `mesh.init()` require a separate `mesh.stationManual()` call?
384
+ ### Why are there three ways to connect a bridge?
250
385
 
251
- Great question! The library now offers **three ways** to connect a bridge:
386
+ 1. `init()` followed by `stationManual()` the original API, still the most
387
+ flexible: you choose the channel and the mode.
388
+ 2. `init()` with `stationSSID` and `stationPassword` — the same, in one call.
389
+ 3. `initAsBridge()` — detects the router's channel, sets the root flags,
390
+ retries the router in the background, and starts the bridge status
391
+ broadcasts. Use this unless you have a reason not to.
252
392
 
253
- 1. **Original**: `init()` + `stationManual()` (most flexible)
254
- 2. **Convenience**: Pass credentials directly to `init()` (new feature)
255
- 3. **Modern**: Use `initAsBridge()` with auto-detection (recommended)
393
+ ### Does the bridge need to be up first?
256
394
 
257
- See [Station Credentials Design Rationale](docs/design/STATION_CREDENTIALS_DESIGN.md) for detailed explanations and comparisons.
395
+ No. Regular nodes started with `channel = 0` scan for the mesh, and a mesh
396
+ that forms before the bridge follows the bridge to its channel when it
397
+ appears. The ESP8266 should be a leaf in a mesh of more than a few nodes
398
+ (`init(..., maxconn = 0)`); see the README.
258
399
 
259
400
  ## Additional Resources
260
401
 
261
- - [painlessMesh Wiki](https://github.com/Alteriom/painlessMesh/wiki)
262
- - [Bridge Examples](https://github.com/Alteriom/painlessMesh/tree/main/examples/bridge)
263
- - [MQTT Bridge Example](https://github.com/Alteriom/painlessMesh/tree/main/examples/mqttBridge)
264
- - [Configuration API Reference](https://github.com/Alteriom/painlessMesh/wiki)
265
- - [Station Credentials Design](docs/design/STATION_CREDENTIALS_DESIGN.md) - Why three approaches exist
266
-
267
- Feel free to ask if you need help with specific use cases like MQTT integration, web servers, or custom data forwarding!
402
+ - [USER_GUIDE.md](USER_GUIDE.md) — the complete guide, including the API reference
403
+ - [examples/bridge_failover/README.md](examples/bridge_failover/README.md) — the election protocol in detail
404
+ - [SECURITY.md](SECURITY.md) — what gateway HTTPS does and does not protect
405
+ - [CHANGELOG.md](CHANGELOG.md) — what changed in 2.0, and what to know before upgrading