@alteriom/painlessmesh 1.7.6 → 1.7.7
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 +107 -0
- package/README.md +81 -6
- package/RELEASE_GUIDE.md +36 -0
- package/docs/MQTT_BRIDGE_COMMANDS.md +10 -10
- package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +1 -1
- package/docs/MQTT_SCHEMA_COMPLIANCE.md +57 -2
- package/docs/PHASE1_GUIDE.md +1 -1
- package/docs/alteriom/overview.md +2 -2
- package/docs/architecture/plugin-system.md +1 -1
- package/docs/archive/RELEASE_SUMMARY.md +1 -1
- package/docs/releases/RELEASE_CHECKLIST_v1.7.6.md +389 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +391 -0
- package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +776 -0
- package/docs/wiki/API-Reference.md +2 -2
- package/docs/wiki/Complete-Documentation.md +1 -1
- package/examples/alteriom/README.md +137 -3
- package/examples/alteriom/alteriom.ino +1 -1
- package/examples/alteriom/alteriom_sensor_package.hpp +557 -2
- package/examples/alteriomImproved/alteriom_sensor_package.hpp +1 -1
- package/examples/alteriomImproved/improved_sensor_node.ino +1 -1
- package/examples/alteriomMetricsHealth/alteriom_sensor_package.hpp +796 -0
- package/examples/alteriomMetricsHealth/metrics_health_node.ino +418 -0
- package/examples/alteriomMetricsHealth/platformio.ini +26 -0
- package/examples/alteriomPhase1/alteriom_sensor_package.hpp +1 -1
- package/examples/alteriomPhase1/phase1_features.ino +2 -2
- package/examples/alteriomPhase2/alteriom_sensor_package.hpp +1 -1
- package/examples/alteriomSensorNode/alteriom_sensor_node.ino +1 -1
- package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +1 -1
- package/examples/bridge/enhanced_mqtt_bridge.hpp +610 -0
- package/examples/bridge/enhanced_mqtt_bridge_example.ino +226 -0
- package/examples/meshCommandNode/alteriom_sensor_package.hpp +1 -1
- package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +1 -1
- package/examples/mqttTopologyTest/mqttTopologyTest.ino +5 -1
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
#ifndef _ENHANCED_MQTT_BRIDGE_HPP_
|
|
2
|
+
#define _ENHANCED_MQTT_BRIDGE_HPP_
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhanced MQTT Bridge for painlessMesh v1.7.7+
|
|
6
|
+
*
|
|
7
|
+
* Extends the basic MQTT bridge with:
|
|
8
|
+
* - Command handlers for requesting specific metrics
|
|
9
|
+
* - Aggregated network statistics
|
|
10
|
+
* - Support for MetricsPackage (Type 204) and HealthCheckPackage (Type 605)
|
|
11
|
+
* - Real-time metric requests and responses
|
|
12
|
+
* - Network-wide health aggregation
|
|
13
|
+
*
|
|
14
|
+
* MQTT Topics:
|
|
15
|
+
* Subscribe:
|
|
16
|
+
* - mesh/command/request_metrics - Request metrics from specific node or all nodes
|
|
17
|
+
* - mesh/command/request_health - Request health check from specific node or all nodes
|
|
18
|
+
* - mesh/command/get_aggregated - Get aggregated statistics
|
|
19
|
+
*
|
|
20
|
+
* Publish:
|
|
21
|
+
* - mesh/metrics/{node_id} - Individual node metrics
|
|
22
|
+
* - mesh/health/{node_id} - Individual node health
|
|
23
|
+
* - mesh/aggregated/metrics - Aggregated mesh-wide metrics
|
|
24
|
+
* - mesh/aggregated/health - Aggregated mesh-wide health summary
|
|
25
|
+
* - mesh/response/metrics - Metrics response to command
|
|
26
|
+
* - mesh/response/health - Health response to command
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
#include <Arduino.h>
|
|
30
|
+
#include <painlessMesh.h>
|
|
31
|
+
#include <PubSubClient.h>
|
|
32
|
+
#include "alteriom/alteriom_sensor_package.hpp"
|
|
33
|
+
|
|
34
|
+
using namespace alteriom;
|
|
35
|
+
|
|
36
|
+
// Command types for metric requests
|
|
37
|
+
#define CMD_REQUEST_METRICS 210
|
|
38
|
+
#define CMD_REQUEST_HEALTH 211
|
|
39
|
+
#define CMD_METRICS_RESPONSE 212
|
|
40
|
+
#define CMD_HEALTH_RESPONSE 213
|
|
41
|
+
|
|
42
|
+
class EnhancedMqttBridge {
|
|
43
|
+
private:
|
|
44
|
+
painlessMesh& mesh;
|
|
45
|
+
PubSubClient& mqttClient;
|
|
46
|
+
|
|
47
|
+
// Configuration
|
|
48
|
+
String topicPrefix = "mesh/";
|
|
49
|
+
String deviceId = "";
|
|
50
|
+
String firmwareVersion = "1.7.7";
|
|
51
|
+
|
|
52
|
+
// Aggregation settings
|
|
53
|
+
bool enableAggregation = true;
|
|
54
|
+
uint32_t aggregationInterval = 60000; // 60 seconds
|
|
55
|
+
unsigned long lastAggregationTime = 0;
|
|
56
|
+
|
|
57
|
+
// Metric storage for aggregation
|
|
58
|
+
struct NodeMetrics {
|
|
59
|
+
uint32_t nodeId;
|
|
60
|
+
uint32_t timestamp;
|
|
61
|
+
uint8_t cpuUsage;
|
|
62
|
+
uint32_t freeHeap;
|
|
63
|
+
uint16_t currentThroughput;
|
|
64
|
+
uint8_t connectionQuality;
|
|
65
|
+
int8_t wifiRSSI;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
struct NodeHealth {
|
|
69
|
+
uint32_t nodeId;
|
|
70
|
+
uint32_t timestamp;
|
|
71
|
+
uint8_t healthStatus;
|
|
72
|
+
uint16_t problemFlags;
|
|
73
|
+
uint8_t memoryHealth;
|
|
74
|
+
uint8_t networkHealth;
|
|
75
|
+
uint8_t performanceHealth;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Storage (limited to prevent memory issues)
|
|
79
|
+
#define MAX_STORED_NODES 20
|
|
80
|
+
NodeMetrics metricsCache[MAX_STORED_NODES];
|
|
81
|
+
NodeHealth healthCache[MAX_STORED_NODES];
|
|
82
|
+
uint8_t metricsCacheSize = 0;
|
|
83
|
+
uint8_t healthCacheSize = 0;
|
|
84
|
+
|
|
85
|
+
// Callback for processing received mesh messages
|
|
86
|
+
std::function<void(uint32_t, String&)> originalReceiveCallback = nullptr;
|
|
87
|
+
|
|
88
|
+
public:
|
|
89
|
+
/**
|
|
90
|
+
* Constructor
|
|
91
|
+
*/
|
|
92
|
+
EnhancedMqttBridge(painlessMesh& mesh, PubSubClient& mqttClient)
|
|
93
|
+
: mesh(mesh), mqttClient(mqttClient) {
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize the enhanced bridge
|
|
98
|
+
*/
|
|
99
|
+
void begin() {
|
|
100
|
+
// Set device ID if not already set
|
|
101
|
+
if (deviceId.length() == 0) {
|
|
102
|
+
deviceId = "ALT-" + String(mesh.getNodeId(), HEX);
|
|
103
|
+
deviceId.toUpperCase();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Subscribe to command topics
|
|
107
|
+
subscribeMQTTCommands();
|
|
108
|
+
|
|
109
|
+
// Set up mesh message handler
|
|
110
|
+
setupMeshCallbacks();
|
|
111
|
+
|
|
112
|
+
Serial.println("Enhanced MQTT Bridge v1.7.7 started");
|
|
113
|
+
Serial.printf("Device ID: %s\n", deviceId.c_str());
|
|
114
|
+
Serial.println("Command topics subscribed:");
|
|
115
|
+
Serial.printf(" - %scommand/request_metrics\n", topicPrefix.c_str());
|
|
116
|
+
Serial.printf(" - %scommand/request_health\n", topicPrefix.c_str());
|
|
117
|
+
Serial.printf(" - %scommand/get_aggregated\n", topicPrefix.c_str());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update function - call in loop()
|
|
122
|
+
*/
|
|
123
|
+
void update() {
|
|
124
|
+
// Check if it's time to publish aggregated statistics
|
|
125
|
+
if (enableAggregation && (millis() - lastAggregationTime > aggregationInterval)) {
|
|
126
|
+
publishAggregatedMetrics();
|
|
127
|
+
publishAggregatedHealth();
|
|
128
|
+
lastAggregationTime = millis();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Set topic prefix
|
|
134
|
+
*/
|
|
135
|
+
void setTopicPrefix(const String& prefix) {
|
|
136
|
+
topicPrefix = prefix;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Set device ID
|
|
141
|
+
*/
|
|
142
|
+
void setDeviceId(const String& id) {
|
|
143
|
+
deviceId = id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Enable/disable aggregation
|
|
148
|
+
*/
|
|
149
|
+
void enableAggregation(bool enable) {
|
|
150
|
+
enableAggregation = enable;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set aggregation interval
|
|
155
|
+
*/
|
|
156
|
+
void setAggregationInterval(uint32_t interval) {
|
|
157
|
+
aggregationInterval = interval;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Handle incoming MQTT messages
|
|
162
|
+
* Call this from your MQTT callback
|
|
163
|
+
*/
|
|
164
|
+
void handleMQTTMessage(String& topic, String& payload) {
|
|
165
|
+
Serial.printf("MQTT message received on topic: %s\n", topic.c_str());
|
|
166
|
+
|
|
167
|
+
if (topic == topicPrefix + "command/request_metrics") {
|
|
168
|
+
handleRequestMetrics(payload);
|
|
169
|
+
} else if (topic == topicPrefix + "command/request_health") {
|
|
170
|
+
handleRequestHealth(payload);
|
|
171
|
+
} else if (topic == topicPrefix + "command/get_aggregated") {
|
|
172
|
+
handleGetAggregated(payload);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private:
|
|
177
|
+
/**
|
|
178
|
+
* Subscribe to MQTT command topics
|
|
179
|
+
*/
|
|
180
|
+
void subscribeMQTTCommands() {
|
|
181
|
+
String topic = topicPrefix + "command/request_metrics";
|
|
182
|
+
mqttClient.subscribe(topic.c_str());
|
|
183
|
+
|
|
184
|
+
topic = topicPrefix + "command/request_health";
|
|
185
|
+
mqttClient.subscribe(topic.c_str());
|
|
186
|
+
|
|
187
|
+
topic = topicPrefix + "command/get_aggregated";
|
|
188
|
+
mqttClient.subscribe(topic.c_str());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Set up mesh callbacks to intercept packages
|
|
193
|
+
*/
|
|
194
|
+
void setupMeshCallbacks() {
|
|
195
|
+
mesh.onReceive([this](uint32_t from, String& msg) {
|
|
196
|
+
this->handleMeshMessage(from, msg);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handle mesh messages and extract metrics/health packages
|
|
202
|
+
*/
|
|
203
|
+
void handleMeshMessage(uint32_t from, String& msg) {
|
|
204
|
+
// Parse message to determine type
|
|
205
|
+
DynamicJsonDocument doc(2048);
|
|
206
|
+
DeserializationError error = deserializeJson(doc, msg);
|
|
207
|
+
|
|
208
|
+
if (error) {
|
|
209
|
+
Serial.printf("JSON parse error from node %u: %s\n", from, error.c_str());
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
JsonObject obj = doc.as<JsonObject>();
|
|
214
|
+
uint8_t msgType = obj["type"];
|
|
215
|
+
|
|
216
|
+
// Handle different package types
|
|
217
|
+
switch(msgType) {
|
|
218
|
+
case 204: // MetricsPackage (SENSOR_METRICS per schema v0.7.2+)
|
|
219
|
+
handleMetricsPackage(from, obj);
|
|
220
|
+
break;
|
|
221
|
+
case 605: // HealthCheckPackage (MESH_METRICS per schema v0.7.2+)
|
|
222
|
+
handleHealthPackage(from, obj);
|
|
223
|
+
break;
|
|
224
|
+
case CMD_METRICS_RESPONSE:
|
|
225
|
+
handleMetricsResponse(from, obj);
|
|
226
|
+
break;
|
|
227
|
+
case CMD_HEALTH_RESPONSE:
|
|
228
|
+
handleHealthResponse(from, obj);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Call original callback if set
|
|
233
|
+
if (originalReceiveCallback) {
|
|
234
|
+
originalReceiveCallback(from, msg);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Handle MetricsPackage received from mesh
|
|
240
|
+
*/
|
|
241
|
+
void handleMetricsPackage(uint32_t from, JsonObject& obj) {
|
|
242
|
+
Serial.printf("Received MetricsPackage from node %u\n", from);
|
|
243
|
+
|
|
244
|
+
// Store in cache for aggregation
|
|
245
|
+
storeMetrics(from, obj);
|
|
246
|
+
|
|
247
|
+
// Publish to MQTT
|
|
248
|
+
publishMetricsToMQTT(from, obj);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Handle HealthCheckPackage received from mesh
|
|
253
|
+
*/
|
|
254
|
+
void handleHealthPackage(uint32_t from, JsonObject& obj) {
|
|
255
|
+
Serial.printf("Received HealthCheckPackage from node %u\n", from);
|
|
256
|
+
|
|
257
|
+
// Store in cache for aggregation
|
|
258
|
+
storeHealth(from, obj);
|
|
259
|
+
|
|
260
|
+
// Publish to MQTT
|
|
261
|
+
publishHealthToMQTT(from, obj);
|
|
262
|
+
|
|
263
|
+
// Check for critical health and alert
|
|
264
|
+
uint8_t healthStatus = obj["health"];
|
|
265
|
+
if (healthStatus == 0) {
|
|
266
|
+
publishCriticalAlert(from, obj);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Store metrics in cache for aggregation
|
|
272
|
+
*/
|
|
273
|
+
void storeMetrics(uint32_t nodeId, JsonObject& obj) {
|
|
274
|
+
// Find existing entry or add new
|
|
275
|
+
int index = findMetricsCacheIndex(nodeId);
|
|
276
|
+
if (index == -1) {
|
|
277
|
+
if (metricsCacheSize < MAX_STORED_NODES) {
|
|
278
|
+
index = metricsCacheSize++;
|
|
279
|
+
} else {
|
|
280
|
+
// Cache full, replace oldest
|
|
281
|
+
index = 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
metricsCache[index].nodeId = nodeId;
|
|
286
|
+
metricsCache[index].timestamp = obj["ts"];
|
|
287
|
+
metricsCache[index].cpuUsage = obj["cpu"];
|
|
288
|
+
metricsCache[index].freeHeap = obj["heap"];
|
|
289
|
+
metricsCache[index].currentThroughput = obj["throughput"];
|
|
290
|
+
metricsCache[index].connectionQuality = obj["connQual"];
|
|
291
|
+
metricsCache[index].wifiRSSI = obj["rssi"];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Store health in cache for aggregation
|
|
296
|
+
*/
|
|
297
|
+
void storeHealth(uint32_t nodeId, JsonObject& obj) {
|
|
298
|
+
// Find existing entry or add new
|
|
299
|
+
int index = findHealthCacheIndex(nodeId);
|
|
300
|
+
if (index == -1) {
|
|
301
|
+
if (healthCacheSize < MAX_STORED_NODES) {
|
|
302
|
+
index = healthCacheSize++;
|
|
303
|
+
} else {
|
|
304
|
+
// Cache full, replace oldest
|
|
305
|
+
index = 0;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
healthCache[index].nodeId = nodeId;
|
|
310
|
+
healthCache[index].timestamp = obj["ts"];
|
|
311
|
+
healthCache[index].healthStatus = obj["health"];
|
|
312
|
+
healthCache[index].problemFlags = obj["problems"];
|
|
313
|
+
healthCache[index].memoryHealth = obj["memHealth"];
|
|
314
|
+
healthCache[index].networkHealth = obj["netHealth"];
|
|
315
|
+
healthCache[index].performanceHealth = obj["perfHealth"];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Find metrics cache index for node
|
|
320
|
+
*/
|
|
321
|
+
int findMetricsCacheIndex(uint32_t nodeId) {
|
|
322
|
+
for (int i = 0; i < metricsCacheSize; i++) {
|
|
323
|
+
if (metricsCache[i].nodeId == nodeId) {
|
|
324
|
+
return i;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return -1;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Find health cache index for node
|
|
332
|
+
*/
|
|
333
|
+
int findHealthCacheIndex(uint32_t nodeId) {
|
|
334
|
+
for (int i = 0; i < healthCacheSize; i++) {
|
|
335
|
+
if (healthCache[i].nodeId == nodeId) {
|
|
336
|
+
return i;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return -1;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Publish metrics to MQTT
|
|
344
|
+
*/
|
|
345
|
+
void publishMetricsToMQTT(uint32_t from, JsonObject& obj) {
|
|
346
|
+
String topic = topicPrefix + "metrics/" + String(from);
|
|
347
|
+
|
|
348
|
+
// Build payload
|
|
349
|
+
String payload;
|
|
350
|
+
serializeJson(obj, payload);
|
|
351
|
+
|
|
352
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Publish health to MQTT
|
|
357
|
+
*/
|
|
358
|
+
void publishHealthToMQTT(uint32_t from, JsonObject& obj) {
|
|
359
|
+
String topic = topicPrefix + "health/" + String(from);
|
|
360
|
+
|
|
361
|
+
// Build payload
|
|
362
|
+
String payload;
|
|
363
|
+
serializeJson(obj, payload);
|
|
364
|
+
|
|
365
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Publish critical health alert
|
|
370
|
+
*/
|
|
371
|
+
void publishCriticalAlert(uint32_t from, JsonObject& obj) {
|
|
372
|
+
String topic = topicPrefix + "alerts/critical";
|
|
373
|
+
|
|
374
|
+
String payload = "{";
|
|
375
|
+
payload += "\"alert_type\":\"critical_health\",";
|
|
376
|
+
payload += "\"node_id\":" + String(from) + ",";
|
|
377
|
+
payload += "\"health_status\":" + String((uint8_t)obj["health"]) + ",";
|
|
378
|
+
payload += "\"problem_flags\":" + String((uint16_t)obj["problems"]) + ",";
|
|
379
|
+
payload += "\"recommendations\":\"" + obj["recommend"].as<String>() + "\",";
|
|
380
|
+
payload += "\"timestamp\":\"" + buildISO8601Timestamp() + "\"";
|
|
381
|
+
payload += "}";
|
|
382
|
+
|
|
383
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
384
|
+
Serial.printf("CRITICAL ALERT: Node %u health is critical!\n", from);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Handle request metrics command from MQTT
|
|
389
|
+
*/
|
|
390
|
+
void handleRequestMetrics(String& payload) {
|
|
391
|
+
Serial.println("Handling request_metrics command");
|
|
392
|
+
|
|
393
|
+
// Parse payload to get target node (0 = all nodes)
|
|
394
|
+
DynamicJsonDocument doc(256);
|
|
395
|
+
deserializeJson(doc, payload);
|
|
396
|
+
uint32_t targetNode = doc["node_id"] | 0;
|
|
397
|
+
|
|
398
|
+
// Create command package
|
|
399
|
+
CommandPackage cmd;
|
|
400
|
+
cmd.from = mesh.getNodeId();
|
|
401
|
+
cmd.dest = targetNode;
|
|
402
|
+
cmd.command = CMD_REQUEST_METRICS;
|
|
403
|
+
cmd.commandId = millis();
|
|
404
|
+
|
|
405
|
+
// Send to mesh
|
|
406
|
+
String msg = cmd.toJsonString();
|
|
407
|
+
if (targetNode == 0) {
|
|
408
|
+
mesh.sendBroadcast(msg);
|
|
409
|
+
Serial.println("Metrics requested from all nodes");
|
|
410
|
+
} else {
|
|
411
|
+
mesh.sendSingle(targetNode, msg);
|
|
412
|
+
Serial.printf("Metrics requested from node %u\n", targetNode);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Handle request health command from MQTT
|
|
418
|
+
*/
|
|
419
|
+
void handleRequestHealth(String& payload) {
|
|
420
|
+
Serial.println("Handling request_health command");
|
|
421
|
+
|
|
422
|
+
// Parse payload to get target node (0 = all nodes)
|
|
423
|
+
DynamicJsonDocument doc(256);
|
|
424
|
+
deserializeJson(doc, payload);
|
|
425
|
+
uint32_t targetNode = doc["node_id"] | 0;
|
|
426
|
+
|
|
427
|
+
// Create command package
|
|
428
|
+
CommandPackage cmd;
|
|
429
|
+
cmd.from = mesh.getNodeId();
|
|
430
|
+
cmd.dest = targetNode;
|
|
431
|
+
cmd.command = CMD_REQUEST_HEALTH;
|
|
432
|
+
cmd.commandId = millis();
|
|
433
|
+
|
|
434
|
+
// Send to mesh
|
|
435
|
+
String msg = cmd.toJsonString();
|
|
436
|
+
if (targetNode == 0) {
|
|
437
|
+
mesh.sendBroadcast(msg);
|
|
438
|
+
Serial.println("Health check requested from all nodes");
|
|
439
|
+
} else {
|
|
440
|
+
mesh.sendSingle(targetNode, msg);
|
|
441
|
+
Serial.printf("Health check requested from node %u\n", targetNode);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Handle get aggregated command from MQTT
|
|
447
|
+
*/
|
|
448
|
+
void handleGetAggregated(String& payload) {
|
|
449
|
+
Serial.println("Handling get_aggregated command");
|
|
450
|
+
|
|
451
|
+
// Immediately publish current aggregated stats
|
|
452
|
+
publishAggregatedMetrics();
|
|
453
|
+
publishAggregatedHealth();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Handle metrics response from node
|
|
458
|
+
*/
|
|
459
|
+
void handleMetricsResponse(uint32_t from, JsonObject& obj) {
|
|
460
|
+
Serial.printf("Received metrics response from node %u\n", from);
|
|
461
|
+
|
|
462
|
+
// Publish to response topic
|
|
463
|
+
String topic = topicPrefix + "response/metrics";
|
|
464
|
+
String payload;
|
|
465
|
+
serializeJson(obj, payload);
|
|
466
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Handle health response from node
|
|
471
|
+
*/
|
|
472
|
+
void handleHealthResponse(uint32_t from, JsonObject& obj) {
|
|
473
|
+
Serial.printf("Received health response from node %u\n", from);
|
|
474
|
+
|
|
475
|
+
// Publish to response topic
|
|
476
|
+
String topic = topicPrefix + "response/health";
|
|
477
|
+
String payload;
|
|
478
|
+
serializeJson(obj, payload);
|
|
479
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Publish aggregated metrics
|
|
484
|
+
*/
|
|
485
|
+
void publishAggregatedMetrics() {
|
|
486
|
+
if (metricsCacheSize == 0) {
|
|
487
|
+
Serial.println("No metrics to aggregate");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Calculate aggregated statistics
|
|
492
|
+
uint32_t totalCPU = 0;
|
|
493
|
+
uint32_t totalHeap = 0;
|
|
494
|
+
uint32_t totalThroughput = 0;
|
|
495
|
+
uint32_t totalQuality = 0;
|
|
496
|
+
int32_t totalRSSI = 0;
|
|
497
|
+
uint32_t minHeap = 0xFFFFFFFF;
|
|
498
|
+
uint32_t maxHeap = 0;
|
|
499
|
+
uint8_t minQuality = 100;
|
|
500
|
+
uint8_t maxQuality = 0;
|
|
501
|
+
|
|
502
|
+
for (int i = 0; i < metricsCacheSize; i++) {
|
|
503
|
+
totalCPU += metricsCache[i].cpuUsage;
|
|
504
|
+
totalHeap += metricsCache[i].freeHeap;
|
|
505
|
+
totalThroughput += metricsCache[i].currentThroughput;
|
|
506
|
+
totalQuality += metricsCache[i].connectionQuality;
|
|
507
|
+
totalRSSI += metricsCache[i].wifiRSSI;
|
|
508
|
+
|
|
509
|
+
if (metricsCache[i].freeHeap < minHeap) minHeap = metricsCache[i].freeHeap;
|
|
510
|
+
if (metricsCache[i].freeHeap > maxHeap) maxHeap = metricsCache[i].freeHeap;
|
|
511
|
+
if (metricsCache[i].connectionQuality < minQuality) minQuality = metricsCache[i].connectionQuality;
|
|
512
|
+
if (metricsCache[i].connectionQuality > maxQuality) maxQuality = metricsCache[i].connectionQuality;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Build aggregated payload
|
|
516
|
+
String topic = topicPrefix + "aggregated/metrics";
|
|
517
|
+
String payload = "{";
|
|
518
|
+
payload += "\"node_count\":" + String(metricsCacheSize) + ",";
|
|
519
|
+
payload += "\"avg_cpu\":" + String(totalCPU / metricsCacheSize) + ",";
|
|
520
|
+
payload += "\"avg_heap\":" + String(totalHeap / metricsCacheSize) + ",";
|
|
521
|
+
payload += "\"min_heap\":" + String(minHeap) + ",";
|
|
522
|
+
payload += "\"max_heap\":" + String(maxHeap) + ",";
|
|
523
|
+
payload += "\"total_throughput\":" + String(totalThroughput) + ",";
|
|
524
|
+
payload += "\"avg_quality\":" + String(totalQuality / metricsCacheSize) + ",";
|
|
525
|
+
payload += "\"min_quality\":" + String(minQuality) + ",";
|
|
526
|
+
payload += "\"max_quality\":" + String(maxQuality) + ",";
|
|
527
|
+
payload += "\"avg_rssi\":" + String(totalRSSI / metricsCacheSize) + ",";
|
|
528
|
+
payload += "\"timestamp\":\"" + buildISO8601Timestamp() + "\"";
|
|
529
|
+
payload += "}";
|
|
530
|
+
|
|
531
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
532
|
+
Serial.printf("Published aggregated metrics for %d nodes\n", metricsCacheSize);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Publish aggregated health
|
|
537
|
+
*/
|
|
538
|
+
void publishAggregatedHealth() {
|
|
539
|
+
if (healthCacheSize == 0) {
|
|
540
|
+
Serial.println("No health data to aggregate");
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Count nodes by health status
|
|
545
|
+
uint8_t healthyCount = 0;
|
|
546
|
+
uint8_t warningCount = 0;
|
|
547
|
+
uint8_t criticalCount = 0;
|
|
548
|
+
|
|
549
|
+
// Aggregate problem flags
|
|
550
|
+
uint16_t aggregatedProblems = 0;
|
|
551
|
+
|
|
552
|
+
// Average health scores
|
|
553
|
+
uint32_t totalMemHealth = 0;
|
|
554
|
+
uint32_t totalNetHealth = 0;
|
|
555
|
+
uint32_t totalPerfHealth = 0;
|
|
556
|
+
|
|
557
|
+
for (int i = 0; i < healthCacheSize; i++) {
|
|
558
|
+
// Count by status
|
|
559
|
+
if (healthCache[i].healthStatus == 2) healthyCount++;
|
|
560
|
+
else if (healthCache[i].healthStatus == 1) warningCount++;
|
|
561
|
+
else criticalCount++;
|
|
562
|
+
|
|
563
|
+
// Aggregate problems
|
|
564
|
+
aggregatedProblems |= healthCache[i].problemFlags;
|
|
565
|
+
|
|
566
|
+
// Sum health scores
|
|
567
|
+
totalMemHealth += healthCache[i].memoryHealth;
|
|
568
|
+
totalNetHealth += healthCache[i].networkHealth;
|
|
569
|
+
totalPerfHealth += healthCache[i].performanceHealth;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Determine overall mesh health
|
|
573
|
+
uint8_t meshHealth = 2; // Healthy
|
|
574
|
+
if (criticalCount > 0) meshHealth = 0; // Critical
|
|
575
|
+
else if (warningCount > 0) meshHealth = 1; // Warning
|
|
576
|
+
|
|
577
|
+
// Build aggregated payload
|
|
578
|
+
String topic = topicPrefix + "aggregated/health";
|
|
579
|
+
String payload = "{";
|
|
580
|
+
payload += "\"node_count\":" + String(healthCacheSize) + ",";
|
|
581
|
+
payload += "\"mesh_health\":" + String(meshHealth) + ",";
|
|
582
|
+
payload += "\"healthy_nodes\":" + String(healthyCount) + ",";
|
|
583
|
+
payload += "\"warning_nodes\":" + String(warningCount) + ",";
|
|
584
|
+
payload += "\"critical_nodes\":" + String(criticalCount) + ",";
|
|
585
|
+
payload += "\"aggregated_problems\":" + String(aggregatedProblems) + ",";
|
|
586
|
+
payload += "\"avg_memory_health\":" + String(totalMemHealth / healthCacheSize) + ",";
|
|
587
|
+
payload += "\"avg_network_health\":" + String(totalNetHealth / healthCacheSize) + ",";
|
|
588
|
+
payload += "\"avg_performance_health\":" + String(totalPerfHealth / healthCacheSize) + ",";
|
|
589
|
+
payload += "\"timestamp\":\"" + buildISO8601Timestamp() + "\"";
|
|
590
|
+
payload += "}";
|
|
591
|
+
|
|
592
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
593
|
+
Serial.printf("Published aggregated health: %d healthy, %d warning, %d critical\n",
|
|
594
|
+
healthyCount, warningCount, criticalCount);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Build ISO 8601 timestamp
|
|
599
|
+
*/
|
|
600
|
+
String buildISO8601Timestamp() {
|
|
601
|
+
// Simplified - in production use NTP time
|
|
602
|
+
uint32_t uptime = millis() / 1000;
|
|
603
|
+
char buffer[32];
|
|
604
|
+
snprintf(buffer, sizeof(buffer), "1970-01-01T00:%02d:%02dZ",
|
|
605
|
+
(uptime / 60) % 60, uptime % 60);
|
|
606
|
+
return String(buffer);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
#endif // _ENHANCED_MQTT_BRIDGE_HPP_
|