@alteriom/painlessmesh 1.6.1 → 1.7.2
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 +380 -143
- package/LICENSE +674 -674
- package/README.md +477 -434
- package/RELEASE_GUIDE.md +504 -418
- package/docs/DOCUMENTATION_MIGRATION_PLAN.md +175 -175
- package/docs/IMPLEMENTATION_PLAN_MESH_TOPOLOGY.md +1062 -0
- package/docs/MESH_TOPOLOGY_GUIDE.md +992 -0
- package/docs/MESH_TOPOLOGY_PROGRESS.md +422 -0
- package/docs/MQTT_BRIDGE_COMMANDS.md +894 -0
- package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +324 -0
- package/docs/MQTT_COMMAND_SCHEMA_PROPOSAL.md +576 -0
- package/docs/MQTT_SCHEMA_COMPLIANCE.md +285 -0
- package/docs/MQTT_SCHEMA_PROPOSALS.md +446 -0
- package/docs/MQTT_SCHEMA_REVIEW.md +690 -0
- package/docs/OTA_COMMANDS_REFERENCE.md +554 -0
- package/docs/PHASE1_GUIDE.md +349 -0
- package/docs/PHASE2_GUIDE.md +543 -0
- package/docs/README.md +77 -70
- package/docs/SCHEMA_VALIDATION_CHECKLIST.md +222 -0
- package/docs/alteriom/overview.md +507 -507
- package/docs/api/core-api.md +606 -606
- package/docs/architecture/mesh-architecture.md +378 -378
- package/docs/architecture/plugin-system.md +516 -516
- package/docs/getting-started/first-mesh.md +409 -409
- package/docs/getting-started/installation.md +274 -274
- package/docs/getting-started/quickstart.md +157 -157
- package/docs/improvements/FEATURE_PROPOSALS.md +337 -0
- package/docs/improvements/PHASE1_IMPLEMENTATION.md +325 -0
- package/docs/improvements/PHASE2_IMPLEMENTATION.md +567 -0
- package/docs/improvements/README.md +86 -68
- package/docs/improvements/ota-and-status-enhancements.md +911 -0
- package/docs/improvements/ota-status-architecture-diagrams.md +658 -0
- package/docs/improvements/ota-status-quick-reference.md +284 -0
- package/docs/platformio-publishing.md +255 -0
- package/docs/platformio-setup-summary.md +121 -0
- package/docs/troubleshooting/common-issues.md +520 -520
- package/docs/troubleshooting/faq.md +472 -472
- package/docs/tutorials/basic-examples.md +717 -717
- package/docs/wiki/API-Reference.md +245 -245
- package/docs/wiki/Complete-Documentation.md +122 -122
- package/examples/alteriom/README.md +139 -81
- package/examples/alteriom/alteriom.ino +186 -185
- package/examples/alteriom/alteriom_sensor_package.hpp +240 -127
- package/examples/alteriom/platformio.ini +24 -24
- package/examples/alteriomImproved/alteriom_sensor_package.hpp +224 -0
- package/examples/{alteriom → alteriomImproved}/improved_sensor_node.ino +245 -245
- package/examples/alteriomImproved/platformio.ini +25 -0
- package/examples/alteriomPhase1/alteriom_sensor_package.hpp +224 -0
- package/examples/alteriomPhase1/phase1_features.ino +242 -0
- package/examples/alteriomPhase1/platformio.ini +25 -0
- package/examples/alteriomPhase2/alteriom_sensor_package.hpp +224 -0
- package/examples/alteriomPhase2/phase2_features.ino +186 -0
- package/examples/alteriomPhase2/platformio.ini +25 -0
- package/examples/{alteriom → alteriomSensorNode}/alteriom_sensor_node.ino +183 -183
- package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +224 -0
- package/examples/alteriomSensorNode/platformio.ini +25 -0
- package/examples/basic/basic.ino +66 -66
- package/examples/basic/platformio.ini +25 -25
- package/examples/bridge/bridge.ino +51 -51
- package/examples/bridge/mesh_event_publisher.hpp +253 -0
- package/examples/bridge/mesh_topology_reporter.hpp +303 -0
- package/examples/bridge/mqtt_command_bridge.hpp +459 -0
- package/examples/bridge/mqtt_status_bridge.hpp +519 -0
- package/examples/bridge/platformio.ini +25 -25
- package/examples/echoNode/echoNode.ino +33 -33
- package/examples/echoNode/platformio.ini +25 -25
- package/examples/logClient/logClient.ino +109 -109
- package/examples/logClient/platformio.ini +25 -25
- package/examples/logServer/logServer.ino +81 -81
- package/examples/logServer/platformio.ini +25 -25
- package/examples/meshCommandNode/alteriom_sensor_package.hpp +235 -0
- package/examples/meshCommandNode/meshCommandNode.ino +263 -0
- package/examples/meshCommandNode/platformio.ini +25 -0
- package/examples/mqttBridge/mqttBridge.ino +118 -118
- package/examples/mqttBridge/platformio.ini +26 -26
- package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +235 -0
- package/examples/mqttCommandBridge/mesh_event_publisher.hpp +253 -0
- package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +303 -0
- package/examples/mqttCommandBridge/mqttCommandBridge.ino +252 -0
- package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +459 -0
- package/examples/mqttCommandBridge/platformio.ini +26 -0
- package/examples/mqttStatusBridge/mqttStatusBridge.ino +216 -0
- package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +522 -0
- package/examples/mqttStatusBridge/platformio.ini +26 -0
- package/examples/mqttTopologyTest/README.md +467 -0
- package/examples/mqttTopologyTest/mqttTopologyTest.ino +748 -0
- package/examples/mqttTopologyTest/platformio.ini +26 -0
- package/examples/namedMesh/namedMesh.ino +97 -97
- package/examples/namedMesh/platformio.ini +25 -25
- package/examples/otaReceiver/otaReceiver.ino +79 -79
- package/examples/otaReceiver/platformio.ini +25 -25
- package/examples/otaSender/otaSender.ino +160 -151
- package/examples/otaSender/platformio.ini +25 -25
- package/examples/startHere/platformio.ini +25 -25
- package/examples/startHere/startHere.ino +159 -159
- package/examples/webServer/platformio.ini +27 -27
- package/examples/webServer/webServer.ino +89 -89
- package/keywords.txt +48 -48
- package/library.json +55 -34
- package/library.properties +10 -10
- package/package.json +86 -78
- package/src/AlteriomPainlessMesh.h +97 -97
- package/src/arduino/wifi.hpp +365 -365
- package/src/boost/asynctcp.hpp +279 -279
- package/src/painlessMesh.h +70 -70
- package/src/painlessMeshSTA.cpp +236 -236
- package/src/painlessMeshSTA.h +58 -58
- package/src/painlessTaskOptions.h +4 -4
- package/src/painlessmesh/base64.hpp +111 -111
- package/src/painlessmesh/buffer.hpp +229 -229
- package/src/painlessmesh/callback.hpp +91 -91
- package/src/painlessmesh/configuration.hpp +77 -77
- package/src/painlessmesh/connection.hpp +192 -192
- package/src/painlessmesh/layout.hpp +188 -188
- package/src/painlessmesh/logger.hpp +158 -158
- package/src/painlessmesh/memory.hpp +119 -119
- package/src/painlessmesh/mesh.hpp +761 -560
- package/src/painlessmesh/metrics.hpp +322 -322
- package/src/painlessmesh/ntp.hpp +263 -263
- package/src/painlessmesh/ota.hpp +582 -553
- package/src/painlessmesh/plugin.hpp +188 -188
- package/src/painlessmesh/protocol.hpp +813 -813
- package/src/painlessmesh/router.hpp +322 -322
- package/src/painlessmesh/tcp.hpp +71 -71
- package/src/painlessmesh/validation.hpp +238 -238
- package/src/plugin/performance.hpp +214 -214
- package/src/plugin/remote.hpp +64 -64
- package/src/scheduler.cpp +10 -10
- package/src/wifi.cpp +2 -2
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
#ifndef _MQTT_STATUS_BRIDGE_HPP_
|
|
2
|
+
#define _MQTT_STATUS_BRIDGE_HPP_
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Phase 2: MQTT Status Bridge
|
|
6
|
+
*
|
|
7
|
+
* Professional monitoring solution for painlessMesh networks.
|
|
8
|
+
* Publishes comprehensive mesh status to MQTT topics for integration
|
|
9
|
+
* with Grafana, InfluxDB, Prometheus, Home Assistant, and other tools.
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Node list with states
|
|
13
|
+
* - Mesh topology JSON
|
|
14
|
+
* - Performance metrics (Alteriom MQTT Schema v1 compliant)
|
|
15
|
+
* - Active alerts
|
|
16
|
+
* - Per-node health monitoring
|
|
17
|
+
*
|
|
18
|
+
* MQTT Topics:
|
|
19
|
+
* - mesh/status/nodes - Node list with basic info
|
|
20
|
+
* - mesh/status/topology - Complete mesh structure
|
|
21
|
+
* - mesh/status/metrics - Gateway metrics (schema v1 compliant)
|
|
22
|
+
* - mesh/status/alerts - Active alert conditions
|
|
23
|
+
* - mesh/status/node/{id} - Per-node detailed status
|
|
24
|
+
*
|
|
25
|
+
* Compliance:
|
|
26
|
+
* - Uses @alteriom/mqtt-schema v1 for gateway_metrics messages
|
|
27
|
+
* - Includes required envelope fields (schema_version, device_id, etc.)
|
|
28
|
+
* - ISO 8601 timestamps
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
#include <Arduino.h>
|
|
32
|
+
#include <painlessMesh.h>
|
|
33
|
+
#include <PubSubClient.h>
|
|
34
|
+
|
|
35
|
+
class MqttStatusBridge {
|
|
36
|
+
private:
|
|
37
|
+
painlessMesh& mesh;
|
|
38
|
+
PubSubClient& mqttClient;
|
|
39
|
+
Scheduler& scheduler;
|
|
40
|
+
|
|
41
|
+
// Configuration
|
|
42
|
+
uint32_t publishInterval = 30000; // Default 30 seconds
|
|
43
|
+
bool enableTopologyPublish = true;
|
|
44
|
+
bool enableMetricsPublish = true;
|
|
45
|
+
bool enableAlertsPublish = true;
|
|
46
|
+
bool enablePerNodePublish = false; // Can be expensive for large meshes
|
|
47
|
+
|
|
48
|
+
// Topic prefix
|
|
49
|
+
String topicPrefix = "mesh/status/";
|
|
50
|
+
|
|
51
|
+
// Device identification (for Alteriom schema compliance)
|
|
52
|
+
String deviceId = "";
|
|
53
|
+
String firmwareVersion = "1.0.0";
|
|
54
|
+
|
|
55
|
+
// Last publish time
|
|
56
|
+
unsigned long lastPublishTime = 0;
|
|
57
|
+
|
|
58
|
+
// Task scheduler
|
|
59
|
+
Task publishTask;
|
|
60
|
+
|
|
61
|
+
public:
|
|
62
|
+
/**
|
|
63
|
+
* Constructor
|
|
64
|
+
* @param mesh Reference to painlessMesh instance
|
|
65
|
+
* @param mqttClient Reference to PubSubClient instance
|
|
66
|
+
* @param scheduler Reference to Scheduler instance
|
|
67
|
+
*/
|
|
68
|
+
MqttStatusBridge(painlessMesh& mesh, PubSubClient& mqttClient, Scheduler& scheduler)
|
|
69
|
+
: mesh(mesh), mqttClient(mqttClient), scheduler(scheduler) {
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set the publish interval in milliseconds
|
|
74
|
+
* @param interval Interval in milliseconds (default: 30000)
|
|
75
|
+
*/
|
|
76
|
+
void setPublishInterval(uint32_t interval) {
|
|
77
|
+
publishInterval = interval;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set the MQTT topic prefix
|
|
82
|
+
* @param prefix Topic prefix (default: "mesh/status/")
|
|
83
|
+
*/
|
|
84
|
+
void setTopicPrefix(const String& prefix) {
|
|
85
|
+
topicPrefix = prefix;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set device ID for Alteriom schema compliance
|
|
90
|
+
* @param id Device identifier (defaults to mesh node ID if not set)
|
|
91
|
+
*/
|
|
92
|
+
void setDeviceId(const String& id) {
|
|
93
|
+
deviceId = id;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set firmware version for Alteriom schema compliance
|
|
98
|
+
* @param version Firmware version string (default: "1.0.0")
|
|
99
|
+
*/
|
|
100
|
+
void setFirmwareVersion(const String& version) {
|
|
101
|
+
firmwareVersion = version;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Enable or disable topology publishing
|
|
106
|
+
*/
|
|
107
|
+
void enableTopology(bool enable) {
|
|
108
|
+
enableTopologyPublish = enable;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Enable or disable metrics publishing
|
|
113
|
+
*/
|
|
114
|
+
void enableMetrics(bool enable) {
|
|
115
|
+
enableMetricsPublish = enable;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Enable or disable alerts publishing
|
|
120
|
+
*/
|
|
121
|
+
void enableAlerts(bool enable) {
|
|
122
|
+
enableAlertsPublish = enable;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Enable or disable per-node publishing
|
|
127
|
+
* Warning: This can be expensive for large meshes (50+ nodes)
|
|
128
|
+
*/
|
|
129
|
+
void enablePerNode(bool enable) {
|
|
130
|
+
enablePerNodePublish = enable;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initialize the bridge and start publishing
|
|
135
|
+
*/
|
|
136
|
+
void begin() {
|
|
137
|
+
// Create periodic task to publish status
|
|
138
|
+
publishTask.set(TASK_MILLISECOND * publishInterval,
|
|
139
|
+
TASK_FOREVER,
|
|
140
|
+
[this]() { this->publishStatus(); });
|
|
141
|
+
scheduler.addTask(publishTask);
|
|
142
|
+
publishTask.enable();
|
|
143
|
+
|
|
144
|
+
Serial.println("MQTT Status Bridge started");
|
|
145
|
+
Serial.printf("Publishing every %d ms to %s*\n", publishInterval, topicPrefix.c_str());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Stop publishing
|
|
150
|
+
*/
|
|
151
|
+
void stop() {
|
|
152
|
+
publishTask.disable();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Manually trigger a status publish
|
|
157
|
+
*/
|
|
158
|
+
void publishNow() {
|
|
159
|
+
publishStatus();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private:
|
|
163
|
+
/**
|
|
164
|
+
* Main publishing function - collects and publishes all enabled status data
|
|
165
|
+
*/
|
|
166
|
+
void publishStatus() {
|
|
167
|
+
if (!mqttClient.connected()) {
|
|
168
|
+
Serial.println("MQTT not connected, skipping status publish");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
unsigned long startTime = millis();
|
|
173
|
+
|
|
174
|
+
// Always publish node list
|
|
175
|
+
publishNodeList();
|
|
176
|
+
|
|
177
|
+
// Optional: Publish topology
|
|
178
|
+
if (enableTopologyPublish) {
|
|
179
|
+
publishTopology();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Optional: Publish metrics
|
|
183
|
+
if (enableMetricsPublish) {
|
|
184
|
+
publishMetrics();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Optional: Publish alerts
|
|
188
|
+
if (enableAlertsPublish) {
|
|
189
|
+
publishAlerts();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Optional: Publish per-node status (expensive!)
|
|
193
|
+
if (enablePerNodePublish) {
|
|
194
|
+
publishPerNodeStatus();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
unsigned long elapsed = millis() - startTime;
|
|
198
|
+
Serial.printf("Status published in %lu ms\n", elapsed);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Publish list of nodes with basic information
|
|
203
|
+
* Topic: mesh/status/nodes
|
|
204
|
+
*
|
|
205
|
+
* Schema-compliant with proposed mesh_node_list.schema.json v1
|
|
206
|
+
* Uses envelope + nodes array format for interoperability
|
|
207
|
+
*/
|
|
208
|
+
void publishNodeList() {
|
|
209
|
+
auto nodes = mesh.getNodeList(true); // Include self
|
|
210
|
+
|
|
211
|
+
// Get device ID
|
|
212
|
+
String devId = deviceId.length() > 0 ? deviceId : String(mesh.getNodeId());
|
|
213
|
+
|
|
214
|
+
// Build ISO 8601 timestamp
|
|
215
|
+
String timestamp = buildISO8601Timestamp();
|
|
216
|
+
|
|
217
|
+
// Build schema-compliant message with envelope
|
|
218
|
+
String payload = "{";
|
|
219
|
+
// Envelope fields (required by proposed schema)
|
|
220
|
+
payload += "\"schema_version\":1";
|
|
221
|
+
payload += ",\"device_id\":\"" + devId + "\"";
|
|
222
|
+
payload += ",\"device_type\":\"gateway\"";
|
|
223
|
+
payload += ",\"timestamp\":\"" + timestamp + "\"";
|
|
224
|
+
payload += ",\"firmware_version\":\"" + firmwareVersion + "\"";
|
|
225
|
+
|
|
226
|
+
// Nodes array (required by proposed schema)
|
|
227
|
+
payload += ",\"nodes\":[";
|
|
228
|
+
bool firstNode = true;
|
|
229
|
+
for (auto nodeId : nodes) {
|
|
230
|
+
if (!firstNode) payload += ",";
|
|
231
|
+
firstNode = false;
|
|
232
|
+
payload += "{";
|
|
233
|
+
payload += "\"node_id\":\"" + String(nodeId) + "\"";
|
|
234
|
+
payload += ",\"status\":\"online\""; // All listed nodes are online
|
|
235
|
+
payload += ",\"last_seen\":\"" + timestamp + "\"";
|
|
236
|
+
payload += "}";
|
|
237
|
+
}
|
|
238
|
+
payload += "]";
|
|
239
|
+
|
|
240
|
+
// Additional fields (optional in proposed schema)
|
|
241
|
+
payload += ",\"node_count\":" + String(nodes.size());
|
|
242
|
+
payload += ",\"mesh_id\":\"" + String(mesh.getNodeId()) + "\"";
|
|
243
|
+
payload += "}";
|
|
244
|
+
|
|
245
|
+
String topic = topicPrefix + "nodes";
|
|
246
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Publish mesh topology as JSON
|
|
251
|
+
* Topic: mesh/status/topology
|
|
252
|
+
*
|
|
253
|
+
* Schema-compliant with proposed mesh_topology.schema.json v1
|
|
254
|
+
* Uses envelope + connections array format
|
|
255
|
+
*
|
|
256
|
+
* Note: painlessMesh native topology is complex. This implementation
|
|
257
|
+
* creates a simplified schema-compliant version. For full topology details,
|
|
258
|
+
* use the native format via a separate topic if needed.
|
|
259
|
+
*/
|
|
260
|
+
void publishTopology() {
|
|
261
|
+
auto nodes = mesh.getNodeList(true);
|
|
262
|
+
|
|
263
|
+
// Get device ID
|
|
264
|
+
String devId = deviceId.length() > 0 ? deviceId : String(mesh.getNodeId());
|
|
265
|
+
|
|
266
|
+
// Build ISO 8601 timestamp
|
|
267
|
+
String timestamp = buildISO8601Timestamp();
|
|
268
|
+
|
|
269
|
+
// Build schema-compliant message with envelope
|
|
270
|
+
String payload = "{";
|
|
271
|
+
// Envelope fields (required by proposed schema)
|
|
272
|
+
payload += "\"schema_version\":1";
|
|
273
|
+
payload += ",\"device_id\":\"" + devId + "\"";
|
|
274
|
+
payload += ",\"device_type\":\"gateway\"";
|
|
275
|
+
payload += ",\"timestamp\":\"" + timestamp + "\"";
|
|
276
|
+
payload += ",\"firmware_version\":\"" + firmwareVersion + "\"";
|
|
277
|
+
|
|
278
|
+
// Connections array (required by proposed schema)
|
|
279
|
+
// Simplified: Each node connected to root (gateway)
|
|
280
|
+
payload += ",\"connections\":[";
|
|
281
|
+
bool first = true;
|
|
282
|
+
for (auto nodeId : nodes) {
|
|
283
|
+
// Skip self (root node)
|
|
284
|
+
if (nodeId == mesh.getNodeId()) continue;
|
|
285
|
+
|
|
286
|
+
if (!first) payload += ",";
|
|
287
|
+
first = false;
|
|
288
|
+
|
|
289
|
+
payload += "{";
|
|
290
|
+
payload += "\"from_node\":\"" + String(nodeId) + "\"";
|
|
291
|
+
payload += ",\"to_node\":\"" + devId + "\"";
|
|
292
|
+
payload += ",\"link_quality\":0.9"; // Default - can be enhanced
|
|
293
|
+
payload += ",\"hop_count\":1"; // Simplified - assume direct connection
|
|
294
|
+
payload += "}";
|
|
295
|
+
}
|
|
296
|
+
payload += "]";
|
|
297
|
+
|
|
298
|
+
// Additional fields (optional in proposed schema)
|
|
299
|
+
payload += ",\"root_node\":\"" + devId + "\"";
|
|
300
|
+
payload += ",\"total_connections\":" + String(nodes.size() > 0 ? nodes.size() - 1 : 0);
|
|
301
|
+
payload += "}";
|
|
302
|
+
|
|
303
|
+
String topic = topicPrefix + "topology";
|
|
304
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Publish performance metrics
|
|
309
|
+
* Topic: mesh/status/metrics
|
|
310
|
+
*
|
|
311
|
+
* Compliant with @alteriom/mqtt-schema v1 gateway_metrics format
|
|
312
|
+
*/
|
|
313
|
+
void publishMetrics() {
|
|
314
|
+
auto nodes = mesh.getNodeList(true);
|
|
315
|
+
|
|
316
|
+
// Get device ID (use mesh node ID if not explicitly set)
|
|
317
|
+
String devId = deviceId.length() > 0 ? deviceId : String(mesh.getNodeId());
|
|
318
|
+
|
|
319
|
+
// Build ISO 8601 timestamp (simplified - use actual RTC/NTP if available)
|
|
320
|
+
// Format: YYYY-MM-DDTHH:MM:SSZ
|
|
321
|
+
// Note: This uses millis() as fallback. For production, use:
|
|
322
|
+
// - NTP time sync via mesh.getNodeTime() or WiFi NTP client
|
|
323
|
+
// - RTC module for accurate timestamps
|
|
324
|
+
String timestamp;
|
|
325
|
+
#ifdef USE_NTP_TIME
|
|
326
|
+
// If NTP is available, use it
|
|
327
|
+
timestamp = getFormattedTimeISO8601();
|
|
328
|
+
#else
|
|
329
|
+
// Fallback: Use epoch + millis for relative time
|
|
330
|
+
// This is a simplified timestamp - recommend NTP sync for production
|
|
331
|
+
unsigned long totalSeconds = millis() / 1000;
|
|
332
|
+
unsigned long days = totalSeconds / 86400;
|
|
333
|
+
unsigned long hours = (totalSeconds / 3600) % 24;
|
|
334
|
+
unsigned long minutes = (totalSeconds / 60) % 60;
|
|
335
|
+
unsigned long seconds = totalSeconds % 60;
|
|
336
|
+
char timeStr[32];
|
|
337
|
+
// Use Unix epoch start (1970-01-01) + elapsed time
|
|
338
|
+
sprintf(timeStr, "1970-01-%02luT%02lu:%02lu:%02luZ",
|
|
339
|
+
1 + days, hours, minutes, seconds);
|
|
340
|
+
timestamp = timeStr;
|
|
341
|
+
#endif
|
|
342
|
+
|
|
343
|
+
// Build Alteriom schema v1 compliant gateway_metrics message
|
|
344
|
+
String payload = "{";
|
|
345
|
+
// Envelope fields (required by schema)
|
|
346
|
+
payload += "\"schema_version\":1";
|
|
347
|
+
payload += ",\"device_id\":\"" + devId + "\"";
|
|
348
|
+
payload += ",\"device_type\":\"gateway\"";
|
|
349
|
+
payload += ",\"timestamp\":\"" + timestamp + "\"";
|
|
350
|
+
payload += ",\"firmware_version\":\"" + firmwareVersion + "\"";
|
|
351
|
+
|
|
352
|
+
// Metrics object (required by gateway_metrics schema)
|
|
353
|
+
payload += ",\"metrics\":{";
|
|
354
|
+
payload += "\"uptime_s\":" + String(millis() / 1000);
|
|
355
|
+
payload += ",\"mesh_nodes\":" + String(nodes.size());
|
|
356
|
+
|
|
357
|
+
#if defined(ESP32) || defined(ESP8266)
|
|
358
|
+
uint32_t totalHeap = ESP.getFreeHeap();
|
|
359
|
+
// Rough estimate of memory usage percentage (assuming typical ESP heap size)
|
|
360
|
+
uint32_t estimatedTotalHeap = 320000; // Adjust based on actual hardware
|
|
361
|
+
#ifdef ESP8266
|
|
362
|
+
estimatedTotalHeap = 80000;
|
|
363
|
+
#endif
|
|
364
|
+
float memoryUsagePct = 100.0 - ((float)totalHeap / estimatedTotalHeap * 100.0);
|
|
365
|
+
if (memoryUsagePct < 0) memoryUsagePct = 0;
|
|
366
|
+
if (memoryUsagePct > 100) memoryUsagePct = 100;
|
|
367
|
+
|
|
368
|
+
payload += ",\"memory_usage_pct\":" + String(memoryUsagePct, 1);
|
|
369
|
+
#endif
|
|
370
|
+
|
|
371
|
+
payload += ",\"connected_devices\":" + String(nodes.size());
|
|
372
|
+
payload += "}"; // End metrics object
|
|
373
|
+
payload += "}"; // End message
|
|
374
|
+
|
|
375
|
+
String topic = topicPrefix + "metrics";
|
|
376
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Publish active alerts
|
|
381
|
+
* Topic: mesh/status/alerts
|
|
382
|
+
*
|
|
383
|
+
* Schema-compliant with proposed mesh_alert.schema.json v1
|
|
384
|
+
* Uses envelope + alerts array format
|
|
385
|
+
*
|
|
386
|
+
* Tracks node health and publishes alerts when issues are detected.
|
|
387
|
+
*/
|
|
388
|
+
void publishAlerts() {
|
|
389
|
+
auto nodes = mesh.getNodeList(true);
|
|
390
|
+
|
|
391
|
+
// Get device ID
|
|
392
|
+
String devId = deviceId.length() > 0 ? deviceId : String(mesh.getNodeId());
|
|
393
|
+
|
|
394
|
+
// Build ISO 8601 timestamp
|
|
395
|
+
String timestamp = buildISO8601Timestamp();
|
|
396
|
+
|
|
397
|
+
// Build schema-compliant message with envelope
|
|
398
|
+
String payload = "{";
|
|
399
|
+
// Envelope fields (required by proposed schema)
|
|
400
|
+
payload += "\"schema_version\":1";
|
|
401
|
+
payload += ",\"device_id\":\"" + devId + "\"";
|
|
402
|
+
payload += ",\"device_type\":\"gateway\"";
|
|
403
|
+
payload += ",\"timestamp\":\"" + timestamp + "\"";
|
|
404
|
+
payload += ",\"firmware_version\":\"" + firmwareVersion + "\"";
|
|
405
|
+
|
|
406
|
+
// Alerts array (required by proposed schema)
|
|
407
|
+
payload += ",\"alerts\":[";
|
|
408
|
+
bool hasAlerts = false;
|
|
409
|
+
|
|
410
|
+
// Alert: No nodes in mesh
|
|
411
|
+
if (nodes.size() <= 1) { // Only gateway
|
|
412
|
+
if (hasAlerts) payload += ",";
|
|
413
|
+
payload += "{";
|
|
414
|
+
payload += "\"alert_type\":\"node_offline\"";
|
|
415
|
+
payload += ",\"severity\":\"warning\"";
|
|
416
|
+
payload += ",\"message\":\"No sensor nodes in mesh network\"";
|
|
417
|
+
payload += ",\"alert_id\":\"alert-no-nodes\"";
|
|
418
|
+
payload += "}";
|
|
419
|
+
hasAlerts = true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#if defined(ESP32) || defined(ESP8266)
|
|
423
|
+
// Alert: Low memory on gateway
|
|
424
|
+
size_t freeHeap = ESP.getFreeHeap();
|
|
425
|
+
if (freeHeap < 10000) {
|
|
426
|
+
if (hasAlerts) payload += ",";
|
|
427
|
+
payload += "{";
|
|
428
|
+
payload += "\"alert_type\":\"low_memory\"";
|
|
429
|
+
payload += ",\"severity\":\"critical\"";
|
|
430
|
+
payload += ",\"message\":\"Gateway free heap below 10KB\"";
|
|
431
|
+
payload += ",\"node_id\":\"" + devId + "\"";
|
|
432
|
+
payload += ",\"metric_value\":" + String(freeHeap / 1024.0);
|
|
433
|
+
payload += ",\"threshold\":10.0";
|
|
434
|
+
payload += ",\"alert_id\":\"alert-low-mem-gw\"";
|
|
435
|
+
payload += "}";
|
|
436
|
+
hasAlerts = true;
|
|
437
|
+
}
|
|
438
|
+
#endif
|
|
439
|
+
|
|
440
|
+
payload += "]";
|
|
441
|
+
|
|
442
|
+
// Additional fields (optional in proposed schema)
|
|
443
|
+
payload += ",\"alert_count\":" + String(hasAlerts ? 1 : 0);
|
|
444
|
+
payload += "}";
|
|
445
|
+
|
|
446
|
+
String topic = topicPrefix + "alerts";
|
|
447
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Publish detailed status for each node
|
|
452
|
+
* Topic: mesh/status/node/{nodeId}
|
|
453
|
+
*
|
|
454
|
+
* Schema-compliant with sensor_status.schema.json v1 (existing in @alteriom/mqtt-schema)
|
|
455
|
+
* Uses envelope + status enum format
|
|
456
|
+
*
|
|
457
|
+
* Warning: This can generate a lot of MQTT traffic for large meshes!
|
|
458
|
+
* Enable only if per-node status is required.
|
|
459
|
+
*/
|
|
460
|
+
void publishPerNodeStatus() {
|
|
461
|
+
auto nodes = mesh.getNodeList(true); // Include all nodes
|
|
462
|
+
|
|
463
|
+
// Build ISO 8601 timestamp
|
|
464
|
+
String timestamp = buildISO8601Timestamp();
|
|
465
|
+
|
|
466
|
+
for (auto nodeId : nodes) {
|
|
467
|
+
// Build schema-compliant message with envelope
|
|
468
|
+
String payload = "{";
|
|
469
|
+
// Envelope fields (required by sensor_status schema)
|
|
470
|
+
payload += "\"schema_version\":1";
|
|
471
|
+
payload += ",\"device_id\":\"" + String(nodeId) + "\"";
|
|
472
|
+
|
|
473
|
+
// Determine device type - gateway or sensor
|
|
474
|
+
if (nodeId == mesh.getNodeId()) {
|
|
475
|
+
payload += ",\"device_type\":\"gateway\"";
|
|
476
|
+
} else {
|
|
477
|
+
payload += ",\"device_type\":\"sensor\"";
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
payload += ",\"timestamp\":\"" + timestamp + "\"";
|
|
481
|
+
payload += ",\"firmware_version\":\"" + firmwareVersion + "\"";
|
|
482
|
+
|
|
483
|
+
// Status field (required by sensor_status schema)
|
|
484
|
+
bool isConnected = mesh.isConnected(nodeId);
|
|
485
|
+
payload += ",\"status\":\"" + String(isConnected ? "online" : "offline") + "\"";
|
|
486
|
+
|
|
487
|
+
// Optional fields in sensor_status schema
|
|
488
|
+
// Note: signal_strength would require RSSI data from mesh
|
|
489
|
+
// battery_level would require node to report this data
|
|
490
|
+
|
|
491
|
+
payload += "}";
|
|
492
|
+
|
|
493
|
+
String topic = topicPrefix + "node/" + String(nodeId);
|
|
494
|
+
mqttClient.publish(topic.c_str(), payload.c_str());
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private:
|
|
499
|
+
/**
|
|
500
|
+
* Helper function to build ISO 8601 timestamp
|
|
501
|
+
* Shared across all publishing methods for consistency
|
|
502
|
+
*/
|
|
503
|
+
String buildISO8601Timestamp() {
|
|
504
|
+
#ifdef USE_NTP_TIME
|
|
505
|
+
// If NTP is available, use it
|
|
506
|
+
return getFormattedTimeISO8601();
|
|
507
|
+
#else
|
|
508
|
+
// Fallback: Use epoch + millis for relative time
|
|
509
|
+
unsigned long totalSeconds = millis() / 1000;
|
|
510
|
+
unsigned long days = totalSeconds / 86400;
|
|
511
|
+
unsigned long hours = (totalSeconds / 3600) % 24;
|
|
512
|
+
unsigned long minutes = (totalSeconds / 60) % 60;
|
|
513
|
+
unsigned long seconds = totalSeconds % 60;
|
|
514
|
+
char timeStr[32];
|
|
515
|
+
sprintf(timeStr, "1970-01-%02luT%02lu:%02lu:%02luZ",
|
|
516
|
+
1 + days, hours, minutes, seconds);
|
|
517
|
+
return String(timeStr);
|
|
518
|
+
#endif
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
#endif // _MQTT_STATUS_BRIDGE_HPP_
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[platformio]
|
|
2
|
+
src_dir = .
|
|
3
|
+
|
|
4
|
+
[env]
|
|
5
|
+
lib_deps =
|
|
6
|
+
bblanchon/ArduinoJson
|
|
7
|
+
arkhipenko/TaskScheduler
|
|
8
|
+
knolleary/PubSubClient
|
|
9
|
+
|
|
10
|
+
[env:esp8266]
|
|
11
|
+
platform = espressif8266
|
|
12
|
+
board = nodemcuv2
|
|
13
|
+
framework = arduino
|
|
14
|
+
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh
|
|
15
|
+
lib_deps =
|
|
16
|
+
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
+
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
18
|
+
|
|
19
|
+
[env:esp32]
|
|
20
|
+
platform = espressif32
|
|
21
|
+
board = esp32dev
|
|
22
|
+
framework = arduino
|
|
23
|
+
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh
|
|
24
|
+
lib_deps =
|
|
25
|
+
${env.lib_deps} ; Inherit common dependencies
|
|
26
|
+
esp32async/AsyncTCP
|