@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/README.md +102 -63
  3. package/RELEASE_GUIDE.md +147 -8
  4. package/examples/alteriom/README.md +4 -4
  5. package/examples/alteriom/alteriom_custom_package_template.hpp +320 -0
  6. package/examples/alteriom/alteriom_sensor_package.hpp +1 -1
  7. package/examples/alteriom/mppt_example/alteriom_mppt_example.ino +208 -0
  8. package/examples/bridge_failover/bridge_failover.ino +17 -0
  9. package/examples/sendToInternet/CMakeLists.txt +54 -0
  10. package/examples/sendToInternet/PC_NODE_README.md +517 -0
  11. package/examples/sendToInternet/README.md +39 -1
  12. package/examples/sendToInternet/build.sh +153 -0
  13. package/examples/sendToInternet/mock_server_test.ino +361 -0
  14. package/examples/sendToInternet/pc_mesh_node.cpp +361 -0
  15. package/examples/tcpRetryConfig/README.md +110 -0
  16. package/examples/tcpRetryConfig/platformio.ini +26 -0
  17. package/examples/tcpRetryConfig/tcpRetryConfig.ino +154 -0
  18. package/keywords.txt +3 -0
  19. package/library.json +4 -1
  20. package/library.properties +1 -1
  21. package/package.json +3 -3
  22. package/src/AlteriomPainlessMesh.h +6 -14
  23. package/src/arduino/wifi.hpp +352 -114
  24. package/src/connection.cpp +10 -0
  25. package/src/painlessMesh.h +2 -15
  26. package/src/painlessTaskOptions.h +9 -0
  27. package/src/painlessmesh/buffer.hpp +4 -1
  28. package/src/painlessmesh/configuration.hpp +13 -2
  29. package/src/painlessmesh/connection.hpp +36 -21
  30. package/src/painlessmesh/gateway.hpp +0 -1061
  31. package/src/painlessmesh/mesh.hpp +102 -107
  32. package/src/painlessmesh/message_queue.hpp +25 -15
  33. package/src/painlessmesh/metrics.hpp +2 -262
  34. package/src/painlessmesh/plugin.hpp +27 -5
  35. package/src/painlessmesh/tcp.hpp +158 -29
  36. package/src/painlessmesh/validation.hpp +0 -143
  37. package/docs/README.md +0 -132
  38. package/docs/alteriom/overview.md +0 -531
  39. package/docs/api/core-api.md +0 -607
  40. package/docs/api/shared-gateway.md +0 -1207
  41. package/docs/architecture/mesh-architecture.md +0 -399
  42. package/docs/architecture/plugin-system.md +0 -517
  43. package/docs/getting-started/arduino-manual-install.md +0 -313
  44. package/docs/getting-started/first-mesh.md +0 -410
  45. package/docs/getting-started/installation.md +0 -275
  46. package/docs/getting-started/quickstart.md +0 -158
  47. package/docs/troubleshooting/common-issues.md +0 -679
  48. package/docs/troubleshooting/debugging.md +0 -455
  49. package/docs/troubleshooting/external-device-connection.md +0 -283
  50. package/docs/troubleshooting/faq.md +0 -574
  51. package/docs/tutorials/basic-examples.md +0 -718
