@alteriom/painlessmesh 1.7.6 → 1.7.8

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 (38) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +96 -14
  3. package/RELEASE_GUIDE.md +36 -0
  4. package/docs/API_DESIGN_GUIDELINES.md +414 -0
  5. package/docs/BOOLEAN_NAMING_CONVENTION.md +235 -0
  6. package/docs/MQTT_BRIDGE_COMMANDS.md +10 -10
  7. package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +1 -1
  8. package/docs/MQTT_SCHEMA_COMPLIANCE.md +57 -2
  9. package/docs/PHASE1_GUIDE.md +1 -1
  10. package/docs/alteriom/overview.md +25 -2
  11. package/docs/architecture/plugin-system.md +1 -1
  12. package/docs/archive/RELEASE_SUMMARY.md +1 -1
  13. package/docs/releases/RELEASE_CHECKLIST_v1.7.6.md +389 -0
  14. package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +391 -0
  15. package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +794 -0
  16. package/docs/wiki/API-Reference.md +2 -2
  17. package/docs/wiki/Complete-Documentation.md +1 -1
  18. package/examples/alteriom/README.md +150 -4
  19. package/examples/alteriom/alteriom.ino +1 -1
  20. package/examples/alteriom/alteriom_sensor_package.hpp +914 -4
  21. package/examples/alteriomImproved/alteriom_sensor_package.hpp +1 -1
  22. package/examples/alteriomImproved/improved_sensor_node.ino +1 -1
  23. package/examples/alteriomMetricsHealth/alteriom_sensor_package.hpp +796 -0
  24. package/examples/alteriomMetricsHealth/metrics_health_node.ino +418 -0
  25. package/examples/alteriomMetricsHealth/platformio.ini +26 -0
  26. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +1 -1
  27. package/examples/alteriomPhase1/phase1_features.ino +2 -2
  28. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +1 -1
  29. package/examples/alteriomSensorNode/alteriom_sensor_node.ino +1 -1
  30. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +1 -1
  31. package/examples/bridge/enhanced_mqtt_bridge.hpp +610 -0
  32. package/examples/bridge/enhanced_mqtt_bridge_example.ino +226 -0
  33. package/examples/meshCommandNode/alteriom_sensor_package.hpp +1 -1
  34. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +1 -1
  35. package/examples/mqttTopologyTest/mqttTopologyTest.ino +5 -1
  36. package/library.json +1 -1
  37. package/library.properties +1 -1
  38. package/package.json +2 -2
