@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
|
@@ -173,12 +173,34 @@ class BridgeCoordinationPackage : public plugin::BroadcastPackage {
|
|
|
173
173
|
template <typename T>
|
|
174
174
|
class PackageHandler : public layout::Layout<T> {
|
|
175
175
|
public:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
// NOTE: scheduler is optional (defaults to nullptr) for backward
|
|
177
|
+
// compatibility with existing call sites; without it we can't detect
|
|
178
|
+
// the "current" task and behaviour falls back to the original one.
|
|
179
|
+
// Passing the scheduler (mesh.hpp already has it as mScheduler) avoids
|
|
180
|
+
// the use-after-free described below.
|
|
181
|
+
void stop(Scheduler* scheduler = nullptr) {
|
|
182
|
+
// If stop() is called from within the callback of one of the tasks in
|
|
183
|
+
// taskList (e.g. promoteToBridge()), that task is
|
|
184
|
+
// "scheduler->getCurrentTask()" at this exact moment. Disabling it,
|
|
185
|
+
// clearing its callback, or letting its shared_ptr refcount drop to
|
|
186
|
+
// zero here would destroy its closure - which is still on the stack,
|
|
187
|
+
// in the middle of its own execution - causing a use-after-free crash
|
|
188
|
+
// (same bug family as upstream issue #373, but triggered by the
|
|
189
|
+
// shared_ptr refcount instead of a raw delete in onDisable).
|
|
190
|
+
// We simply leave it in the list: addTask() will recognise it as
|
|
191
|
+
// "disabled with a single reference" and reuse it on the next call,
|
|
192
|
+
// exactly like it already does for disabled anonymous tasks (see the
|
|
193
|
+
// comment on addTask() above).
|
|
194
|
+
Task* current = scheduler ? scheduler->getCurrentTask() : nullptr;
|
|
195
|
+
for (auto it = taskList.begin(); it != taskList.end();) {
|
|
196
|
+
if (current != nullptr && it->get() == current) {
|
|
197
|
+
++it;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
(*it)->disable();
|
|
201
|
+
(*it)->setCallback(NULL);
|
|
202
|
+
it = taskList.erase(it);
|
|
180
203
|
}
|
|
181
|
-
taskList.clear();
|
|
182
204
|
callbackList.clear();
|
|
183
205
|
}
|
|
184
206
|
|
package/src/painlessmesh/tcp.hpp
CHANGED
|
@@ -30,6 +30,84 @@ static const uint32_t TCP_EXHAUSTION_RECONNECT_DELAY_MS = 10000; // 10 seconds b
|
|
|
30
30
|
// This prevents repeatedly trying to connect to nodes with unresponsive TCP servers
|
|
31
31
|
static const uint32_t TCP_FAILURE_BLOCK_DURATION_MS = 60000;
|
|
32
32
|
|
|
33
|
+
// Safe operating bounds applied by clampTcpRetryConfig().
|
|
34
|
+
// Each retry allocates a new AsyncClient and schedules a task, so an unbounded
|
|
35
|
+
// maxRetries is a heap and recursion-depth hazard on ESP8266. A zero retry
|
|
36
|
+
// delay would schedule retry tasks with no spacing, i.e. a hot loop allocating
|
|
37
|
+
// an AsyncClient per scheduler tick.
|
|
38
|
+
static const uint8_t TCP_RETRY_MAX_RETRIES_LIMIT = 10;
|
|
39
|
+
static const uint32_t TCP_RETRY_MIN_DELAY_MS = 50;
|
|
40
|
+
static const uint32_t TCP_RETRY_MAX_DELAY_MS = 60000;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* User-tunable TCP connection retry parameters
|
|
44
|
+
*
|
|
45
|
+
* One instance is stored per mesh (painlessmesh::Mesh), configured through
|
|
46
|
+
* Mesh::setTcpRetryConfig(). The member defaults are spelled as the legacy
|
|
47
|
+
* constants above rather than as literals, so the "defaults match the previous
|
|
48
|
+
* hardcoded behaviour exactly" guarantee is enforced by the compiler and cannot
|
|
49
|
+
* silently drift.
|
|
50
|
+
*
|
|
51
|
+
* NOTE: this deliberately lives on the mesh instance rather than at namespace
|
|
52
|
+
* scope. A mutable namespace-scope instance in this header would give every
|
|
53
|
+
* translation unit its own private copy, so a sketch that configured the mesh
|
|
54
|
+
* in one TU and connected from another would silently fall back to defaults
|
|
55
|
+
* with no diagnostic.
|
|
56
|
+
*/
|
|
57
|
+
struct TcpRetryConfig {
|
|
58
|
+
/// Max TCP connect retry attempts before falling back to a WiFi reconnect
|
|
59
|
+
uint8_t maxRetries = TCP_CONNECT_MAX_RETRIES;
|
|
60
|
+
/// Base delay between retry attempts (ms), scaled by exponential backoff
|
|
61
|
+
uint32_t retryDelayMs = TCP_CONNECT_RETRY_DELAY_MS;
|
|
62
|
+
/// Delay after IP acquisition before the TCP connect is attempted (ms)
|
|
63
|
+
uint32_t stabilizationDelayMs = TCP_CONNECT_STABILIZATION_DELAY_MS;
|
|
64
|
+
/// Delay before the WiFi reconnect that follows retry exhaustion (ms)
|
|
65
|
+
uint32_t exhaustionReconnectDelayMs = TCP_EXHAUSTION_RECONNECT_DELAY_MS;
|
|
66
|
+
/// Duration a node is blocklisted after retry exhaustion (ms, 0 = never)
|
|
67
|
+
uint32_t failureBlockDurationMs = TCP_FAILURE_BLOCK_DURATION_MS;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Delay before retry attempt number `retryCount`
|
|
72
|
+
*
|
|
73
|
+
* Exponential backoff with the multiplier capped at 8, giving the default
|
|
74
|
+
* sequence 1s, 2s, 4s, 8s, 8s. Capping prevents excessive delays and keeps the
|
|
75
|
+
* product clear of uint32_t overflow.
|
|
76
|
+
*
|
|
77
|
+
* @param cfg Active retry configuration
|
|
78
|
+
* @param retryCount Zero-based retry attempt number
|
|
79
|
+
* @return Delay in milliseconds
|
|
80
|
+
*/
|
|
81
|
+
inline uint32_t retryBackoffDelay(const TcpRetryConfig &cfg,
|
|
82
|
+
uint8_t retryCount) {
|
|
83
|
+
uint8_t backoffMultiplier = (retryCount < 3) ? (1U << retryCount) : 8;
|
|
84
|
+
return cfg.retryDelayMs * backoffMultiplier;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Coerce a retry configuration into safe operating bounds
|
|
89
|
+
*
|
|
90
|
+
* Only the two values that can render a node unusable are clamped:
|
|
91
|
+
* maxRetries (heap/recursion pressure) and retryDelayMs (hot-loop floor and
|
|
92
|
+
* overflow ceiling). maxRetries == 0 is explicitly allowed - it means "fall
|
|
93
|
+
* back to a WiFi reconnect on the first TCP error", which is the whole point
|
|
94
|
+
* of the low-latency profile. stabilizationDelayMs, exhaustionReconnectDelayMs
|
|
95
|
+
* and failureBlockDurationMs are left alone because 0 is meaningful for each
|
|
96
|
+
* (skip stabilization / reconnect immediately / never blocklist).
|
|
97
|
+
*
|
|
98
|
+
* @param cfg Configuration to clamp (taken by value, returned clamped)
|
|
99
|
+
* @return The clamped configuration
|
|
100
|
+
*/
|
|
101
|
+
inline TcpRetryConfig clampTcpRetryConfig(TcpRetryConfig cfg) {
|
|
102
|
+
if (cfg.maxRetries > TCP_RETRY_MAX_RETRIES_LIMIT)
|
|
103
|
+
cfg.maxRetries = TCP_RETRY_MAX_RETRIES_LIMIT;
|
|
104
|
+
if (cfg.retryDelayMs < TCP_RETRY_MIN_DELAY_MS)
|
|
105
|
+
cfg.retryDelayMs = TCP_RETRY_MIN_DELAY_MS;
|
|
106
|
+
if (cfg.retryDelayMs > TCP_RETRY_MAX_DELAY_MS)
|
|
107
|
+
cfg.retryDelayMs = TCP_RETRY_MAX_DELAY_MS;
|
|
108
|
+
return cfg;
|
|
109
|
+
}
|
|
110
|
+
|
|
33
111
|
inline uint32_t encodeNodeId(const uint8_t *hwaddr) {
|
|
34
112
|
using namespace painlessmesh::logger;
|
|
35
113
|
Log(GENERAL, "encodeNodeId():\n");
|
|
@@ -93,8 +171,9 @@ void initServer(AsyncServer &server, M &mesh) {
|
|
|
93
171
|
*
|
|
94
172
|
* This function attempts to connect to the mesh network via TCP.
|
|
95
173
|
* If the connection fails (error -14 ERR_CONN or other errors), it will
|
|
96
|
-
* retry up to
|
|
97
|
-
* WiFi reconnection cycle.
|
|
174
|
+
* retry up to the configured maxRetries times before triggering a full
|
|
175
|
+
* WiFi reconnection cycle. See Mesh::setTcpRetryConfig() to tune the retry
|
|
176
|
+
* envelope; the defaults reproduce the historic hardcoded behaviour.
|
|
98
177
|
*
|
|
99
178
|
* The retry mechanism helps handle timing issues where:
|
|
100
179
|
* - The TCP server may not be immediately ready after AP initialization
|
|
@@ -117,16 +196,49 @@ template <class T, class M>
|
|
|
117
196
|
void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh,
|
|
118
197
|
uint8_t retryCount = 0) {
|
|
119
198
|
using namespace logger;
|
|
120
|
-
|
|
199
|
+
|
|
200
|
+
// Snapshot the retry configuration once, before any lambda is built. The
|
|
201
|
+
// onError lambda outlives this stack frame, so it captures this snapshot by
|
|
202
|
+
// value rather than reading mesh's member later: a user callback could
|
|
203
|
+
// otherwise mutate the config between the connect attempt and the error,
|
|
204
|
+
// tearing the backoff schedule mid-sequence. Each retry re-enters connect()
|
|
205
|
+
// and re-snapshots, so a config change still takes effect on the next
|
|
206
|
+
// attempt.
|
|
207
|
+
const TcpRetryConfig cfg = mesh.getTcpRetryConfig();
|
|
208
|
+
|
|
121
209
|
Log(CONNECTION, "tcp::connect(): Attempting connection to port %d (attempt %d/%d)\n",
|
|
122
|
-
port, retryCount + 1,
|
|
210
|
+
port, retryCount + 1, cfg.maxRetries + 1);
|
|
211
|
+
|
|
212
|
+
// Guard shared between onError and onConnect: under normal conditions a
|
|
213
|
+
// TCP connection attempt leads to only ONE of the two outcomes. But if
|
|
214
|
+
// WiFi drops right as the TCP handshake completes, AsyncTCP can queue
|
|
215
|
+
// BOTH events (connection succeeded at the TCP level + abort due to the
|
|
216
|
+
// WiFi link being lost) for the same AsyncClient, and both callbacks end
|
|
217
|
+
// up firing for the SAME object. Without this guard, the client would be
|
|
218
|
+
// "handed over" twice: once to a BufferedConnection (via onConnect, which
|
|
219
|
+
// becomes its owner and will delete it via its own destructor) and once
|
|
220
|
+
// to the retry path (via onError, which schedules it again for deletion)
|
|
221
|
+
// - two scheduleAsyncClientDeletion() calls on the same pointer, and
|
|
222
|
+
// therefore a double deferred deletion on the same object once both
|
|
223
|
+
// tasks fire.
|
|
224
|
+
auto claimed = std::make_shared<bool>(false);
|
|
123
225
|
|
|
124
226
|
// Store retry count and connection parameters for the error handler
|
|
125
227
|
// We need to capture these by value since they're used in the lambda
|
|
126
|
-
client.onError([&mesh, ip, port, retryCount](void *, AsyncClient *client, int8_t err) {
|
|
228
|
+
client.onError([&mesh, ip, port, retryCount, claimed, cfg](void *, AsyncClient *client, int8_t err) {
|
|
229
|
+
if (*claimed) {
|
|
230
|
+
// onConnect has already claimed this client (the connection actually
|
|
231
|
+
// succeeded, and it's already been wrapped in a BufferedConnection
|
|
232
|
+
// that owns it): don't touch the same object again.
|
|
233
|
+
Log(CONNECTION,
|
|
234
|
+
"tcp_err(): onError fired after onConnect had already claimed "
|
|
235
|
+
"the client - ignored to avoid double handling\n");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
*claimed = true;
|
|
127
239
|
if (mesh.semaphoreTake()) {
|
|
128
|
-
Log(CONNECTION, "tcp_err(): error trying to connect %d (attempt %d/%d)\n",
|
|
129
|
-
err, retryCount + 1,
|
|
240
|
+
Log(CONNECTION, "tcp_err(): error trying to connect %d (attempt %d/%d)\n",
|
|
241
|
+
err, retryCount + 1, cfg.maxRetries + 1);
|
|
130
242
|
|
|
131
243
|
// Check if we have retries left - retry logic only works on real hardware
|
|
132
244
|
// In test environment (PAINLESSMESH_BOOST), fall through to dropped connection
|
|
@@ -134,27 +246,21 @@ void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh,
|
|
|
134
246
|
(void)ip;
|
|
135
247
|
(void)port;
|
|
136
248
|
#if !defined(PAINLESSMESH_BOOST) && (defined(ESP32) || defined(ESP8266))
|
|
137
|
-
if (retryCount <
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// - retryCount=3: 1000ms * 8 = 8s (capped at 8)
|
|
144
|
-
// - retryCount=4: 1000ms * 8 = 8s (capped at 8)
|
|
145
|
-
// Cap multiplier at 8 to prevent excessive delays
|
|
146
|
-
uint8_t backoffMultiplier = (retryCount < 3) ? (1U << retryCount) : 8;
|
|
147
|
-
uint32_t retryDelay = TCP_CONNECT_RETRY_DELAY_MS * backoffMultiplier;
|
|
148
|
-
|
|
149
|
-
Log(CONNECTION, "tcp_err(): Scheduling retry in %u ms (backoff x%d)\n",
|
|
150
|
-
retryDelay, backoffMultiplier);
|
|
249
|
+
if (retryCount < cfg.maxRetries) {
|
|
250
|
+
// Delay grows with exponential backoff, multiplier capped at 8.
|
|
251
|
+
// With the default 1000ms base that is: 1s, 2s, 4s, 8s, 8s.
|
|
252
|
+
uint32_t retryDelay = retryBackoffDelay(cfg, retryCount);
|
|
253
|
+
|
|
254
|
+
Log(CONNECTION, "tcp_err(): Scheduling retry in %u ms\n", retryDelay);
|
|
151
255
|
|
|
152
256
|
// Schedule a retry after a delay using the mesh's task scheduler
|
|
153
257
|
// Note: &mesh is captured by reference because:
|
|
154
258
|
// 1. Mesh is a singleton that lives for the program's lifetime
|
|
155
259
|
// 2. The task scheduler belongs to the mesh, so mesh is always valid when task runs
|
|
156
260
|
// 3. Copying the mesh object is not possible/allowed
|
|
157
|
-
// Recursion depth is strictly bounded by
|
|
261
|
+
// Recursion depth is strictly bounded by cfg.maxRetries (default 5),
|
|
262
|
+
// which clampTcpRetryConfig() never lets exceed
|
|
263
|
+
// TCP_RETRY_MAX_RETRIES_LIMIT (10).
|
|
158
264
|
mesh.addTask([&mesh, ip, port, retryCount]() {
|
|
159
265
|
Log(CONNECTION, "tcp_err(): Retrying TCP connection...\n");
|
|
160
266
|
|
|
@@ -178,7 +284,7 @@ void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh,
|
|
|
178
284
|
// Adding a significant delay before reconnection prevents rapid reconnection loops
|
|
179
285
|
// when the TCP server is persistently unavailable or overloaded
|
|
180
286
|
Log(CONNECTION, "tcp_err(): All %d retries exhausted for IP %s\n",
|
|
181
|
-
|
|
287
|
+
cfg.maxRetries + 1, ip.toString().c_str());
|
|
182
288
|
|
|
183
289
|
// Block this node temporarily to prevent immediate reconnection to the same unresponsive node
|
|
184
290
|
// This helps when the bridge's TCP server is down but WiFi AP is still running
|
|
@@ -186,15 +292,19 @@ void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh,
|
|
|
186
292
|
#if !defined(PAINLESSMESH_BOOST)
|
|
187
293
|
// Try to decode nodeId from IP and block it
|
|
188
294
|
// Only works for mesh IPs (format: 10.x.x.1)
|
|
295
|
+
// A failureBlockDurationMs of 0 is an explicit "never blocklist" opt-out.
|
|
296
|
+
// Passing it through would set blockUntil = millis() + 0, which
|
|
297
|
+
// isNodeBlocked() reads as already expired - so the entry would do
|
|
298
|
+
// nothing except linger in the blocklist until cleanup sweeps it.
|
|
189
299
|
uint32_t failedNodeId = decodeNodeIdFromIP(ip);
|
|
190
|
-
if (failedNodeId != 0) {
|
|
300
|
+
if (failedNodeId != 0 && cfg.failureBlockDurationMs > 0) {
|
|
191
301
|
// Note: This requires M to be wifi::Mesh which has blockNodeAfterTCPFailure
|
|
192
|
-
mesh.blockNodeAfterTCPFailure(ip,
|
|
302
|
+
mesh.blockNodeAfterTCPFailure(ip, cfg.failureBlockDurationMs);
|
|
193
303
|
}
|
|
194
304
|
#endif
|
|
195
|
-
|
|
305
|
+
|
|
196
306
|
Log(CONNECTION, "tcp_err(): Scheduling WiFi reconnection in %u ms\n",
|
|
197
|
-
|
|
307
|
+
cfg.exhaustionReconnectDelayMs);
|
|
198
308
|
|
|
199
309
|
// Defer deletion of the failed AsyncClient to prevent heap corruption
|
|
200
310
|
// Use the centralized deletion scheduler to ensure proper spacing between deletions
|
|
@@ -208,13 +318,32 @@ void connect(AsyncClient &client, IPAddress ip, uint16_t port, M &mesh,
|
|
|
208
318
|
mesh.addTask([&mesh]() {
|
|
209
319
|
Log(CONNECTION, "tcp_err(): Executing delayed WiFi reconnection after retry exhaustion\n");
|
|
210
320
|
mesh.droppedConnectionCallbacks.execute(0, true);
|
|
211
|
-
},
|
|
321
|
+
}, cfg.exhaustionReconnectDelayMs);
|
|
212
322
|
mesh.semaphoreGive();
|
|
213
323
|
}
|
|
214
324
|
});
|
|
215
325
|
|
|
216
326
|
client.onConnect(
|
|
217
|
-
[&mesh](void *, AsyncClient *client) {
|
|
327
|
+
[&mesh, claimed](void *, AsyncClient *client) {
|
|
328
|
+
if (*claimed) {
|
|
329
|
+
// onError has already fired for this client (e.g. an abort
|
|
330
|
+
// caused by losing WiFi right while the TCP handshake was
|
|
331
|
+
// completing): the client is already scheduled for deletion by
|
|
332
|
+
// the retry path. Wrapping it in a new BufferedConnection now
|
|
333
|
+
// would give it two owners at once. This connection did however
|
|
334
|
+
// genuinely succeed at the TCP level (that's why onConnect
|
|
335
|
+
// fired): close it explicitly here, so that when the deferred
|
|
336
|
+
// deletion already scheduled by onError fires, it finds a
|
|
337
|
+
// properly closed client instead of one still "connected" -
|
|
338
|
+
// otherwise we'd fall right back into the bug we're fixing.
|
|
339
|
+
Log(CONNECTION,
|
|
340
|
+
"tcp::connect(): onConnect fired after onError had already "
|
|
341
|
+
"claimed the client - closing the connection and discarding "
|
|
342
|
+
"it\n");
|
|
343
|
+
client->close();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
*claimed = true;
|
|
218
347
|
if (mesh.semaphoreTake()) {
|
|
219
348
|
Log(CONNECTION, "New STA connection incoming\n");
|
|
220
349
|
auto conn = std::make_shared<T>(client, &mesh, true);
|
|
@@ -8,12 +8,9 @@
|
|
|
8
8
|
* security and robustness of the mesh network.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
#include <list>
|
|
12
|
-
#include <map>
|
|
13
11
|
#ifndef ARDUINO
|
|
14
12
|
#include <chrono>
|
|
15
13
|
#endif
|
|
16
|
-
#include "ArduinoJson.h"
|
|
17
14
|
#include "painlessmesh/configuration.hpp"
|
|
18
15
|
|
|
19
16
|
namespace painlessmesh {
|
|
@@ -45,146 +42,6 @@ struct ValidationConfig {
|
|
|
45
42
|
bool strict_type_checking = true; // Enable strict type validation
|
|
46
43
|
};
|
|
47
44
|
|
|
48
|
-
/**
|
|
49
|
-
* Rate limiting for preventing message spam
|
|
50
|
-
*/
|
|
51
|
-
class RateLimiter {
|
|
52
|
-
public:
|
|
53
|
-
RateLimiter(size_t max_messages_per_second = 10, size_t window_size_ms = 1000)
|
|
54
|
-
: max_messages_(max_messages_per_second), window_size_(window_size_ms) {}
|
|
55
|
-
|
|
56
|
-
bool allow_message(uint32_t node_id) {
|
|
57
|
-
uint32_t current_time = get_current_time();
|
|
58
|
-
auto& history = node_history_[node_id];
|
|
59
|
-
|
|
60
|
-
// Remove old entries outside the window
|
|
61
|
-
while (!history.empty() &&
|
|
62
|
-
(current_time - history.front()) > window_size_) {
|
|
63
|
-
history.pop_front();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check if limit is exceeded
|
|
67
|
-
if (history.size() >= max_messages_) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Record this message
|
|
72
|
-
history.push_back(current_time);
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
void clear_node_history(uint32_t node_id) { node_history_.erase(node_id); }
|
|
77
|
-
|
|
78
|
-
void clear_all_history() { node_history_.clear(); }
|
|
79
|
-
|
|
80
|
-
private:
|
|
81
|
-
uint32_t get_current_time() const {
|
|
82
|
-
#ifdef ARDUINO
|
|
83
|
-
return millis();
|
|
84
|
-
#else
|
|
85
|
-
auto now = std::chrono::steady_clock::now();
|
|
86
|
-
auto duration = now.time_since_epoch();
|
|
87
|
-
return std::chrono::duration_cast<std::chrono::milliseconds>(duration)
|
|
88
|
-
.count();
|
|
89
|
-
#endif
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
size_t max_messages_;
|
|
93
|
-
size_t window_size_;
|
|
94
|
-
std::map<uint32_t, std::list<uint32_t>> node_history_;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* JSON message validator
|
|
99
|
-
*/
|
|
100
|
-
class MessageValidator {
|
|
101
|
-
public:
|
|
102
|
-
explicit MessageValidator(const ValidationConfig& config = ValidationConfig{})
|
|
103
|
-
: config_(config) {}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Validate a JSON message for basic structure and security
|
|
107
|
-
*/
|
|
108
|
-
ValidationResult validate_message(const JsonObject& obj,
|
|
109
|
-
size_t message_size = 0) const {
|
|
110
|
-
// Check message size
|
|
111
|
-
if (message_size > config_.max_message_size) {
|
|
112
|
-
return ValidationResult::MESSAGE_TOO_LARGE;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Check required fields based on message type
|
|
116
|
-
if (!obj["type"].is<int>()) {
|
|
117
|
-
return ValidationResult::MISSING_REQUIRED_FIELD;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Validate node IDs if present
|
|
121
|
-
if (obj["from"].is<uint32_t>()) {
|
|
122
|
-
uint32_t from_id = obj["from"].as<uint32_t>();
|
|
123
|
-
if (!is_valid_node_id(from_id)) {
|
|
124
|
-
return ValidationResult::INVALID_NODE_ID;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (obj["dest"].is<uint32_t>()) {
|
|
129
|
-
uint32_t dest_id = obj["dest"].as<uint32_t>();
|
|
130
|
-
if (dest_id != 0 && !is_valid_node_id(dest_id)) { // 0 is broadcast
|
|
131
|
-
return ValidationResult::INVALID_NODE_ID;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Validate string fields
|
|
136
|
-
for (JsonPair pair : obj) {
|
|
137
|
-
if (pair.value().is<const char*>()) {
|
|
138
|
-
const char* str_value = pair.value().as<const char*>();
|
|
139
|
-
if (strlen(str_value) > config_.max_string_length) {
|
|
140
|
-
return ValidationResult::INVALID_FIELD_VALUE;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return ValidationResult::VALID;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Validate node ID range
|
|
150
|
-
*/
|
|
151
|
-
bool is_valid_node_id(uint32_t node_id) const {
|
|
152
|
-
return node_id >= config_.min_node_id && node_id <= config_.max_node_id;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get validation error message
|
|
157
|
-
*/
|
|
158
|
-
const char* get_error_message(ValidationResult result) const {
|
|
159
|
-
switch (result) {
|
|
160
|
-
case ValidationResult::VALID:
|
|
161
|
-
return "Valid";
|
|
162
|
-
case ValidationResult::INVALID_JSON:
|
|
163
|
-
return "Invalid JSON format";
|
|
164
|
-
case ValidationResult::MISSING_REQUIRED_FIELD:
|
|
165
|
-
return "Missing required field";
|
|
166
|
-
case ValidationResult::INVALID_FIELD_TYPE:
|
|
167
|
-
return "Invalid field type";
|
|
168
|
-
case ValidationResult::INVALID_FIELD_VALUE:
|
|
169
|
-
return "Invalid field value";
|
|
170
|
-
case ValidationResult::MESSAGE_TOO_LARGE:
|
|
171
|
-
return "Message too large";
|
|
172
|
-
case ValidationResult::INVALID_NODE_ID:
|
|
173
|
-
return "Invalid node ID";
|
|
174
|
-
case ValidationResult::RATE_LIMIT_EXCEEDED:
|
|
175
|
-
return "Rate limit exceeded";
|
|
176
|
-
default:
|
|
177
|
-
return "Unknown error";
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const ValidationConfig& get_config() const { return config_; }
|
|
182
|
-
void set_config(const ValidationConfig& config) { config_ = config; }
|
|
183
|
-
|
|
184
|
-
private:
|
|
185
|
-
ValidationConfig config_;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
45
|
/**
|
|
189
46
|
* Secure random number generation for mesh operations
|
|
190
47
|
*/
|
package/docs/README.md
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# 📚 AlteriomPainlessMesh Documentation
|
|
2
|
-
|
|
3
|
-
Welcome to the comprehensive documentation for the Alteriom fork of painlessMesh - a user-friendly ESP8266/ESP32 mesh networking library with advanced OTA updates, MQTT integration, and structured IoT packages.
|
|
4
|
-
|
|
5
|
-
## 🌟 What's New in Alteriom Fork
|
|
6
|
-
|
|
7
|
-
- **Broadcast OTA Distribution** - 98% network traffic reduction for large meshes
|
|
8
|
-
- **MQTT Status Bridge** - Enterprise monitoring integration (Grafana, InfluxDB)
|
|
9
|
-
- **Structured Packages** - SensorPackage, CommandPackage, StatusPackage
|
|
10
|
-
- **Enhanced CI/CD** - Automated releases to NPM, PlatformIO, Arduino Library Manager
|
|
11
|
-
|
|
12
|
-
## 📖 Documentation Structure
|
|
13
|
-
|
|
14
|
-
### Getting Started
|
|
15
|
-
|
|
16
|
-
- [Quick Start Guide](getting-started/quickstart.md) - Get up and running in minutes
|
|
17
|
-
- [Installation](getting-started/installation.md) - Detailed installation instructions
|
|
18
|
-
- [First Mesh Network](getting-started/first-mesh.md) - Your first working mesh
|
|
19
|
-
|
|
20
|
-
### Architecture & Design
|
|
21
|
-
|
|
22
|
-
- [Mesh Architecture](architecture/mesh-architecture.md) - How painlessMesh works internally
|
|
23
|
-
- [Plugin System](architecture/plugin-system.md) - Understanding the plugin architecture
|
|
24
|
-
- [Message Routing](architecture/routing.md) - Message routing algorithms and strategies
|
|
25
|
-
- [Time Synchronization](architecture/time-sync.md) - Mesh-wide time synchronization
|
|
26
|
-
|
|
27
|
-
### API Reference
|
|
28
|
-
|
|
29
|
-
- [Core API](api/core-api.md) - Main painlessMesh class methods
|
|
30
|
-
- [Shared Gateway API](api/shared-gateway.md) - Shared Gateway Mode reference (v1.9.0+)
|
|
31
|
-
- [Plugin API](api/plugin-api.md) - Creating custom packages and plugins
|
|
32
|
-
- [Configuration](api/configuration.md) - Configuration options and constants
|
|
33
|
-
- [Callbacks](api/callbacks.md) - Event handling and callbacks
|
|
34
|
-
|
|
35
|
-
### Tutorials & Examples
|
|
36
|
-
|
|
37
|
-
- [Basic Examples](tutorials/basic-examples.md) - Simple mesh networking examples
|
|
38
|
-
- [Custom Packages](tutorials/custom-packages.md) - Creating your own message types
|
|
39
|
-
- [Sensor Networks](tutorials/sensor-networks.md) - Building IoT sensor networks
|
|
40
|
-
- [Bridge to Internet](../BRIDGE_TO_INTERNET.md) - Connecting mesh to WiFi/Internet/MQTT
|
|
41
|
-
|
|
42
|
-
### Alteriom Extensions
|
|
43
|
-
|
|
44
|
-
- [Alteriom Overview](alteriom/overview.md) - Alteriom-specific functionality
|
|
45
|
-
- [Sensor Packages](alteriom/sensor-packages.md) - Environmental sensor data handling
|
|
46
|
-
- [Command System](alteriom/command-system.md) - Device command and control
|
|
47
|
-
- [Status Monitoring](alteriom/status-monitoring.md) - Device health and diagnostics
|
|
48
|
-
|
|
49
|
-
### 📡 MQTT Integration
|
|
50
|
-
|
|
51
|
-
- **[MQTT Bridge Commands](MQTT_BRIDGE_COMMANDS.md)** - Complete MQTT command API
|
|
52
|
-
- **[MQTT Bridge Implementation](MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md)** - Implementation details
|
|
53
|
-
- **[MQTT Schema Compliance](MQTT_SCHEMA_COMPLIANCE.md)** - Schema validation
|
|
54
|
-
- **[OTA Commands Reference](OTA_COMMANDS_REFERENCE.md)** - OTA update commands
|
|
55
|
-
- **[Mesh Topology Guide](MESH_TOPOLOGY_GUIDE.md)** - Topology reporting over MQTT
|
|
56
|
-
|
|
57
|
-
### Advanced Topics
|
|
58
|
-
|
|
59
|
-
- [Performance Optimization](advanced/performance.md) - Optimizing mesh performance
|
|
60
|
-
- [Memory Management](advanced/memory.md) - Managing ESP8266/ESP32 memory constraints
|
|
61
|
-
- [Security Considerations](advanced/security.md) - Securing your mesh network
|
|
62
|
-
- [OTA Updates](advanced/ota.md) - Over-the-air firmware updates in mesh
|
|
63
|
-
|
|
64
|
-
### Troubleshooting
|
|
65
|
-
|
|
66
|
-
- [Common Issues](troubleshooting/common-issues.md) - Solutions to frequent problems
|
|
67
|
-
- [ESP32-C6 Compatibility](troubleshooting/ESP32_C6_COMPATIBILITY.md) - ESP32-C6 specific issues and solutions
|
|
68
|
-
- [Debugging Guide](troubleshooting/debugging.md) - Tools and techniques for debugging
|
|
69
|
-
- [FAQ](troubleshooting/faq.md) - Frequently asked questions
|
|
70
|
-
- [Network Issues](troubleshooting/network-issues.md) - Connectivity and mesh topology problems
|
|
71
|
-
|
|
72
|
-
### Development
|
|
73
|
-
|
|
74
|
-
- [Contributing](development/contributing.md) - How to contribute to painlessMesh
|
|
75
|
-
- [Building & Testing](development/building.md) - Development environment setup
|
|
76
|
-
- [Documentation](development/documentation.md) - Contributing to documentation
|
|
77
|
-
- [Release Process](development/releases.md) - Understanding releases and versioning
|
|
78
|
-
- **[Docker Testing](development/DOCKER_TESTING.md)** - Containerized testing environment
|
|
79
|
-
- **[Testing Summary](development/TESTING_SUMMARY.md)** - Complete test suite overview
|
|
80
|
-
- **[Arduino Compliance](development/ARDUINO_COMPLIANCE_SUMMARY.md)** - Arduino Library Manager standards
|
|
81
|
-
- **[PlatformIO Usage](development/PLATFORMIO_USAGE.md)** - PlatformIO integration guide
|
|
82
|
-
|
|
83
|
-
### 📦 Releases & Changelogs
|
|
84
|
-
|
|
85
|
-
- **[Feature History](releases/FEATURE_HISTORY.md)** - ⭐ Consolidated Phase 1 & 2 development history
|
|
86
|
-
- **[Release Notes v1.7.0](releases/RELEASE_NOTES_1.7.0.md)** - Detailed v1.7.0 release notes
|
|
87
|
-
- **[CHANGELOG](../CHANGELOG.md)** - Complete version history
|
|
88
|
-
- **[RELEASE_GUIDE](../RELEASE_GUIDE.md)** - Maintainer release process
|
|
89
|
-
- [Phase 1 Details](releases/PHASE1_SUMMARY.md) - v1.6.x detailed summary (archived)
|
|
90
|
-
- [Phase 2 Details](releases/PHASE2_SUMMARY.md) - v1.7.x detailed summary (archived)
|
|
91
|
-
|
|
92
|
-
### 🗂️ Core Documentation (Root)
|
|
93
|
-
|
|
94
|
-
- **[Main README](../README.md)** - Project overview and quick start
|
|
95
|
-
- **[CONTRIBUTING](../CONTRIBUTING.md)** - Contribution guidelines
|
|
96
|
-
- **[LICENSE](../LICENSE)** - LGPL-3.0 license terms
|
|
97
|
-
|
|
98
|
-
### 🗃️ Historical & Archive
|
|
99
|
-
|
|
100
|
-
- **[Archive](archive/)** - Historical bug fixes and obsolete documentation
|
|
101
|
-
- Bug fix documentation (SCONS, VECTOR, LIBRARY fixes)
|
|
102
|
-
- Legacy deployment guides
|
|
103
|
-
- Superseded release documentation
|
|
104
|
-
|
|
105
|
-
### 🚀 Improvements & Enhancements
|
|
106
|
-
|
|
107
|
-
- **[Improvements Overview](improvements/README.md)** - Complete guide to library enhancements
|
|
108
|
-
- **[OTA & Status Enhancements](improvements/OTA_STATUS_ENHANCEMENTS.md)** 📋 - Complete reference for all options
|
|
109
|
-
- ✅ Phase 1 (v1.6.x): Compressed OTA + Enhanced Status
|
|
110
|
-
- ✅ Phase 2 (v1.7.0): Broadcast OTA + MQTT Bridge
|
|
111
|
-
- 📋 Phase 3 (Future): Progressive rollout, P2P distribution, real-time telemetry
|
|
112
|
-
- **[Implementation History](improvements/IMPLEMENTATION_HISTORY.md)** 🔧 - Technical details for Phases 1-2
|
|
113
|
-
- **[Future Proposals](improvements/FUTURE_PROPOSALS.md)** 🚀 - Phase 3+ roadmap and specifications
|
|
114
|
-
|
|
115
|
-
## Quick Links
|
|
116
|
-
|
|
117
|
-
- **[GitHub Repository](https://github.com/Alteriom/painlessMesh)**
|
|
118
|
-
- **[API Documentation](https://alteriom.github.io/painlessMesh/#/api/doxygen)**
|
|
119
|
-
- **[Community Forum](https://groups.google.com/forum/#!forum/painlessmesh-user)**
|
|
120
|
-
- **[Issue Tracker](https://github.com/Alteriom/painlessMesh/issues)**
|
|
121
|
-
|
|
122
|
-
## Need Help?
|
|
123
|
-
|
|
124
|
-
- Start with the [Quick Start Guide](getting-started/quickstart.md)
|
|
125
|
-
- Check the [FAQ](troubleshooting/faq.md) for common questions
|
|
126
|
-
- Browse [Examples](tutorials/basic-examples.md) for practical use cases
|
|
127
|
-
- Join our [Community Forum](https://groups.google.com/forum/#!forum/painlessmesh-user)
|
|
128
|
-
- Report bugs on the [Issue Tracker](https://github.com/Alteriom/painlessMesh/issues)
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
This documentation is maintained by the painlessMesh community. Contributions are welcome! See our [Documentation Contributing Guide](development/documentation.md) to get started.
|