@@ -0,0 +1,361 @@
1
+ /**
2
+ * PC Mesh Node - Test sendToInternet() from Regular Node Through Bridge
3
+ *
4
+ * This example demonstrates a PC-based mesh node that can join a painlessMesh
5
+ * network with ESP32/ESP8266 devices and test sendToInternet() functionality
6
+ * as a REGULAR NODE (not a bridge).
7
+ *
8
+ * PURPOSE:
9
+ * Test the complete flow: PC Node → Bridge → Internet → Bridge → PC Node
10
+ *
11
+ * CONNECTION METHOD:
12
+ * ESP nodes use WiFi mesh credentials: mesh.init(MESH_PREFIX, MESH_PASSWORD, &scheduler, MESH_PORT)
13
+ * PC node uses TCP/IP connection: connects to bridge's IP:port (e.g., 192.168.1.100:5555)
14
+ *
15
+ * Both methods result in a mesh node that can use sendToInternet() - the difference
16
+ * is the transport layer (WiFi mesh vs TCP/IP), not the painlessMesh protocol.
17
+ *
18
+ * SETUP:
19
+ * 1. Configure ESP bridge with: mesh.init("FishFarmMesh", "securepass", &scheduler, 5555)
20
+ * 2. Run this PC node: ./pc_mesh_node <bridge_ip> 5555
21
+ * 3. Run mock HTTP server: cd test/mock-http-server && python3 server.py
22
+ * 4. PC node sends HTTP requests via sendToInternet() through bridge
23
+ *
24
+ * COMPILE:
25
+ * g++ -std=c++14 -o pc_mesh_node pc_mesh_node.cpp \
26
+ * -I../../src -I../../test/include -I../../test/ArduinoJson/src \
27
+ * -I../../test/TaskScheduler/src -I../../test/boost \
28
+ * ../../src/scheduler.cpp \
29
+ * -lboost_system -pthread
30
+ *
31
+ * Or use CMake (see CMakeLists.txt in this directory)
32
+ *
33
+ * RUN:
34
+ * ./pc_mesh_node 192.168.1.100 5555
35
+ *
36
+ * Where:
37
+ * - 192.168.1.100 is your ESP bridge's IP address on the LAN
38
+ * - 5555 is the TCP port the bridge is listening on (MESH_PORT)
39
+ **/
40
+
41
+ #include <iostream>
42
+ #include <string>
43
+ #include <memory>
44
+ #include <chrono>
45
+ #include <thread>
46
+
47
+ // Boost includes
48
+ #include <boost/asio/ip/address.hpp>
49
+
50
+ // Arduino emulation for PC
51
+ #include "Arduino.h"
52
+ #include "boost/asynctcp.hpp"
53
+
54
+ // painlessMesh includes
55
+ #include "painlessmesh/mesh.hpp"
56
+ #include "painlessmesh/gateway.hpp"
57
+
58
+ // Global objects required by Arduino emulation
59
+ WiFiClass WiFi;
60
+ ESPClass ESP;
61
+
62
+ using PMesh = painlessmesh::Mesh<painlessmesh::Connection>;
63
+ using namespace painlessmesh;
64
+
65
+ // Global logger
66
+ painlessmesh::logger::LogClass Log;
67
+
68
+ /**
69
+ * PC Mesh Node Class
70
+ *
71
+ * This class wraps a painlessMesh instance and provides methods
72
+ * to connect to a bridge node and send Internet requests through it.
73
+ */
74
+ class PCMeshNode : public PMesh {
75
+ public:
76
+ PCMeshNode(Scheduler* scheduler, uint32_t nodeId, boost::asio::io_context& io)
77
+ : io_service(io), scheduler_(scheduler) {
78
+ this->nodeId = nodeId;
79
+ this->init(scheduler, this->nodeId);
80
+
81
+ // Start listening server (so bridge can connect back to us if needed)
82
+ pServer = std::make_shared<AsyncServer>(io_service, this->nodeId);
83
+ painlessmesh::tcp::initServer<painlessmesh::Connection, PMesh>(*pServer, (*this));
84
+
85
+ std::cout << "✓ PC Mesh Node initialized with ID: " << this->nodeId << std::endl;
86
+ }
87
+
88
+ /**
89
+ * Connect to a bridge node
90
+ */
91
+ bool connectToBridge(const std::string& bridgeIP, uint16_t bridgePort) {
92
+ std::cout << "Connecting to bridge at " << bridgeIP << ":" << bridgePort << "..." << std::endl;
93
+
94
+ try {
95
+ auto pClient = new AsyncClient(io_service);
96
+
97
+ // Set up connection callbacks
98
+ bool connected = false;
99
+ pClient->onConnect([&connected](void*, AsyncClient* client) {
100
+ connected = true;
101
+ std::cout << "✓ Connected to bridge!" << std::endl;
102
+ });
103
+
104
+ pClient->onDisconnect([](void*, AsyncClient* client) {
105
+ std::cout << "✗ Disconnected from bridge" << std::endl;
106
+ });
107
+
108
+ // Connect to the bridge
109
+ painlessmesh::tcp::connect<Connection, PMesh>(
110
+ (*pClient),
111
+ boost::asio::ip::make_address(bridgeIP),
112
+ bridgePort,
113
+ (*this)
114
+ );
115
+
116
+ // Wait for connection with timeout
117
+ auto startTime = std::chrono::steady_clock::now();
118
+ while (!connected) {
119
+ this->update();
120
+ io_service.poll();
121
+
122
+ // Use longer sleep to reduce CPU usage during wait
123
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
124
+
125
+ auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
126
+ std::chrono::steady_clock::now() - startTime
127
+ ).count();
128
+
129
+ if (elapsed > 30) {
130
+ std::cout << "✗ Connection timeout after 30 seconds" << std::endl;
131
+ return false;
132
+ }
133
+ }
134
+
135
+ return true;
136
+ } catch (const std::exception& e) {
137
+ std::cout << "✗ Connection error: " << e.what() << std::endl;
138
+ return false;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Send an HTTP request to the Internet via the bridge
144
+ */
145
+ void testSendToInternet(const std::string& url, const std::string& payload = "") {
146
+ std::cout << "\n📡 Testing sendToInternet()..." << std::endl;
147
+ std::cout << " URL: " << url << std::endl;
148
+ if (!payload.empty()) {
149
+ std::cout << " Payload: " << payload << std::endl;
150
+ }
151
+
152
+ // Check if we have Internet connection through bridge
153
+ if (!this->hasInternetConnection()) {
154
+ std::cout << " ⚠️ No Internet connection available through bridge" << std::endl;
155
+ std::cout << " Make sure the bridge node has router access and is connected" << std::endl;
156
+ return;
157
+ }
158
+
159
+ std::cout << " ✓ Bridge with Internet found" << std::endl;
160
+
161
+ // Send the request
162
+ uint32_t msgId = this->sendToInternet(
163
+ url,
164
+ payload,
165
+ [url](bool success, uint16_t httpStatus, std::string error) {
166
+ std::cout << "\n📥 Response received:" << std::endl;
167
+ std::cout << " Success: " << (success ? "✓ YES" : "✗ NO") << std::endl;
168
+ std::cout << " HTTP Status: " << httpStatus << std::endl;
169
+ if (!error.empty()) {
170
+ std::cout << " Error: " << error << std::endl;
171
+ }
172
+
173
+ // Validate the result
174
+ if (success && httpStatus == 200) {
175
+ std::cout << " 🎉 TEST PASSED: Request successful!" << std::endl;
176
+ } else {
177
+ std::cout << " ❌ TEST FAILED: Request unsuccessful" << std::endl;
178
+ }
179
+ }
180
+ );
181
+
182
+ if (msgId > 0) {
183
+ std::cout << " ✓ Request queued with message ID: " << msgId << std::endl;
184
+ std::cout << " Waiting for response from bridge..." << std::endl;
185
+ } else {
186
+ std::cout << " ✗ Failed to queue request" << std::endl;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Main update loop
192
+ */
193
+ void runUpdateLoop(int durationSeconds = 0) {
194
+ std::cout << "\n🔄 Starting update loop";
195
+ if (durationSeconds > 0) {
196
+ std::cout << " (running for " << durationSeconds << " seconds)";
197
+ }
198
+ std::cout << "..." << std::endl;
199
+
200
+ auto startTime = std::chrono::steady_clock::now();
201
+
202
+ while (true) {
203
+ this->update();
204
+ io_service.poll();
205
+ scheduler_->execute();
206
+
207
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
208
+
209
+ if (durationSeconds > 0) {
210
+ auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
211
+ std::chrono::steady_clock::now() - startTime
212
+ ).count();
213
+
214
+ if (elapsed >= durationSeconds) {
215
+ std::cout << "✓ Update loop completed after " << elapsed << " seconds" << std::endl;
216
+ break;
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ std::shared_ptr<AsyncServer> pServer;
223
+ boost::asio::io_context& io_service;
224
+ Scheduler* scheduler_;
225
+ };
226
+
227
+ /**
228
+ * Print usage information
229
+ */
230
+ void printUsage(const char* programName) {
231
+ std::cout << "\nUsage: " << programName << " <bridge_ip> <bridge_port> [mock_server_ip] [mock_server_port]\n" << std::endl;
232
+ std::cout << "Arguments:" << std::endl;
233
+ std::cout << " bridge_ip IP address of ESP bridge node (e.g., 192.168.1.100)" << std::endl;
234
+ std::cout << " bridge_port Mesh port of bridge (default: 5555)" << std::endl;
235
+ std::cout << " mock_server_ip IP address of mock HTTP server (optional, default: localhost)" << std::endl;
236
+ std::cout << " mock_server_port Port of mock HTTP server (optional, default: 8080)" << std::endl;
237
+ std::cout << "\nExample:" << std::endl;
238
+ std::cout << " " << programName << " 192.168.1.100 5555" << std::endl;
239
+ std::cout << " " << programName << " 192.168.1.100 5555 192.168.1.50 8080" << std::endl;
240
+ std::cout << std::endl;
241
+ }
242
+
243
+ /**
244
+ * Run automated test suite
245
+ */
246
+ void runTests(PCMeshNode& node, const std::string& mockServerUrl) {
247
+ std::cout << "\n" << std::string(60, '=') << std::endl;
248
+ std::cout << "Starting Automated Test Suite" << std::endl;
249
+ std::cout << std::string(60, '=') << std::endl;
250
+
251
+ // Wait for mesh to stabilize
252
+ std::cout << "\nWaiting 5 seconds for mesh to stabilize..." << std::endl;
253
+ std::this_thread::sleep_for(std::chrono::seconds(5));
254
+
255
+ // Test 1: HTTP 200 Success
256
+ std::cout << "\n[Test 1/5] HTTP 200 Success" << std::endl;
257
+ node.testSendToInternet(mockServerUrl + "/status/200");
258
+ node.runUpdateLoop(5);
259
+
260
+ // Test 2: HTTP 404 Not Found
261
+ std::cout << "\n[Test 2/5] HTTP 404 Not Found" << std::endl;
262
+ node.testSendToInternet(mockServerUrl + "/status/404");
263
+ node.runUpdateLoop(5);
264
+
265
+ // Test 3: HTTP 500 Server Error
266
+ std::cout << "\n[Test 3/5] HTTP 500 Server Error" << std::endl;
267
+ node.testSendToInternet(mockServerUrl + "/status/500");
268
+ node.runUpdateLoop(5);
269
+
270
+ // Test 4: WhatsApp API Simulation
271
+ std::cout << "\n[Test 4/5] WhatsApp API Simulation" << std::endl;
272
+ std::string whatsappUrl = mockServerUrl + "/whatsapp?phone=%2B1234567890&apikey=test&text=Hello%20from%20PC%20node";
273
+ node.testSendToInternet(whatsappUrl);
274
+ node.runUpdateLoop(5);
275
+
276
+ // Test 5: Echo with JSON Payload
277
+ std::cout << "\n[Test 5/5] Echo Endpoint with JSON Payload" << std::endl;
278
+ // Using raw string literal for safer JSON construction
279
+ // In production, consider using ArduinoJson for proper JSON handling
280
+ char jsonBuffer[256];
281
+ snprintf(jsonBuffer, sizeof(jsonBuffer),
282
+ R"({"source":"pc_node","test":"sendToInternet","timestamp":%lu})",
283
+ (unsigned long)millis());
284
+ std::string payload(jsonBuffer);
285
+ node.testSendToInternet(mockServerUrl + "/echo", payload);
286
+ node.runUpdateLoop(5);
287
+
288
+ std::cout << "\n" << std::string(60, '=') << std::endl;
289
+ std::cout << "Test Suite Complete!" << std::endl;
290
+ std::cout << std::string(60, '=') << std::endl;
291
+ }
292
+
293
+ /**
294
+ * Main entry point
295
+ */
296
+ int main(int argc, char* argv[]) {
297
+ std::cout << "\n" << std::string(60, '=') << std::endl;
298
+ std::cout << "painlessMesh - PC Mesh Node Example" << std::endl;
299
+ std::cout << "Testing sendToInternet() Through Bridge" << std::endl;
300
+ std::cout << std::string(60, '=') << std::endl;
301
+
302
+ // Parse command line arguments
303
+ if (argc < 3) {
304
+ printUsage(argv[0]);
305
+ return 1;
306
+ }
307
+
308
+ std::string bridgeIP = argv[1];
309
+ uint16_t bridgePort = std::stoi(argv[2]);
310
+ std::string mockServerIP = (argc > 3) ? argv[3] : "127.0.0.1";
311
+ uint16_t mockServerPort = (argc > 4) ? std::stoi(argv[4]) : 8080;
312
+
313
+ std::cout << "\nConfiguration:" << std::endl;
314
+ std::cout << " Bridge: " << bridgeIP << ":" << bridgePort << std::endl;
315
+ std::cout << " Mock Server: http://" << mockServerIP << ":" << mockServerPort << std::endl;
316
+ std::cout << std::endl;
317
+
318
+ // Setup logging
319
+ Log.setLogLevel(painlessmesh::logger::ERROR | painlessmesh::logger::STARTUP |
320
+ painlessmesh::logger::CONNECTION | painlessmesh::logger::COMMUNICATION);
321
+
322
+ // Create IO context and scheduler
323
+ boost::asio::io_context io_service;
324
+ Scheduler scheduler;
325
+
326
+ // Create PC mesh node with a random node ID
327
+ uint32_t nodeId = 1000000 + (millis() % 1000000);
328
+ PCMeshNode node(&scheduler, nodeId, io_service);
329
+
330
+ // Enable sendToInternet API
331
+ node.enableSendToInternet();
332
+ std::cout << "✓ sendToInternet() API enabled" << std::endl;
333
+
334
+ // Connect to bridge
335
+ if (!node.connectToBridge(bridgeIP, bridgePort)) {
336
+ std::cerr << "\n✗ Failed to connect to bridge" << std::endl;
337
+ std::cerr << "Make sure:" << std::endl;
338
+ std::cerr << " 1. Bridge node is running on " << bridgeIP << ":" << bridgePort << std::endl;
339
+ std::cerr << " 2. Firewall allows TCP connections on port " << bridgePort << std::endl;
340
+ std::cerr << " 3. PC and bridge are on the same network" << std::endl;
341
+ return 1;
342
+ }
343
+
344
+ // Wait for mesh to establish
345
+ std::cout << "\nWaiting for mesh to establish..." << std::endl;
346
+ node.runUpdateLoop(10);
347
+
348
+ // Build mock server URL
349
+ std::string mockServerUrl = "http://" + mockServerIP + ":" + std::to_string(mockServerPort);
350
+
351
+ // Run automated tests
352
+ runTests(node, mockServerUrl);
353
+
354
+ // Keep running for a bit to receive any late responses
355
+ std::cout << "\nKeeping connection alive for 10 more seconds..." << std::endl;
356
+ node.runUpdateLoop(10);
357
+
358
+ std::cout << "\n✓ PC Mesh Node shutting down..." << std::endl;
359
+
360
+ return 0;
361
+ }
@@ -0,0 +1,110 @@
1
+ # tcpRetryConfig
2
+
3
+ Tuning painlessMesh's TCP connection retry behaviour with
4
+ `setTcpRetryConfig()` (issue
5
+ [#378](https://github.com/Alteriom/painlessMesh/issues/378)).
6
+
7
+ ## What this controls
8
+
9
+ When a node acquires an IP and tries to open its TCP connection to the mesh,
10
+ that connection can fail — the parent's TCP server may not be ready yet, the
11
+ network stack may still be settling, or several nodes may be connecting at
12
+ once. painlessMesh retries with exponential backoff before giving up and
13
+ falling back to a full WiFi reconnect.
14
+
15
+ Five parameters describe that behaviour:
16
+
17
+ | Field | Default | Meaning |
18
+ |---|---|---|
19
+ | `maxRetries` | 5 | TCP connect attempts after the first before giving up |
20
+ | `retryDelayMs` | 1000 | Base delay between retries; scaled 1x, 2x, 4x, 8x, 8x |
21
+ | `stabilizationDelayMs` | 500 | Wait after IP acquisition before the first attempt |
22
+ | `exhaustionReconnectDelayMs` | 10000 | Wait before the WiFi reconnect that follows exhaustion |
23
+ | `failureBlockDurationMs` | 60000 | How long a failed peer is skipped during AP selection |
24
+
25
+ With the defaults, a node that cannot reach its parent spends
26
+ 1 + 2 + 4 + 8 + 8 = **23 s** retrying, then waits another **10 s** before
27
+ reconnecting WiFi, and will not re-select that same peer for **60 s**.
28
+
29
+ ## Profiles in this sketch
30
+
31
+ Switch with `#define ACTIVE_PROFILE`.
32
+
33
+ | | `PROFILE_REALTIME` | default | `PROFILE_RELIABLE` | `PROFILE_BATTERY` |
34
+ |---|---|---|---|---|
35
+ | `maxRetries` | 1 | 5 | 10 | 2 |
36
+ | `retryDelayMs` | 200 | 1000 | 2000 | 3000 |
37
+ | `stabilizationDelayMs` | 100 | 500 | 1000 | 500 |
38
+ | `exhaustionReconnectDelayMs` | 1000 | 10000 | 30000 | 60000 |
39
+ | `failureBlockDurationMs` | 5000 | 60000 | 180000 | 300000 |
40
+ | worst-case retry time | 0.2 s | 23 s | 126 s | 9 s |
41
+ | full failure cycle | 1.2 s | 33 s | 156 s | 69 s |
42
+
43
+ - **`PROFILE_REALTIME`** — real-time sensor and LED meshes, the use case from
44
+ [discussion #368](https://github.com/Alteriom/painlessMesh/discussions/368).
45
+ A node stuck in a 23 s backoff is worse than one that drops and re-scans, so
46
+ fail fast and move on.
47
+ - **`PROFILE_RELIABLE`** — industrial meshes where getting connected matters
48
+ more than how long it takes. `maxRetries = 10` is the maximum the library
49
+ accepts.
50
+ - **`PROFILE_BATTERY`** — every retry is radio-on time. Few attempts, spaced
51
+ widely, and a long blocklist so the node stops waking up for a peer that is
52
+ known to be down.
53
+
54
+ ## Clamping
55
+
56
+ `setTcpRetryConfig()` coerces the two values that can render a node unusable:
57
+
58
+ - `maxRetries` is capped at **10**. Each retry allocates an `AsyncClient` and
59
+ schedules a task, so an unbounded value is a heap and recursion-depth hazard
60
+ on ESP8266.
61
+ - `retryDelayMs` is held between **50 ms** and **60000 ms**. A zero delay would
62
+ schedule retries with no spacing — a hot loop allocating an `AsyncClient`
63
+ every scheduler tick. The ceiling keeps `retryDelayMs * 8` clear of `uint32_t`
64
+ overflow.
65
+
66
+ Everything else passes through untouched, including zeros, which are
67
+ meaningful:
68
+
69
+ - `maxRetries = 0` — do not retry at all; fall straight back to a WiFi
70
+ reconnect on the first TCP error.
71
+ - `stabilizationDelayMs = 0` — attempt the TCP connection immediately on IP
72
+ acquisition.
73
+ - `exhaustionReconnectDelayMs = 0` — reconnect WiFi immediately after
74
+ exhaustion.
75
+ - `failureBlockDurationMs = 0` — never blocklist a failed peer.
76
+
77
+ Call `getTcpRetryConfig()` after setting to see what actually took effect; the
78
+ sketch prints this at startup.
79
+
80
+ ## The defaults exist for a reason
81
+
82
+ painlessMesh 1.9.x deliberately *raised* these values (retries 3 → 5, base
83
+ delay 500 ms → 1000 ms) to fix real-world mesh instability. Tuning them back
84
+ down reintroduces the problems that change fixed. Symptoms of an over-aggressive
85
+ profile:
86
+
87
+ - **Connection churn** — nodes repeatedly connect and drop. `maxRetries` is too
88
+ low for how long your parent actually takes to be ready; raise it or raise
89
+ `stabilizationDelayMs`.
90
+ - **Rapid reconnect loops / network congestion** — a node hammers a parent whose
91
+ TCP server is down. `exhaustionReconnectDelayMs` is too short.
92
+ - **The same dead peer is picked over and over** — `failureBlockDurationMs` is
93
+ shorter than one full retry-plus-reconnect cycle, so the peer comes off the
94
+ blocklist before the node has finished failing over. Keep
95
+ `failureBlockDurationMs` greater than
96
+ `(sum of retry backoffs) + exhaustionReconnectDelayMs`. All three profiles
97
+ above satisfy this; `catch_tcp_blocklist.cpp` pins it as a test.
98
+
99
+ Change one parameter at a time and watch the serial log with
100
+ `mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION)`.
101
+
102
+ ## Building
103
+
104
+ ```bash
105
+ # PlatformIO
106
+ pio run -e esp32 # or -e esp8266
107
+
108
+ # Arduino CLI
109
+ arduino-cli compile --fqbn esp32:esp32:esp32 examples/tcpRetryConfig/tcpRetryConfig.ino
110
+ ```
@@ -0,0 +1,26 @@
1
+ [platformio]
2
+ src_dir = .
3
+
4
+ [env]
5
+ lib_deps =
6
+ bblanchon/ArduinoJson
7
+ arkhipenko/TaskScheduler
8
+
9
+ lib_ldf_mode = deep+
10
+ [env:esp8266]
11
+ platform = espressif8266
12
+ board = nodemcuv2
13
+ framework = arduino
14
+ lib_extra_dirs = ../../
15
+ lib_deps =
16
+ ${env.lib_deps} ; Inherit common dependencies
17
+ esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
18
+
19
+ [env:esp32]
20
+ platform = espressif32
21
+ board = esp32dev
22
+ framework = arduino
23
+ lib_extra_dirs = ../../
24
+ lib_deps =
25
+ ${env.lib_deps} ; Inherit common dependencies
26
+ esp32async/AsyncTCP
@@ -0,0 +1,154 @@
1
+ //************************************************************
2
+ // Tuning the TCP connection retry behaviour (issue #378)
3
+ //
4
+ // painlessMesh retries a failed TCP connection with exponential backoff
5
+ // before giving up and falling back to a full WiFi reconnect. The defaults
6
+ // (5 retries, 1s base delay -> 1s, 2s, 4s, 8s, 8s) are tuned for general
7
+ // purpose meshes and deliberately favour reliability over speed.
8
+ //
9
+ // setTcpRetryConfig() lets you pick a different trade-off. Three ready-made
10
+ // profiles are shown below; switch between them with ACTIVE_PROFILE.
11
+ //
12
+ // Read examples/tcpRetryConfig/README.md before changing these values - the
13
+ // defaults exist because 1.9.x raised them to fix real mesh instability.
14
+ //************************************************************
15
+ #include <painlessMesh.h>
16
+
17
+ #define MESH_SSID "whateverYouLike"
18
+ #define MESH_PASSWORD "somethingSneaky"
19
+ #define MESH_PORT 5555
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Profiles
23
+ // ---------------------------------------------------------------------------
24
+ #define PROFILE_REALTIME 1 // low latency: fail fast, reconnect fast
25
+ #define PROFILE_RELIABLE 2 // industrial: many retries, long backoff
26
+ #define PROFILE_BATTERY 3 // conserve power: few retries, long block
27
+
28
+ // >>> Change this line to try a different profile <<<
29
+ #define ACTIVE_PROFILE PROFILE_REALTIME
30
+
31
+ // Prototypes
32
+ void sendMessage();
33
+ void receivedCallback(uint32_t from, String &msg);
34
+ void newConnectionCallback(uint32_t nodeId);
35
+ void droppedConnectionCallback(uint32_t nodeId);
36
+
37
+ Scheduler userScheduler;
38
+ painlessMesh mesh;
39
+
40
+ Task taskSendMessage(TASK_SECOND * 5, TASK_FOREVER, &sendMessage);
41
+
42
+ // Build the retry configuration for the selected profile.
43
+ painlessmesh::tcp::TcpRetryConfig buildRetryConfig() {
44
+ painlessmesh::tcp::TcpRetryConfig cfg; // starts at the library defaults
45
+
46
+ #if ACTIVE_PROFILE == PROFILE_REALTIME
47
+ // Real-time sensor / LED meshes: a stalled node is worse than a dropped
48
+ // one. Give up on the TCP handshake almost immediately and go straight
49
+ // back to scanning for another parent.
50
+ cfg.maxRetries = 1; // default 5
51
+ cfg.retryDelayMs = 200; // default 1000
52
+ cfg.stabilizationDelayMs = 100; // default 500
53
+ cfg.exhaustionReconnectDelayMs = 1000; // default 10000
54
+ cfg.failureBlockDurationMs = 5000; // default 60000
55
+
56
+ #elif ACTIVE_PROFILE == PROFILE_RELIABLE
57
+ // Industrial / high-reliability meshes: connectivity matters more than
58
+ // how long it takes to get there. Retry patiently and keep a failed peer
59
+ // out of the running for a good while.
60
+ cfg.maxRetries = 10; // default 5 (this is the maximum)
61
+ cfg.retryDelayMs = 2000; // default 1000
62
+ cfg.stabilizationDelayMs = 1000; // default 500
63
+ cfg.exhaustionReconnectDelayMs = 30000; // default 10000
64
+ // Must exceed one full failure cycle (126s of retries + 30s reconnect),
65
+ // otherwise a dead peer leaves the blocklist before we finished failing
66
+ // over and gets re-selected immediately.
67
+ cfg.failureBlockDurationMs = 180000; // default 60000
68
+
69
+ #elif ACTIVE_PROFILE == PROFILE_BATTERY
70
+ // Battery-powered nodes: every retry is radio time. Few attempts, spaced
71
+ // widely, and a long blocklist so we do not keep waking up for a peer
72
+ // that is known to be down.
73
+ cfg.maxRetries = 2; // default 5
74
+ cfg.retryDelayMs = 3000; // default 1000
75
+ cfg.stabilizationDelayMs = 500; // default 500
76
+ cfg.exhaustionReconnectDelayMs = 60000; // default 10000
77
+ cfg.failureBlockDurationMs = 300000; // default 60000
78
+
79
+ #else
80
+ #error "ACTIVE_PROFILE must be one of PROFILE_REALTIME, PROFILE_RELIABLE, PROFILE_BATTERY"
81
+ #endif
82
+
83
+ return cfg;
84
+ }
85
+
86
+ void setup() {
87
+ Serial.begin(115200);
88
+
89
+ mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
90
+
91
+ // Apply the retry configuration BEFORE init() so the very first connection
92
+ // attempt already uses it.
93
+ mesh.setTcpRetryConfig(buildRetryConfig());
94
+
95
+ mesh.init(MESH_SSID, MESH_PASSWORD, &userScheduler, MESH_PORT);
96
+
97
+ // Read the configuration back. Values outside safe operating bounds are
98
+ // clamped by the setter, so this prints what is actually in effect - not
99
+ // necessarily what was requested.
100
+ painlessmesh::tcp::TcpRetryConfig active = mesh.getTcpRetryConfig();
101
+ Serial.println();
102
+ Serial.println(F("Effective TCP retry configuration:"));
103
+ Serial.printf(" maxRetries = %u\n",
104
+ (unsigned)active.maxRetries);
105
+ Serial.printf(" retryDelayMs = %u\n",
106
+ (unsigned)active.retryDelayMs);
107
+ Serial.printf(" stabilizationDelayMs = %u\n",
108
+ (unsigned)active.stabilizationDelayMs);
109
+ Serial.printf(" exhaustionReconnectDelayMs = %u\n",
110
+ (unsigned)active.exhaustionReconnectDelayMs);
111
+ Serial.printf(" failureBlockDurationMs = %u\n",
112
+ (unsigned)active.failureBlockDurationMs);
113
+
114
+ // Worst-case time spent retrying before falling back to a WiFi reconnect.
115
+ uint32_t worstCase = 0;
116
+ for (uint8_t i = 0; i < active.maxRetries; ++i) {
117
+ worstCase += painlessmesh::tcp::retryBackoffDelay(active, i);
118
+ }
119
+ Serial.printf(" -> worst-case retry time = %u ms\n", (unsigned)worstCase);
120
+ Serial.printf(" -> plus reconnect delay = %u ms\n",
121
+ (unsigned)(worstCase + active.exhaustionReconnectDelayMs));
122
+ Serial.println();
123
+
124
+ mesh.onReceive(&receivedCallback);
125
+ mesh.onNewConnection(&newConnectionCallback);
126
+ mesh.onDroppedConnection(&droppedConnectionCallback);
127
+
128
+ userScheduler.addTask(taskSendMessage);
129
+ taskSendMessage.enable();
130
+ }
131
+
132
+ void loop() {
133
+ mesh.update();
134
+ }
135
+
136
+ void sendMessage() {
137
+ String msg = "Hello from node ";
138
+ msg += mesh.getNodeId();
139
+ mesh.sendBroadcast(msg);
140
+ }
141
+
142
+ void receivedCallback(uint32_t from, String &msg) {
143
+ Serial.printf("tcpRetryConfig: Received from %u msg=%s\n", from, msg.c_str());
144
+ }
145
+
146
+ void newConnectionCallback(uint32_t nodeId) {
147
+ Serial.printf("--> Connected to node %u\n", nodeId);
148
+ }
149
+
150
+ void droppedConnectionCallback(uint32_t nodeId) {
151
+ // With an aggressive profile you should expect to see this more often -
152
+ // and to see the reconnect that follows it happen much sooner.
153
+ Serial.printf("--> Dropped connection to node %u\n", nodeId);
154
+ }
package/keywords.txt CHANGED
@@ -4,6 +4,7 @@
4
4
  painlessMesh KEYWORD1
5
5
  Scheduler KEYWORD1
6
6
  Task KEYWORD1
7
+ TcpRetryConfig KEYWORD1
7
8
 
8
9
  # Methods and Functions (KEYWORD2)
9
10
  init KEYWORD2
@@ -18,6 +19,8 @@ getNodeList KEYWORD2
18
19
  getNodeId KEYWORD2
19
20
  getNodeTime KEYWORD2
20
21
  setDebugMsgTypes KEYWORD2
22
+ setTcpRetryConfig KEYWORD2
23
+ getTcpRetryConfig KEYWORD2
21
24
  subConnectionJson KEYWORD2
22
25
  asNodeTree KEYWORD2
23
26
 
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.19",
9
+ "version": "1.10.0",
10
10
  "frameworks": [
11
11
  "arduino"
12
12
  ],
@@ -87,7 +87,10 @@
87
87
  "examples/namedMesh/namedMesh.ino",
88
88
  "examples/otaReceiver/otaReceiver.ino",
89
89
  "examples/otaSender/otaSender.ino",
90
+ "examples/alteriom/mppt_example/alteriom_mppt_example.ino",
90
91
  "examples/priority/priority_basic_example.ino",
92
+ "examples/priority/priority_with_queue.ino",
93
+ "examples/sendToInternet/mock_server_test.ino",
91
94
  "examples/sendToInternet/sendToInternet.ino",
92
95
  "examples/sharedGateway/sharedGateway.ino",
93
96
  "examples/startHere/startHere.ino",