@@ -0,0 +1,796 @@
1
+ #ifndef ALTERIOM_SENSOR_PACKAGE_HPP
2
+ #define ALTERIOM_SENSOR_PACKAGE_HPP
3
+
4
+ #include "painlessmesh/plugin.hpp"
5
+
6
+ namespace alteriom {
7
+
8
+ /**
9
+ * @brief Sensor data package for broadcasting environmental measurements
10
+ *
11
+ * This package is designed for Alteriom's IoT sensor network requirements,
12
+ * allowing nodes to share environmental data across the mesh.
13
+ */
14
+ class SensorPackage : public painlessmesh::plugin::BroadcastPackage {
15
+ public:
16
+ // Temperature in Celsius
17
+ double temperature = 0.0;
18
+ // Relative humidity percentage
19
+ double humidity = 0.0;
20
+ // Atmospheric pressure in hPa
21
+ double pressure = 0.0;
22
+ // Unique sensor identifier
23
+ uint32_t sensorId = 0;
24
+ // Unix timestamp of measurement
25
+ uint32_t timestamp = 0;
26
+ // Battery level percentage
27
+ uint8_t batteryLevel = 0;
28
+
29
+ // Type ID 200 for Alteriom sensors
30
+ SensorPackage() : BroadcastPackage(200) {}
31
+
32
+ SensorPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
33
+ temperature = jsonObj["temp"];
34
+ humidity = jsonObj["hum"];
35
+ pressure = jsonObj["press"];
36
+ sensorId = jsonObj["sid"];
37
+ timestamp = jsonObj["ts"];
38
+ batteryLevel = jsonObj["bat"];
39
+ }
40
+
41
+ JsonObject addTo(JsonObject&& jsonObj) const {
42
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
43
+ jsonObj["temp"] = temperature;
44
+ jsonObj["hum"] = humidity;
45
+ jsonObj["press"] = pressure;
46
+ jsonObj["sid"] = sensorId;
47
+ jsonObj["ts"] = timestamp;
48
+ jsonObj["bat"] = batteryLevel;
49
+ return jsonObj;
50
+ }
51
+
52
+ #if ARDUINOJSON_VERSION_MAJOR < 7
53
+ size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 6); }
54
+ #endif
55
+ };
56
+
57
+ /**
58
+ * @brief Command package for controlling Alteriom devices
59
+ *
60
+ * Single-destination package for sending specific commands to individual nodes.
61
+ */
62
+ class CommandPackage : public painlessmesh::plugin::SinglePackage {
63
+ public:
64
+ uint8_t command = 0; // Command type
65
+ uint32_t targetDevice = 0; // Target device ID
66
+ TSTRING parameters = ""; // Command parameters as JSON string
67
+ uint32_t commandId = 0; // Unique command identifier for tracking
68
+
69
+ CommandPackage()
70
+ : SinglePackage(400) {} // Type ID 400 (COMMAND per mqtt-schema v0.7.2+)
71
+
72
+ CommandPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
73
+ command = jsonObj["cmd"];
74
+ targetDevice = jsonObj["target"];
75
+ parameters = jsonObj["params"].as<TSTRING>();
76
+ commandId = jsonObj["cid"];
77
+ }
78
+
79
+ JsonObject addTo(JsonObject&& jsonObj) const {
80
+ jsonObj = SinglePackage::addTo(std::move(jsonObj));
81
+ jsonObj["cmd"] = command;
82
+ jsonObj["target"] = targetDevice;
83
+ jsonObj["params"] = parameters;
84
+ jsonObj["cid"] = commandId;
85
+ return jsonObj;
86
+ }
87
+
88
+ #if ARDUINOJSON_VERSION_MAJOR < 7
89
+ size_t jsonObjectSize() const {
90
+ return JSON_OBJECT_SIZE(noJsonFields + 4) + parameters.length();
91
+ }
92
+ #endif
93
+ };
94
+
95
+ /**
96
+ * @brief Status report package for device health monitoring
97
+ */
98
+ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
99
+ public:
100
+ uint8_t deviceStatus = 0; // Device status flags
101
+ uint32_t uptime = 0; // Device uptime in seconds
102
+ uint16_t freeMemory = 0; // Free memory in KB
103
+ uint8_t wifiStrength = 0; // WiFi signal strength
104
+ TSTRING firmwareVersion = ""; // Current firmware version
105
+
106
+ // Command response fields (for MQTT bridge)
107
+ uint32_t responseToCommand = 0; // CommandId this is responding to
108
+ TSTRING responseMessage = ""; // Success/error message
109
+
110
+ StatusPackage() : BroadcastPackage(202) {} // Type ID 202 for Alteriom status
111
+
112
+ StatusPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
113
+ deviceStatus = jsonObj["status"];
114
+ uptime = jsonObj["uptime"];
115
+ freeMemory = jsonObj["mem"];
116
+ wifiStrength = jsonObj["wifi"];
117
+ firmwareVersion = jsonObj["fw"].as<TSTRING>();
118
+ responseToCommand = jsonObj["respTo"] | 0;
119
+ responseMessage = jsonObj["respMsg"].as<TSTRING>();
120
+ }
121
+
122
+ JsonObject addTo(JsonObject&& jsonObj) const {
123
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
124
+ jsonObj["status"] = deviceStatus;
125
+ jsonObj["uptime"] = uptime;
126
+ jsonObj["mem"] = freeMemory;
127
+ jsonObj["wifi"] = wifiStrength;
128
+ jsonObj["fw"] = firmwareVersion;
129
+ if (responseToCommand > 0) {
130
+ jsonObj["respTo"] = responseToCommand;
131
+ jsonObj["respMsg"] = responseMessage;
132
+ }
133
+ return jsonObj;
134
+ }
135
+
136
+ #if ARDUINOJSON_VERSION_MAJOR < 7
137
+ size_t jsonObjectSize() const {
138
+ return JSON_OBJECT_SIZE(noJsonFields + 7) + firmwareVersion.length() +
139
+ responseMessage.length();
140
+ }
141
+ #endif
142
+ };
143
+
144
+ /**
145
+ * @brief Enhanced status package with comprehensive health metrics (Phase 1)
146
+ *
147
+ * This is an extended version of StatusPackage that includes additional
148
+ * mesh statistics, performance metrics, and alerting capabilities.
149
+ * Type ID 203 is used to distinguish from the basic StatusPackage (202).
150
+ */
151
+ class EnhancedStatusPackage : public painlessmesh::plugin::BroadcastPackage {
152
+ public:
153
+ // Device Health (from original StatusPackage)
154
+ uint8_t deviceStatus = 0; // Device status flags
155
+ uint32_t uptime = 0; // Device uptime in seconds
156
+ uint16_t freeMemory = 0; // Free memory in KB
157
+ uint8_t wifiStrength = 0; // WiFi signal strength
158
+ TSTRING firmwareVersion = ""; // Current firmware version
159
+ TSTRING firmwareMD5 = ""; // Firmware hash for OTA verification
160
+
161
+ // Mesh Statistics
162
+ uint16_t nodeCount = 0; // Number of known nodes in mesh
163
+ uint8_t connectionCount = 0; // Number of direct connections
164
+ uint32_t messagesReceived = 0; // Total messages received
165
+ uint32_t messagesSent = 0; // Total messages sent
166
+ uint32_t messagesDropped = 0; // Total messages dropped/failed
167
+
168
+ // Performance Metrics
169
+ uint16_t avgLatency = 0; // Average message latency in ms
170
+ uint8_t packetLossRate = 0; // Packet loss rate percentage (0-100)
171
+ uint16_t throughput = 0; // Network throughput in bytes/sec
172
+
173
+ // Warnings/Alerts
174
+ uint8_t alertFlags = 0; // Bit flags for various alert conditions
175
+ TSTRING lastError = ""; // Last error message for diagnostics
176
+
177
+ EnhancedStatusPackage()
178
+ : BroadcastPackage(604) {
179
+ } // Type ID 604 for enhanced status (MESH_STATUS per mqtt-schema v0.7.2+)
180
+
181
+ EnhancedStatusPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
182
+ deviceStatus = jsonObj["status"];
183
+ uptime = jsonObj["uptime"];
184
+ freeMemory = jsonObj["mem"];
185
+ wifiStrength = jsonObj["wifi"];
186
+ firmwareVersion = jsonObj["fw"].as<TSTRING>();
187
+ firmwareMD5 = jsonObj["fwMD5"].as<TSTRING>();
188
+
189
+ nodeCount = jsonObj["nodes"];
190
+ connectionCount = jsonObj["conns"];
191
+ messagesReceived = jsonObj["msgRx"];
192
+ messagesSent = jsonObj["msgTx"];
193
+ messagesDropped = jsonObj["msgDrop"];
194
+
195
+ avgLatency = jsonObj["latency"];
196
+ packetLossRate = jsonObj["loss"];
197
+ throughput = jsonObj["throughput"];
198
+
199
+ alertFlags = jsonObj["alerts"];
200
+ lastError = jsonObj["lastErr"].as<TSTRING>();
201
+ }
202
+
203
+ JsonObject addTo(JsonObject&& jsonObj) const {
204
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
205
+
206
+ // Device Health
207
+ jsonObj["status"] = deviceStatus;
208
+ jsonObj["uptime"] = uptime;
209
+ jsonObj["mem"] = freeMemory;
210
+ jsonObj["wifi"] = wifiStrength;
211
+ jsonObj["fw"] = firmwareVersion;
212
+ jsonObj["fwMD5"] = firmwareMD5;
213
+
214
+ // Mesh Statistics
215
+ jsonObj["nodes"] = nodeCount;
216
+ jsonObj["conns"] = connectionCount;
217
+ jsonObj["msgRx"] = messagesReceived;
218
+ jsonObj["msgTx"] = messagesSent;
219
+ jsonObj["msgDrop"] = messagesDropped;
220
+
221
+ // Performance Metrics
222
+ jsonObj["latency"] = avgLatency;
223
+ jsonObj["loss"] = packetLossRate;
224
+ jsonObj["throughput"] = throughput;
225
+
226
+ // Alerts
227
+ jsonObj["alerts"] = alertFlags;
228
+ jsonObj["lastErr"] = lastError;
229
+
230
+ return jsonObj;
231
+ }
232
+
233
+ #if ARDUINOJSON_VERSION_MAJOR < 7
234
+ size_t jsonObjectSize() const {
235
+ return JSON_OBJECT_SIZE(noJsonFields + 18) + firmwareVersion.length() +
236
+ firmwareMD5.length() + lastError.length();
237
+ }
238
+ #endif
239
+ };
240
+
241
+ /**
242
+ * @brief Detailed performance metrics package for monitoring (Phase 2)
243
+ *
244
+ * Provides comprehensive performance data including CPU usage, memory trends,
245
+ * network throughput, and other metrics useful for dashboards and monitoring.
246
+ * Type ID 204 for Alteriom metrics.
247
+ */
248
+ class MetricsPackage : public painlessmesh::plugin::BroadcastPackage {
249
+ public:
250
+ // CPU and Processing
251
+ uint8_t cpuUsage = 0; // CPU usage percentage (0-100)
252
+ uint32_t loopIterations = 0; // Loop iterations per second
253
+ uint16_t taskQueueSize = 0; // Number of pending tasks
254
+
255
+ // Memory Metrics
256
+ uint32_t freeHeap = 0; // Free heap memory in bytes
257
+ uint32_t minFreeHeap = 0; // Minimum free heap since boot
258
+ uint32_t heapFragmentation = 0; // Heap fragmentation percentage
259
+ uint32_t maxAllocHeap = 0; // Largest allocatable block
260
+
261
+ // Network Performance
262
+ uint32_t bytesReceived = 0; // Total bytes received
263
+ uint32_t bytesSent = 0; // Total bytes sent
264
+ uint16_t packetsReceived = 0; // Total packets received
265
+ uint16_t packetsSent = 0; // Total packets sent
266
+ uint16_t packetsDropped = 0; // Packets dropped
267
+ uint16_t currentThroughput = 0; // Current throughput in bytes/sec
268
+
269
+ // Timing and Latency
270
+ uint32_t avgResponseTime = 0; // Average response time in microseconds
271
+ uint32_t maxResponseTime = 0; // Maximum response time in microseconds
272
+ uint16_t avgMeshLatency = 0; // Average mesh latency in milliseconds
273
+
274
+ // Connection Quality
275
+ uint8_t connectionQuality = 0; // Overall connection quality (0-100)
276
+ int8_t wifiRSSI = 0; // WiFi RSSI in dBm
277
+
278
+ // Collection metadata
279
+ uint32_t collectionTimestamp = 0; // When metrics were collected
280
+ uint32_t collectionInterval = 0; // Interval between collections in ms
281
+
282
+ // MQTT Schema v0.7.2+ message_type for faster classification
283
+ uint16_t messageType = 204; // SENSOR_METRICS (aligns with schema v0.7.2+)
284
+
285
+ MetricsPackage() : BroadcastPackage(204) {}
286
+
287
+ MetricsPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
288
+ cpuUsage = jsonObj["cpu"];
289
+ loopIterations = jsonObj["loops"];
290
+ taskQueueSize = jsonObj["tasks"];
291
+
292
+ freeHeap = jsonObj["heap"];
293
+ minFreeHeap = jsonObj["minHeap"];
294
+ heapFragmentation = jsonObj["fragHeap"];
295
+ maxAllocHeap = jsonObj["maxHeap"];
296
+
297
+ bytesReceived = jsonObj["bytesRx"];
298
+ bytesSent = jsonObj["bytesTx"];
299
+ packetsReceived = jsonObj["pktsRx"];
300
+ packetsSent = jsonObj["pktsTx"];
301
+ packetsDropped = jsonObj["pktsDrop"];
302
+ currentThroughput = jsonObj["throughput"];
303
+
304
+ avgResponseTime = jsonObj["avgResp"];
305
+ maxResponseTime = jsonObj["maxResp"];
306
+ avgMeshLatency = jsonObj["avgLat"];
307
+
308
+ connectionQuality = jsonObj["connQual"];
309
+ wifiRSSI = jsonObj["rssi"];
310
+
311
+ collectionTimestamp = jsonObj["ts"];
312
+ collectionInterval = jsonObj["interval"];
313
+ messageType = jsonObj["message_type"] | 204;
314
+ }
315
+
316
+ JsonObject addTo(JsonObject&& jsonObj) const {
317
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
318
+
319
+ // CPU and Processing
320
+ jsonObj["cpu"] = cpuUsage;
321
+ jsonObj["loops"] = loopIterations;
322
+ jsonObj["tasks"] = taskQueueSize;
323
+
324
+ // Memory Metrics
325
+ jsonObj["heap"] = freeHeap;
326
+ jsonObj["minHeap"] = minFreeHeap;
327
+ jsonObj["fragHeap"] = heapFragmentation;
328
+ jsonObj["maxHeap"] = maxAllocHeap;
329
+
330
+ // Network Performance
331
+ jsonObj["bytesRx"] = bytesReceived;
332
+ jsonObj["bytesTx"] = bytesSent;
333
+ jsonObj["pktsRx"] = packetsReceived;
334
+ jsonObj["pktsTx"] = packetsSent;
335
+ jsonObj["pktsDrop"] = packetsDropped;
336
+ jsonObj["throughput"] = currentThroughput;
337
+
338
+ // Timing and Latency
339
+ jsonObj["avgResp"] = avgResponseTime;
340
+ jsonObj["maxResp"] = maxResponseTime;
341
+ jsonObj["avgLat"] = avgMeshLatency;
342
+
343
+ // Connection Quality
344
+ jsonObj["connQual"] = connectionQuality;
345
+ jsonObj["rssi"] = wifiRSSI;
346
+
347
+ // Metadata
348
+ jsonObj["ts"] = collectionTimestamp;
349
+ jsonObj["interval"] = collectionInterval;
350
+ jsonObj["message_type"] = messageType; // MQTT Schema v0.7.1+
351
+
352
+ return jsonObj;
353
+ }
354
+
355
+ #if ARDUINOJSON_VERSION_MAJOR < 7
356
+ size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 22); }
357
+ #endif
358
+ };
359
+
360
+ /**
361
+ * @brief Health check package for proactive problem detection (Phase 2)
362
+ *
363
+ * Provides early warning indicators and health status to detect issues
364
+ * before they cause failures. Used for predictive maintenance and alerting.
365
+ * Type ID 605 for Alteriom health checks (MESH_METRICS per mqtt-schema
366
+ * v0.7.2+).
367
+ */
368
+ class HealthCheckPackage : public painlessmesh::plugin::BroadcastPackage {
369
+ public:
370
+ // Overall Health Status (0=critical, 1=warning, 2=healthy)
371
+ uint8_t healthStatus = 2;
372
+
373
+ // Problem Indicators (bit flags)
374
+ uint16_t problemFlags = 0; // Bit flags for specific problems
375
+ /*
376
+ * Problem flag bits:
377
+ * 0x0001 - Low memory warning
378
+ * 0x0002 - High CPU usage
379
+ * 0x0004 - Connection instability
380
+ * 0x0008 - High packet loss
381
+ * 0x0010 - Network congestion
382
+ * 0x0020 - Low battery (if applicable)
383
+ * 0x0040 - Thermal warning
384
+ * 0x0080 - Mesh partition detected
385
+ * 0x0100 - OTA in progress
386
+ * 0x0200 - Configuration error
387
+ */
388
+
389
+ // Memory Health
390
+ uint8_t memoryHealth = 100; // Memory health score (0-100)
391
+ uint32_t memoryTrend = 0; // Bytes/hour memory loss (for leak detection)
392
+
393
+ // Network Health
394
+ uint8_t networkHealth = 100; // Network health score (0-100)
395
+ uint8_t packetLossPercent = 0; // Packet loss percentage
396
+ uint8_t reconnectionCount = 0; // Reconnections in last hour
397
+
398
+ // Performance Health
399
+ uint8_t performanceHealth = 100; // Performance health score (0-100)
400
+ uint32_t missedDeadlines = 0; // Missed task deadlines
401
+ uint16_t maxLoopTime = 0; // Maximum loop execution time in ms
402
+
403
+ // Environmental (if sensors available)
404
+ int8_t temperature = 0; // Device temperature in Celsius
405
+ uint8_t temperatureHealth = 100; // Temperature health score
406
+
407
+ // Uptime and Stability
408
+ uint32_t uptime = 0; // Uptime in seconds
409
+ uint16_t crashCount = 0; // Crash/restart count
410
+ uint32_t lastRebootReason = 0; // Last reboot reason code
411
+
412
+ // Predictive indicators
413
+ uint16_t estimatedTimeToFailure =
414
+ 0; // Estimated hours until failure (0=unknown)
415
+ TSTRING recommendations = ""; // Recommended actions
416
+
417
+ // Check metadata
418
+ uint32_t checkTimestamp = 0; // When check was performed
419
+ uint32_t nextCheckDue = 0; // When next check is due
420
+
421
+ // MQTT Schema v0.7.2+ message_type for faster classification
422
+ uint16_t messageType = 605; // MESH_METRICS type code (mesh performance
423
+ // health per mqtt-schema v0.7.2+)
424
+
425
+ HealthCheckPackage() : BroadcastPackage(605) {}
426
+
427
+ HealthCheckPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
428
+ healthStatus = jsonObj["health"];
429
+ problemFlags = jsonObj["problems"];
430
+
431
+ memoryHealth = jsonObj["memHealth"];
432
+ memoryTrend = jsonObj["memTrend"];
433
+
434
+ networkHealth = jsonObj["netHealth"];
435
+ packetLossPercent = jsonObj["loss"];
436
+ reconnectionCount = jsonObj["reconn"];
437
+
438
+ performanceHealth = jsonObj["perfHealth"];
439
+ missedDeadlines = jsonObj["missed"];
440
+ maxLoopTime = jsonObj["maxLoop"];
441
+
442
+ temperature = jsonObj["temp"];
443
+ temperatureHealth = jsonObj["tempHealth"];
444
+
445
+ uptime = jsonObj["uptime"];
446
+ crashCount = jsonObj["crashes"];
447
+ lastRebootReason = jsonObj["reboot"];
448
+
449
+ estimatedTimeToFailure = jsonObj["ettf"];
450
+ recommendations = jsonObj["recommend"].as<TSTRING>();
451
+
452
+ checkTimestamp = jsonObj["ts"];
453
+ nextCheckDue = jsonObj["nextCheck"];
454
+ messageType = jsonObj["message_type"] | 605;
455
+ }
456
+
457
+ JsonObject addTo(JsonObject&& jsonObj) const {
458
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
459
+
460
+ jsonObj["health"] = healthStatus;
461
+ jsonObj["problems"] = problemFlags;
462
+
463
+ jsonObj["memHealth"] = memoryHealth;
464
+ jsonObj["memTrend"] = memoryTrend;
465
+
466
+ jsonObj["netHealth"] = networkHealth;
467
+ jsonObj["loss"] = packetLossPercent;
468
+ jsonObj["reconn"] = reconnectionCount;
469
+
470
+ jsonObj["perfHealth"] = performanceHealth;
471
+ jsonObj["missed"] = missedDeadlines;
472
+ jsonObj["maxLoop"] = maxLoopTime;
473
+
474
+ jsonObj["temp"] = temperature;
475
+ jsonObj["tempHealth"] = temperatureHealth;
476
+
477
+ jsonObj["uptime"] = uptime;
478
+ jsonObj["crashes"] = crashCount;
479
+ jsonObj["reboot"] = lastRebootReason;
480
+
481
+ jsonObj["ettf"] = estimatedTimeToFailure;
482
+ jsonObj["recommend"] = recommendations;
483
+
484
+ jsonObj["ts"] = checkTimestamp;
485
+ jsonObj["nextCheck"] = nextCheckDue;
486
+ jsonObj["message_type"] = messageType; // MQTT Schema v0.7.1+
487
+
488
+ return jsonObj;
489
+ }
490
+
491
+ #if ARDUINOJSON_VERSION_MAJOR < 7
492
+ size_t jsonObjectSize() const {
493
+ return JSON_OBJECT_SIZE(noJsonFields + 20) + recommendations.length();
494
+ }
495
+ #endif
496
+ };
497
+
498
+ /**
499
+ * @brief Mesh node information for a single node
500
+ */
501
+ struct MeshNodeInfo {
502
+ uint32_t nodeId = 0; // Node identifier
503
+ uint8_t status = 0; // 0=offline, 1=online, 2=unreachable
504
+ uint32_t lastSeen = 0; // Unix timestamp of last communication
505
+ int8_t signalStrength = 0; // RSSI in dBm
506
+ };
507
+
508
+ /**
509
+ * @brief Mesh node list package (Type 600 - MESH_NODE_LIST)
510
+ *
511
+ * Provides list of all nodes in the mesh network with their status.
512
+ * Type ID 600 for MESH_NODE_LIST per mqtt-schema v0.7.2+.
513
+ */
514
+ class MeshNodeListPackage : public painlessmesh::plugin::BroadcastPackage {
515
+ public:
516
+ // Array of node information (max 50 nodes)
517
+ MeshNodeInfo nodes[50];
518
+ uint8_t nodeCount = 0; // Actual number of nodes
519
+ TSTRING meshId = ""; // Mesh network identifier
520
+
521
+ // MQTT Schema v0.7.2+ message_type
522
+ uint16_t messageType = 600; // MESH_NODE_LIST
523
+
524
+ MeshNodeListPackage() : BroadcastPackage(600) {}
525
+
526
+ MeshNodeListPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
527
+ JsonArray nodesArray = jsonObj["nodes"];
528
+ nodeCount = nodesArray.size();
529
+ if (nodeCount > 50) nodeCount = 50;
530
+
531
+ for (uint8_t i = 0; i < nodeCount; i++) {
532
+ JsonObject node = nodesArray[i];
533
+ nodes[i].nodeId = node["nodeId"];
534
+ nodes[i].status = node["status"];
535
+ nodes[i].lastSeen = node["lastSeen"];
536
+ nodes[i].signalStrength = node["rssi"];
537
+ }
538
+
539
+ meshId = jsonObj["meshId"].as<TSTRING>();
540
+ messageType = jsonObj["message_type"] | 600;
541
+ }
542
+
543
+ JsonObject addTo(JsonObject&& jsonObj) const {
544
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
545
+
546
+ JsonArray nodesArray = jsonObj["nodes"].to<JsonArray>();
547
+ for (uint8_t i = 0; i < nodeCount; i++) {
548
+ JsonObject node = nodesArray.add<JsonObject>();
549
+ node["nodeId"] = nodes[i].nodeId;
550
+ node["status"] = nodes[i].status;
551
+ node["lastSeen"] = nodes[i].lastSeen;
552
+ node["rssi"] = nodes[i].signalStrength;
553
+ }
554
+
555
+ jsonObj["nodeCount"] = nodeCount;
556
+ jsonObj["meshId"] = meshId;
557
+ jsonObj["message_type"] = messageType;
558
+
559
+ return jsonObj;
560
+ }
561
+
562
+ #if ARDUINOJSON_VERSION_MAJOR < 7
563
+ size_t jsonObjectSize() const {
564
+ return JSON_OBJECT_SIZE(noJsonFields + 3) + JSON_ARRAY_SIZE(nodeCount) +
565
+ nodeCount * JSON_OBJECT_SIZE(4) + meshId.length();
566
+ }
567
+ #endif
568
+ };
569
+
570
+ /**
571
+ * @brief Mesh connection information
572
+ */
573
+ struct MeshConnection {
574
+ uint32_t fromNode = 0; // Source node ID
575
+ uint32_t toNode = 0; // Destination node ID
576
+ float linkQuality = 0.0; // Link quality 0.0-1.0
577
+ uint16_t latencyMs = 0; // Latency in milliseconds
578
+ uint8_t hopCount = 1; // Number of hops
579
+ };
580
+
581
+ /**
582
+ * @brief Mesh topology package (Type 601 - MESH_TOPOLOGY)
583
+ *
584
+ * Provides mesh network topology with all connections.
585
+ * Type ID 601 for MESH_TOPOLOGY per mqtt-schema v0.7.2+.
586
+ */
587
+ class MeshTopologyPackage : public painlessmesh::plugin::BroadcastPackage {
588
+ public:
589
+ // Array of connections (max 100 connections)
590
+ MeshConnection connections[100];
591
+ uint8_t connectionCount = 0; // Actual number of connections
592
+ uint32_t rootNode = 0; // Root/gateway node ID
593
+
594
+ // MQTT Schema v0.7.2+ message_type
595
+ uint16_t messageType = 601; // MESH_TOPOLOGY
596
+
597
+ MeshTopologyPackage() : BroadcastPackage(601) {}
598
+
599
+ MeshTopologyPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
600
+ JsonArray connsArray = jsonObj["connections"];
601
+ connectionCount = connsArray.size();
602
+ if (connectionCount > 100) connectionCount = 100;
603
+
604
+ for (uint8_t i = 0; i < connectionCount; i++) {
605
+ JsonObject conn = connsArray[i];
606
+ connections[i].fromNode = conn["from"];
607
+ connections[i].toNode = conn["to"];
608
+ connections[i].linkQuality = conn["quality"];
609
+ connections[i].latencyMs = conn["latency"];
610
+ connections[i].hopCount = conn["hops"];
611
+ }
612
+
613
+ rootNode = jsonObj["rootNode"];
614
+ messageType = jsonObj["message_type"] | 601;
615
+ }
616
+
617
+ JsonObject addTo(JsonObject&& jsonObj) const {
618
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
619
+
620
+ JsonArray connsArray = jsonObj["connections"].to<JsonArray>();
621
+ for (uint8_t i = 0; i < connectionCount; i++) {
622
+ JsonObject conn = connsArray.add<JsonObject>();
623
+ conn["from"] = connections[i].fromNode;
624
+ conn["to"] = connections[i].toNode;
625
+ conn["quality"] = connections[i].linkQuality;
626
+ conn["latency"] = connections[i].latencyMs;
627
+ conn["hops"] = connections[i].hopCount;
628
+ }
629
+
630
+ jsonObj["totalConnections"] = connectionCount;
631
+ jsonObj["rootNode"] = rootNode;
632
+ jsonObj["message_type"] = messageType;
633
+
634
+ return jsonObj;
635
+ }
636
+
637
+ #if ARDUINOJSON_VERSION_MAJOR < 7
638
+ size_t jsonObjectSize() const {
639
+ return JSON_OBJECT_SIZE(noJsonFields + 3) +
640
+ JSON_ARRAY_SIZE(connectionCount) +
641
+ connectionCount * JSON_OBJECT_SIZE(5);
642
+ }
643
+ #endif
644
+ };
645
+
646
+ /**
647
+ * @brief Mesh alert information
648
+ */
649
+ struct MeshAlert {
650
+ uint8_t alertType =
651
+ 0; // 0=low_memory, 1=node_offline, 2=connection_lost, etc.
652
+ uint8_t severity = 0; // 0=info, 1=warning, 2=critical
653
+ TSTRING message = ""; // Human-readable message
654
+ uint32_t nodeId = 0; // Related node ID
655
+ float metricValue = 0.0; // Related metric value
656
+ float threshold = 0.0; // Threshold that triggered alert
657
+ uint32_t alertId = 0; // Unique alert ID
658
+ };
659
+
660
+ /**
661
+ * @brief Mesh alert package (Type 602 - MESH_ALERT)
662
+ *
663
+ * Provides mesh network alerts for critical events.
664
+ * Type ID 602 for MESH_ALERT per mqtt-schema v0.7.2+.
665
+ */
666
+ class MeshAlertPackage : public painlessmesh::plugin::BroadcastPackage {
667
+ public:
668
+ // Array of alerts (max 20 alerts)
669
+ MeshAlert alerts[20];
670
+ uint8_t alertCount = 0; // Actual number of alerts
671
+
672
+ // MQTT Schema v0.7.2+ message_type
673
+ uint16_t messageType = 602; // MESH_ALERT
674
+
675
+ MeshAlertPackage() : BroadcastPackage(602) {}
676
+
677
+ MeshAlertPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
678
+ JsonArray alertsArray = jsonObj["alerts"];
679
+ alertCount = alertsArray.size();
680
+ if (alertCount > 20) alertCount = 20;
681
+
682
+ for (uint8_t i = 0; i < alertCount; i++) {
683
+ JsonObject alert = alertsArray[i];
684
+ alerts[i].alertType = alert["type"];
685
+ alerts[i].severity = alert["severity"];
686
+ alerts[i].message = alert["msg"].as<TSTRING>();
687
+ alerts[i].nodeId = alert["nodeId"];
688
+ alerts[i].metricValue = alert["value"];
689
+ alerts[i].threshold = alert["threshold"];
690
+ alerts[i].alertId = alert["alertId"];
691
+ }
692
+
693
+ messageType = jsonObj["message_type"] | 602;
694
+ }
695
+
696
+ JsonObject addTo(JsonObject&& jsonObj) const {
697
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
698
+
699
+ JsonArray alertsArray = jsonObj["alerts"].to<JsonArray>();
700
+ for (uint8_t i = 0; i < alertCount; i++) {
701
+ JsonObject alert = alertsArray.add<JsonObject>();
702
+ alert["type"] = alerts[i].alertType;
703
+ alert["severity"] = alerts[i].severity;
704
+ alert["msg"] = alerts[i].message;
705
+ alert["nodeId"] = alerts[i].nodeId;
706
+ alert["value"] = alerts[i].metricValue;
707
+ alert["threshold"] = alerts[i].threshold;
708
+ alert["alertId"] = alerts[i].alertId;
709
+ }
710
+
711
+ jsonObj["alertCount"] = alertCount;
712
+ jsonObj["message_type"] = messageType;
713
+
714
+ return jsonObj;
715
+ }
716
+
717
+ #if ARDUINOJSON_VERSION_MAJOR < 7
718
+ size_t jsonObjectSize() const {
719
+ size_t size = JSON_OBJECT_SIZE(noJsonFields + 2) +
720
+ JSON_ARRAY_SIZE(alertCount) +
721
+ alertCount * JSON_OBJECT_SIZE(7);
722
+ for (uint8_t i = 0; i < alertCount; i++) {
723
+ size += alerts[i].message.length();
724
+ }
725
+ return size;
726
+ }
727
+ #endif
728
+ };
729
+
730
+ /**
731
+ * @brief Mesh bridge package (Type 603 - MESH_BRIDGE)
732
+ *
733
+ * Encapsulates native mesh protocol messages for bridging.
734
+ * Type ID 603 for MESH_BRIDGE per mqtt-schema v0.7.2+.
735
+ */
736
+ class MeshBridgePackage : public painlessmesh::plugin::BroadcastPackage {
737
+ public:
738
+ uint8_t meshProtocol = 0; // 0=painlessMesh, 1=esp-now, 2=ble-mesh, etc.
739
+ uint32_t fromNodeId = 0; // Source node ID
740
+ uint32_t toNodeId = 0; // Destination node ID (0=broadcast)
741
+ uint16_t meshType = 0; // Mesh protocol-specific message type
742
+ TSTRING rawPayload = ""; // Raw payload (hex/base64 encoded)
743
+ int8_t rssi = 0; // Signal strength
744
+ uint8_t hopCount = 0; // Number of hops
745
+ uint32_t meshTimestamp = 0; // Mesh protocol timestamp
746
+ uint32_t gatewayNodeId = 0; // Gateway's node ID
747
+ TSTRING meshNetworkId = ""; // Mesh network identifier
748
+
749
+ // MQTT Schema v0.7.2+ message_type
750
+ uint16_t messageType = 603; // MESH_BRIDGE
751
+
752
+ MeshBridgePackage() : BroadcastPackage(603) {}
753
+
754
+ MeshBridgePackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
755
+ meshProtocol = jsonObj["protocol"];
756
+ fromNodeId = jsonObj["fromNode"];
757
+ toNodeId = jsonObj["toNode"];
758
+ meshType = jsonObj["meshType"];
759
+ rawPayload = jsonObj["payload"].as<TSTRING>();
760
+ rssi = jsonObj["rssi"];
761
+ hopCount = jsonObj["hops"];
762
+ meshTimestamp = jsonObj["meshTs"];
763
+ gatewayNodeId = jsonObj["gateway"];
764
+ meshNetworkId = jsonObj["meshId"].as<TSTRING>();
765
+ messageType = jsonObj["message_type"] | 603;
766
+ }
767
+
768
+ JsonObject addTo(JsonObject&& jsonObj) const {
769
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
770
+
771
+ jsonObj["protocol"] = meshProtocol;
772
+ jsonObj["fromNode"] = fromNodeId;
773
+ jsonObj["toNode"] = toNodeId;
774
+ jsonObj["meshType"] = meshType;
775
+ jsonObj["payload"] = rawPayload;
776
+ jsonObj["rssi"] = rssi;
777
+ jsonObj["hops"] = hopCount;
778
+ jsonObj["meshTs"] = meshTimestamp;
779
+ jsonObj["gateway"] = gatewayNodeId;
780
+ jsonObj["meshId"] = meshNetworkId;
781
+ jsonObj["message_type"] = messageType;
782
+
783
+ return jsonObj;
784
+ }
785
+
786
+ #if ARDUINOJSON_VERSION_MAJOR < 7
787
+ size_t jsonObjectSize() const {
788
+ return JSON_OBJECT_SIZE(noJsonFields + 11) + rawPayload.length() +
789
+ meshNetworkId.length();
790
+ }
791
+ #endif
792
+ };
793
+
794
+ } // namespace alteriom
795
+
796
+ #endif // ALTERIOM_SENSOR_PACKAGE_HPP