@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
@@ -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
- void stop() {
177
- for (auto&& task : taskList) {
178
- task->disable();
179
- task->setCallback(NULL);
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
 
@@ -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 TCP_CONNECT_MAX_RETRIES times before triggering a full
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, TCP_CONNECT_MAX_RETRIES + 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, TCP_CONNECT_MAX_RETRIES + 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 < TCP_CONNECT_MAX_RETRIES) {
138
- // Calculate delay with exponential backoff: base_delay * 2^retryCount
139
- // This gives increasing time between retries as failures accumulate:
140
- // - retryCount=0: 1000ms * 1 = 1s
141
- // - retryCount=1: 1000ms * 2 = 2s
142
- // - retryCount=2: 1000ms * 4 = 4s
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 TCP_CONNECT_MAX_RETRIES (default: 5)
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
- TCP_CONNECT_MAX_RETRIES + 1, ip.toString().c_str());
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, TCP_FAILURE_BLOCK_DURATION_MS);
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
- TCP_EXHAUSTION_RECONNECT_DELAY_MS);
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
- }, TCP_EXHAUSTION_RECONNECT_DELAY_MS);
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.