@alteriom/painlessmesh 1.6.1 → 1.7.3

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 (152) hide show
  1. package/CHANGELOG.md +435 -144
  2. package/LICENSE +674 -674
  3. package/README.md +491 -434
  4. package/RELEASE_GUIDE.md +504 -418
  5. package/docs/DOCUMENTATION_MIGRATION_PLAN.md +175 -175
  6. package/docs/IMPLEMENTATION_PLAN_MESH_TOPOLOGY.md +1062 -0
  7. package/docs/MESH_TOPOLOGY_GUIDE.md +992 -0
  8. package/docs/MESH_TOPOLOGY_PROGRESS.md +422 -0
  9. package/docs/MQTT_BRIDGE_COMMANDS.md +894 -0
  10. package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +324 -0
  11. package/docs/MQTT_COMMAND_SCHEMA_PROPOSAL.md +576 -0
  12. package/docs/MQTT_SCHEMA_COMPLIANCE.md +285 -0
  13. package/docs/MQTT_SCHEMA_PROPOSALS.md +446 -0
  14. package/docs/MQTT_SCHEMA_REVIEW.md +690 -0
  15. package/docs/OTA_COMMANDS_REFERENCE.md +554 -0
  16. package/docs/PHASE1_GUIDE.md +349 -0
  17. package/docs/PHASE2_GUIDE.md +543 -0
  18. package/docs/README.md +130 -71
  19. package/docs/SCHEMA_VALIDATION_CHECKLIST.md +222 -0
  20. package/docs/alteriom/overview.md +507 -507
  21. package/docs/api/core-api.md +606 -606
  22. package/docs/architecture/mesh-architecture.md +378 -378
  23. package/docs/architecture/plugin-system.md +516 -516
  24. package/docs/archive/DOCUSAURUS_DEPLOYMENT.md +166 -0
  25. package/docs/archive/FEATURE_PROPOSALS.md +337 -0
  26. package/docs/archive/LIBRARY_JSON_FIX.md +98 -0
  27. package/docs/archive/LIBRARY_STRUCTURE_FIX.md +215 -0
  28. package/docs/archive/PHASE1_IMPLEMENTATION.md +325 -0
  29. package/docs/archive/PHASE2_IMPLEMENTATION.md +567 -0
  30. package/docs/archive/RELEASE_SUMMARY.md +173 -0
  31. package/docs/archive/SCONS_BUILD_FIX.md +313 -0
  32. package/docs/archive/TRIGGER_RELEASE.md +280 -0
  33. package/docs/archive/VECTOR_INCLUDE_FIX.md +129 -0
  34. package/docs/archive/ota-and-status-enhancements.md +911 -0
  35. package/docs/archive/ota-status-architecture-diagrams.md +658 -0
  36. package/docs/archive/ota-status-quick-reference.md +284 -0
  37. package/docs/development/ARDUINO_COMPLIANCE_SUMMARY.md +71 -0
  38. package/docs/development/CODE_REFACTORING_RECOMMENDATIONS.md +1011 -0
  39. package/docs/development/DOCKER_TESTING.md +196 -0
  40. package/docs/development/PLATFORMIO_USAGE.md +180 -0
  41. package/docs/development/TESTING_SUMMARY.md +126 -0
  42. package/docs/development/contributing.md +301 -0
  43. package/docs/development/documentation.md +583 -0
  44. package/docs/getting-started/first-mesh.md +409 -409
  45. package/docs/getting-started/installation.md +274 -274
  46. package/docs/getting-started/quickstart.md +157 -157
  47. package/docs/improvements/FUTURE_PROPOSALS.md +1016 -0
  48. package/docs/improvements/IMPLEMENTATION_HISTORY.md +1091 -0
  49. package/docs/improvements/OTA_STATUS_ENHANCEMENTS.md +709 -0
  50. package/docs/improvements/README.md +212 -69
  51. package/docs/platformio-publishing.md +255 -0
  52. package/docs/platformio-setup-summary.md +121 -0
  53. package/docs/releases/FEATURE_HISTORY.md +543 -0
  54. package/docs/releases/PATCH_v1.7.3.md +262 -0
  55. package/docs/releases/PHASE1_SUMMARY.md +246 -0
  56. package/docs/releases/PHASE2_SUMMARY.md +499 -0
  57. package/docs/releases/RELEASE_NOTES_1.7.0.md +539 -0
  58. package/docs/troubleshooting/common-issues.md +520 -520
  59. package/docs/troubleshooting/debugging.md +455 -0
  60. package/docs/troubleshooting/faq.md +472 -472
  61. package/docs/tutorials/basic-examples.md +717 -717
  62. package/docs/wiki/API-Reference.md +245 -245
  63. package/docs/wiki/Complete-Documentation.md +122 -122
  64. package/examples/alteriom/README.md +139 -81
  65. package/examples/alteriom/alteriom.ino +186 -185
  66. package/examples/alteriom/alteriom_sensor_package.hpp +240 -127
  67. package/examples/alteriom/platformio.ini +24 -24
  68. package/examples/alteriomImproved/alteriom_sensor_package.hpp +224 -0
  69. package/examples/{alteriom → alteriomImproved}/improved_sensor_node.ino +245 -245
  70. package/examples/alteriomImproved/platformio.ini +25 -0
  71. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +224 -0
  72. package/examples/alteriomPhase1/phase1_features.ino +242 -0
  73. package/examples/alteriomPhase1/platformio.ini +25 -0
  74. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +224 -0
  75. package/examples/alteriomPhase2/phase2_features.ino +186 -0
  76. package/examples/alteriomPhase2/platformio.ini +25 -0
  77. package/examples/{alteriom → alteriomSensorNode}/alteriom_sensor_node.ino +183 -183
  78. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +224 -0
  79. package/examples/alteriomSensorNode/platformio.ini +25 -0
  80. package/examples/basic/basic.ino +66 -66
  81. package/examples/basic/platformio.ini +25 -25
  82. package/examples/bridge/bridge.ino +51 -51
  83. package/examples/bridge/mesh_event_publisher.hpp +253 -0
  84. package/examples/bridge/mesh_topology_reporter.hpp +303 -0
  85. package/examples/bridge/mqtt_command_bridge.hpp +459 -0
  86. package/examples/bridge/mqtt_status_bridge.hpp +519 -0
  87. package/examples/bridge/platformio.ini +25 -25
  88. package/examples/echoNode/echoNode.ino +33 -33
  89. package/examples/echoNode/platformio.ini +25 -25
  90. package/examples/logClient/logClient.ino +109 -109
  91. package/examples/logClient/platformio.ini +25 -25
  92. package/examples/logServer/logServer.ino +81 -81
  93. package/examples/logServer/platformio.ini +25 -25
  94. package/examples/meshCommandNode/alteriom_sensor_package.hpp +235 -0
  95. package/examples/meshCommandNode/meshCommandNode.ino +263 -0
  96. package/examples/meshCommandNode/platformio.ini +25 -0
  97. package/examples/mqttBridge/mqttBridge.ino +118 -118
  98. package/examples/mqttBridge/platformio.ini +26 -26
  99. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +235 -0
  100. package/examples/mqttCommandBridge/mesh_event_publisher.hpp +253 -0
  101. package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +303 -0
  102. package/examples/mqttCommandBridge/mqttCommandBridge.ino +252 -0
  103. package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +459 -0
  104. package/examples/mqttCommandBridge/platformio.ini +26 -0
  105. package/examples/mqttStatusBridge/mqttStatusBridge.ino +216 -0
  106. package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +522 -0
  107. package/examples/mqttStatusBridge/platformio.ini +26 -0
  108. package/examples/mqttTopologyTest/README.md +467 -0
  109. package/examples/mqttTopologyTest/mqttTopologyTest.ino +748 -0
  110. package/examples/mqttTopologyTest/platformio.ini +26 -0
  111. package/examples/namedMesh/namedMesh.ino +97 -97
  112. package/examples/namedMesh/platformio.ini +25 -25
  113. package/examples/otaReceiver/otaReceiver.ino +79 -79
  114. package/examples/otaReceiver/platformio.ini +25 -25
  115. package/examples/otaSender/otaSender.ino +160 -151
  116. package/examples/otaSender/platformio.ini +25 -25
  117. package/examples/startHere/platformio.ini +25 -25
  118. package/examples/startHere/startHere.ino +159 -159
  119. package/examples/webServer/platformio.ini +27 -27
  120. package/examples/webServer/webServer.ino +89 -89
  121. package/keywords.txt +48 -48
  122. package/library.json +55 -34
  123. package/library.properties +10 -10
  124. package/package.json +86 -78
  125. package/src/AlteriomPainlessMesh.h +97 -97
  126. package/src/arduino/wifi.hpp +365 -365
  127. package/src/boost/asynctcp.hpp +279 -279
  128. package/src/painlessMesh.h +70 -70
  129. package/src/painlessMeshSTA.cpp +236 -236
  130. package/src/painlessMeshSTA.h +58 -58
  131. package/src/painlessTaskOptions.h +4 -4
  132. package/src/painlessmesh/base64.hpp +111 -111
  133. package/src/painlessmesh/buffer.hpp +229 -229
  134. package/src/painlessmesh/callback.hpp +91 -91
  135. package/src/painlessmesh/configuration.hpp +77 -77
  136. package/src/painlessmesh/connection.hpp +192 -192
  137. package/src/painlessmesh/layout.hpp +188 -188
  138. package/src/painlessmesh/logger.hpp +158 -158
  139. package/src/painlessmesh/memory.hpp +119 -119
  140. package/src/painlessmesh/mesh.hpp +761 -560
  141. package/src/painlessmesh/metrics.hpp +322 -322
  142. package/src/painlessmesh/ntp.hpp +263 -263
  143. package/src/painlessmesh/ota.hpp +582 -553
  144. package/src/painlessmesh/plugin.hpp +188 -188
  145. package/src/painlessmesh/protocol.hpp +813 -813
  146. package/src/painlessmesh/router.hpp +338 -322
  147. package/src/painlessmesh/tcp.hpp +71 -71
  148. package/src/painlessmesh/validation.hpp +238 -238
  149. package/src/plugin/performance.hpp +214 -214
  150. package/src/plugin/remote.hpp +64 -64
  151. package/src/scheduler.cpp +10 -10
  152. package/src/wifi.cpp +2 -2
@@ -0,0 +1,253 @@
1
+ #ifndef MESH_EVENT_PUBLISHER_HPP
2
+ #define MESH_EVENT_PUBLISHER_HPP
3
+
4
+ #include "painlessMesh.h"
5
+ #include <PubSubClient.h>
6
+ #include <ArduinoJson.h>
7
+
8
+ /**
9
+ * MeshEventPublisher - Publish real-time mesh events
10
+ *
11
+ * Publishes mesh state change events conforming to @alteriom/mqtt-schema v0.5.0
12
+ * mesh_event.schema.json specification.
13
+ *
14
+ * Event Types:
15
+ * - node_join: New node entered mesh
16
+ * - node_leave: Node left mesh (clean disconnect)
17
+ * - connection_lost: Direct connection failed
18
+ * - connection_restored: Connection recovered
19
+ *
20
+ * @author Alteriom Development Team
21
+ * @date October 12, 2025
22
+ */
23
+ class MeshEventPublisher {
24
+ private:
25
+ painlessMesh& mesh;
26
+ PubSubClient& mqttClient;
27
+ String meshId;
28
+ String eventTopic;
29
+ String firmwareVersion;
30
+
31
+ public:
32
+ MeshEventPublisher(painlessMesh& mesh, PubSubClient& mqttClient,
33
+ const String& meshId, const String& topic = "alteriom/mesh/events",
34
+ const String& fwVersion = "GW 2.3.4")
35
+ : mesh(mesh), mqttClient(mqttClient), meshId(meshId),
36
+ eventTopic(topic), firmwareVersion(fwVersion) {}
37
+
38
+ /**
39
+ * Publish node join event
40
+ * Called when a new node connects to the mesh
41
+ */
42
+ void publishNodeJoin(uint32_t nodeId) {
43
+ DynamicJsonDocument doc(1024);
44
+
45
+ // Envelope
46
+ doc["schema_version"] = 1;
47
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
48
+ doc["device_type"] = "gateway";
49
+ doc["timestamp"] = getISO8601Timestamp();
50
+ doc["firmware_version"] = firmwareVersion;
51
+
52
+ // Event
53
+ doc["event"] = "mesh_event";
54
+ doc["event_type"] = "node_join";
55
+ doc["mesh_id"] = meshId;
56
+
57
+ JsonArray affected = doc.createNestedArray("affected_nodes");
58
+ affected.add(generateDeviceId(nodeId));
59
+
60
+ JsonObject details = doc.createNestedObject("details");
61
+ details["total_nodes"] = mesh.getNodeList().size() + 1;
62
+ details["timestamp"] = getISO8601Timestamp();
63
+
64
+ publishEvent(doc, "Node Join");
65
+ }
66
+
67
+ /**
68
+ * Publish node leave event
69
+ * Called when a node disconnects from the mesh
70
+ */
71
+ void publishNodeLeave(uint32_t nodeId) {
72
+ DynamicJsonDocument doc(1024);
73
+
74
+ // Envelope
75
+ doc["schema_version"] = 1;
76
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
77
+ doc["device_type"] = "gateway";
78
+ doc["timestamp"] = getISO8601Timestamp();
79
+ doc["firmware_version"] = firmwareVersion;
80
+
81
+ // Event
82
+ doc["event"] = "mesh_event";
83
+ doc["event_type"] = "node_leave";
84
+ doc["mesh_id"] = meshId;
85
+
86
+ JsonArray affected = doc.createNestedArray("affected_nodes");
87
+ affected.add(generateDeviceId(nodeId));
88
+
89
+ JsonObject details = doc.createNestedObject("details");
90
+ details["reason"] = "connection_lost";
91
+ details["last_seen"] = getISO8601Timestamp();
92
+ details["total_nodes"] = mesh.getNodeList().size() + 1;
93
+
94
+ publishEvent(doc, "Node Leave");
95
+ }
96
+
97
+ /**
98
+ * Publish connection lost event
99
+ * Called when a direct connection fails
100
+ */
101
+ void publishConnectionLost(uint32_t nodeId) {
102
+ DynamicJsonDocument doc(1024);
103
+
104
+ doc["schema_version"] = 1;
105
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
106
+ doc["device_type"] = "gateway";
107
+ doc["timestamp"] = getISO8601Timestamp();
108
+ doc["firmware_version"] = firmwareVersion;
109
+
110
+ doc["event"] = "mesh_event";
111
+ doc["event_type"] = "connection_lost";
112
+ doc["mesh_id"] = meshId;
113
+
114
+ JsonArray affected = doc.createNestedArray("affected_nodes");
115
+ affected.add(generateDeviceId(nodeId));
116
+
117
+ JsonObject details = doc.createNestedObject("details");
118
+ details["reason"] = "timeout";
119
+ details["timestamp"] = getISO8601Timestamp();
120
+
121
+ publishEvent(doc, "Connection Lost");
122
+ }
123
+
124
+ /**
125
+ * Publish connection restored event
126
+ * Called when a connection is re-established
127
+ */
128
+ void publishConnectionRestored(uint32_t nodeId) {
129
+ DynamicJsonDocument doc(1024);
130
+
131
+ doc["schema_version"] = 1;
132
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
133
+ doc["device_type"] = "gateway";
134
+ doc["timestamp"] = getISO8601Timestamp();
135
+ doc["firmware_version"] = firmwareVersion;
136
+
137
+ doc["event"] = "mesh_event";
138
+ doc["event_type"] = "connection_restored";
139
+ doc["mesh_id"] = meshId;
140
+
141
+ JsonArray affected = doc.createNestedArray("affected_nodes");
142
+ affected.add(generateDeviceId(nodeId));
143
+
144
+ JsonObject details = doc.createNestedObject("details");
145
+ details["timestamp"] = getISO8601Timestamp();
146
+
147
+ publishEvent(doc, "Connection Restored");
148
+ }
149
+
150
+ /**
151
+ * Publish network split event
152
+ * Called when mesh partitions into separate segments
153
+ */
154
+ void publishNetworkSplit() {
155
+ DynamicJsonDocument doc(1024);
156
+
157
+ doc["schema_version"] = 1;
158
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
159
+ doc["device_type"] = "gateway";
160
+ doc["timestamp"] = getISO8601Timestamp();
161
+ doc["firmware_version"] = firmwareVersion;
162
+
163
+ doc["event"] = "mesh_event";
164
+ doc["event_type"] = "network_split";
165
+ doc["mesh_id"] = meshId;
166
+
167
+ JsonArray affected = doc.createNestedArray("affected_nodes");
168
+ // TODO: Detect which nodes are in different partitions
169
+
170
+ JsonObject details = doc.createNestedObject("details");
171
+ details["timestamp"] = getISO8601Timestamp();
172
+ details["partition_count"] = 2; // TODO: Calculate actual partitions
173
+
174
+ publishEvent(doc, "Network Split");
175
+ }
176
+
177
+ /**
178
+ * Publish network merged event
179
+ * Called when mesh partitions rejoin
180
+ */
181
+ void publishNetworkMerged() {
182
+ DynamicJsonDocument doc(1024);
183
+
184
+ doc["schema_version"] = 1;
185
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
186
+ doc["device_type"] = "gateway";
187
+ doc["timestamp"] = getISO8601Timestamp();
188
+ doc["firmware_version"] = firmwareVersion;
189
+
190
+ doc["event"] = "mesh_event";
191
+ doc["event_type"] = "network_merged";
192
+ doc["mesh_id"] = meshId;
193
+
194
+ JsonArray affected = doc.createNestedArray("affected_nodes");
195
+ // TODO: List nodes that rejoined
196
+
197
+ JsonObject details = doc.createNestedObject("details");
198
+ details["timestamp"] = getISO8601Timestamp();
199
+
200
+ publishEvent(doc, "Network Merged");
201
+ }
202
+
203
+ private:
204
+ /**
205
+ * Internal method to publish event to MQTT
206
+ */
207
+ void publishEvent(const DynamicJsonDocument& doc, const char* eventName) {
208
+ if (!mqttClient.connected()) {
209
+ Serial.printf("[Event] ⚠️ MQTT not connected, cannot publish %s\n", eventName);
210
+ return;
211
+ }
212
+
213
+ String payload;
214
+ serializeJson(doc, payload);
215
+
216
+ bool success = mqttClient.publish(eventTopic.c_str(), payload.c_str());
217
+
218
+ if (success) {
219
+ Serial.printf("[Event] ✅ Published: %s (node: %s)\n",
220
+ eventName,
221
+ doc["affected_nodes"][0].as<const char*>());
222
+ } else {
223
+ Serial.printf("[Event] ❌ Failed to publish: %s\n", eventName);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Generate device ID in Alteriom format (ALT-XXXXXXXXXXXX)
229
+ */
230
+ String generateDeviceId(uint32_t nodeId) {
231
+ char deviceId[20];
232
+ snprintf(deviceId, sizeof(deviceId), "ALT-%012X", nodeId);
233
+ return String(deviceId);
234
+ }
235
+
236
+ /**
237
+ * Get current timestamp in ISO 8601 format
238
+ * TODO: Use proper NTP time if available
239
+ */
240
+ String getISO8601Timestamp() {
241
+ // For now, use epoch-based timestamp
242
+ // In production, this should use actual NTP-synced time
243
+ unsigned long ms = millis();
244
+ char timestamp[32];
245
+ snprintf(timestamp, sizeof(timestamp), "2025-10-12T%02lu:%02lu:%02luZ",
246
+ (ms / 3600000) % 24,
247
+ (ms / 60000) % 60,
248
+ (ms / 1000) % 60);
249
+ return String(timestamp);
250
+ }
251
+ };
252
+
253
+ #endif // MESH_EVENT_PUBLISHER_HPP
@@ -0,0 +1,303 @@
1
+ #ifndef MESH_TOPOLOGY_REPORTER_HPP
2
+ #define MESH_TOPOLOGY_REPORTER_HPP
3
+
4
+ #include "painlessMesh.h"
5
+ #include <ArduinoJson.h>
6
+
7
+ /**
8
+ * MeshTopologyReporter - Generate schema-compliant topology messages
9
+ *
10
+ * Generates mesh topology messages conforming to @alteriom/mqtt-schema v0.5.0
11
+ * mesh_topology.schema.json specification.
12
+ *
13
+ * Features:
14
+ * - Full topology snapshots with nodes, connections, and metrics
15
+ * - Incremental updates for node join/leave events
16
+ * - Change detection to avoid redundant publishes
17
+ * - Network metrics aggregation
18
+ *
19
+ * @author Alteriom Development Team
20
+ * @date October 12, 2025
21
+ */
22
+ class MeshTopologyReporter {
23
+ private:
24
+ painlessMesh& mesh;
25
+ String meshId;
26
+ String lastTopologyHash; // Detect changes
27
+ String firmwareVersion;
28
+
29
+ public:
30
+ MeshTopologyReporter(painlessMesh& mesh, const String& meshId, const String& fwVersion = "GW 2.3.4")
31
+ : mesh(mesh), meshId(meshId), firmwareVersion(fwVersion) {}
32
+
33
+ /**
34
+ * Generate full topology message (schema v0.5.0 compliant)
35
+ *
36
+ * Returns JSON string ready for MQTT publish to:
37
+ * alteriom/mesh/{mesh_id}/topology
38
+ */
39
+ String generateFullTopology() {
40
+ DynamicJsonDocument doc(8192); // Large buffer for full topology
41
+
42
+ // Envelope fields (from @alteriom/mqtt-schema)
43
+ doc["schema_version"] = 1;
44
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
45
+ doc["device_type"] = "gateway";
46
+ doc["timestamp"] = getISO8601Timestamp();
47
+ doc["firmware_version"] = firmwareVersion;
48
+
49
+ // Event discriminator
50
+ doc["event"] = "mesh_topology";
51
+
52
+ // Mesh identification
53
+ doc["mesh_id"] = meshId;
54
+ doc["gateway_node_id"] = generateDeviceId(mesh.getNodeId());
55
+
56
+ // Nodes array
57
+ JsonArray nodes = doc.createNestedArray("nodes");
58
+ addNodeInfo(nodes, mesh.getNodeId(), "gateway"); // Self
59
+
60
+ auto nodeList = mesh.getNodeList(false);
61
+ for (auto nodeId : nodeList) {
62
+ addNodeInfo(nodes, nodeId, "sensor"); // Assume sensor role
63
+ }
64
+
65
+ // Connections array
66
+ JsonArray connections = doc.createNestedArray("connections");
67
+ addConnectionInfo(connections);
68
+
69
+ // Network metrics
70
+ JsonObject metrics = doc.createNestedObject("metrics");
71
+ metrics["total_nodes"] = nodeList.size() + 1; // +1 for gateway
72
+ metrics["online_nodes"] = nodeList.size() + 1;
73
+ metrics["network_diameter"] = calculateNetworkDiameter();
74
+ metrics["avg_connection_quality"] = calculateAvgQuality();
75
+ metrics["messages_per_second"] = calculateThroughput();
76
+
77
+ doc["update_type"] = "full";
78
+
79
+ String output;
80
+ serializeJson(doc, output);
81
+ return output;
82
+ }
83
+
84
+ /**
85
+ * Generate incremental topology update
86
+ *
87
+ * @param nodeId Node that joined or left
88
+ * @param joined true if node joined, false if left
89
+ */
90
+ String generateIncrementalUpdate(uint32_t nodeId, bool joined) {
91
+ DynamicJsonDocument doc(2048);
92
+
93
+ // Envelope fields
94
+ doc["schema_version"] = 1;
95
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
96
+ doc["device_type"] = "gateway";
97
+ doc["timestamp"] = getISO8601Timestamp();
98
+ doc["firmware_version"] = firmwareVersion;
99
+
100
+ doc["event"] = "mesh_topology";
101
+ doc["mesh_id"] = meshId;
102
+ doc["gateway_node_id"] = generateDeviceId(mesh.getNodeId());
103
+
104
+ // Node that changed
105
+ JsonArray nodes = doc.createNestedArray("nodes");
106
+ if (joined) {
107
+ addNodeInfo(nodes, nodeId, "sensor");
108
+ } else {
109
+ // For node leaving, mark as offline
110
+ JsonObject node = nodes.createNestedObject();
111
+ node["node_id"] = generateDeviceId(nodeId);
112
+ node["status"] = "offline";
113
+ }
114
+
115
+ // Update connections
116
+ JsonArray connections = doc.createNestedArray("connections");
117
+ // Add connections related to this node
118
+ addConnectionInfoForNode(connections, nodeId);
119
+
120
+ // Updated metrics
121
+ auto nodeList = mesh.getNodeList(false);
122
+ JsonObject metrics = doc.createNestedObject("metrics");
123
+ metrics["total_nodes"] = nodeList.size() + 1;
124
+ metrics["online_nodes"] = nodeList.size() + 1;
125
+
126
+ doc["update_type"] = "incremental";
127
+
128
+ String output;
129
+ serializeJson(doc, output);
130
+ return output;
131
+ }
132
+
133
+ /**
134
+ * Check if topology has changed since last check
135
+ * Updates internal hash if changed
136
+ */
137
+ bool hasTopologyChanged() {
138
+ String currentHash = calculateTopologyHash();
139
+ if (currentHash != lastTopologyHash) {
140
+ lastTopologyHash = currentHash;
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+
146
+ private:
147
+ /**
148
+ * Add node information to JSON array
149
+ */
150
+ void addNodeInfo(JsonArray& nodes, uint32_t nodeId, const char* role) {
151
+ JsonObject node = nodes.createNestedObject();
152
+ node["node_id"] = generateDeviceId(nodeId);
153
+ node["role"] = role;
154
+ node["status"] = "online";
155
+ node["last_seen"] = getISO8601Timestamp();
156
+
157
+ // Gateway node has additional info
158
+ if (nodeId == mesh.getNodeId()) {
159
+ node["firmware_version"] = firmwareVersion;
160
+ node["uptime_seconds"] = millis() / 1000;
161
+ #if defined(ESP32) || defined(ESP8266)
162
+ node["free_memory_kb"] = ESP.getFreeHeap() / 1024;
163
+ #else
164
+ node["free_memory_kb"] = 0;
165
+ #endif
166
+ node["connection_count"] = mesh.getNodeList().size();
167
+ } else {
168
+ // For other nodes, use placeholder values
169
+ // TODO: Get actual values from node status messages
170
+ node["firmware_version"] = "SN 2.3.4";
171
+ node["uptime_seconds"] = 0;
172
+ node["free_memory_kb"] = 0;
173
+ node["connection_count"] = 0;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Add connection information to JSON array
179
+ */
180
+ void addConnectionInfo(JsonArray& connections) {
181
+ // Get direct connections with quality metrics
182
+ auto connDetails = mesh.getConnectionDetails();
183
+
184
+ String gatewayId = generateDeviceId(mesh.getNodeId());
185
+
186
+ for (auto& conn : connDetails) {
187
+ JsonObject connObj = connections.createNestedObject();
188
+ connObj["from_node"] = gatewayId;
189
+ connObj["to_node"] = generateDeviceId(conn.nodeId);
190
+ connObj["quality"] = conn.quality;
191
+ connObj["latency_ms"] = conn.avgLatency;
192
+ connObj["rssi"] = conn.rssi;
193
+ connObj["hop_count"] = conn.hopCount;
194
+ }
195
+
196
+ // TODO: Add indirect connections from mesh topology
197
+ // For now, we only report direct connections from gateway
198
+ }
199
+
200
+ /**
201
+ * Add connection information for a specific node
202
+ */
203
+ void addConnectionInfoForNode(JsonArray& connections, uint32_t nodeId) {
204
+ auto connDetails = mesh.getConnectionDetails();
205
+ String gatewayId = generateDeviceId(mesh.getNodeId());
206
+ String targetId = generateDeviceId(nodeId);
207
+
208
+ for (auto& conn : connDetails) {
209
+ if (conn.nodeId == nodeId) {
210
+ JsonObject connObj = connections.createNestedObject();
211
+ connObj["from_node"] = gatewayId;
212
+ connObj["to_node"] = targetId;
213
+ connObj["quality"] = conn.quality;
214
+ connObj["latency_ms"] = conn.avgLatency;
215
+ connObj["rssi"] = conn.rssi;
216
+ connObj["hop_count"] = conn.hopCount;
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Calculate network diameter (max hop count)
223
+ */
224
+ int calculateNetworkDiameter() {
225
+ int maxHops = 0;
226
+ auto nodeList = mesh.getNodeList(false);
227
+
228
+ for (auto nodeId : nodeList) {
229
+ int hops = mesh.getHopCount(nodeId);
230
+ if (hops > maxHops) {
231
+ maxHops = hops;
232
+ }
233
+ }
234
+
235
+ return maxHops;
236
+ }
237
+
238
+ /**
239
+ * Calculate average connection quality
240
+ */
241
+ float calculateAvgQuality() {
242
+ auto connDetails = mesh.getConnectionDetails();
243
+
244
+ if (connDetails.empty()) {
245
+ return 0.0;
246
+ }
247
+
248
+ int totalQuality = 0;
249
+ for (auto& conn : connDetails) {
250
+ totalQuality += conn.quality;
251
+ }
252
+
253
+ return (float)totalQuality / connDetails.size();
254
+ }
255
+
256
+ /**
257
+ * Calculate network throughput (messages per second)
258
+ */
259
+ float calculateThroughput() {
260
+ // TODO: Implement actual throughput tracking
261
+ // For now return placeholder
262
+ return 12.4;
263
+ }
264
+
265
+ /**
266
+ * Calculate topology hash for change detection
267
+ */
268
+ String calculateTopologyHash() {
269
+ auto nodeList = mesh.getNodeList(true);
270
+ String hash = String(nodeList.size());
271
+ for (auto nodeId : nodeList) {
272
+ hash += String(nodeId);
273
+ }
274
+ return hash;
275
+ }
276
+
277
+ /**
278
+ * Generate device ID in Alteriom format (ALT-XXXXXXXXXXXX)
279
+ */
280
+ String generateDeviceId(uint32_t nodeId) {
281
+ char deviceId[20];
282
+ snprintf(deviceId, sizeof(deviceId), "ALT-%012X", nodeId);
283
+ return String(deviceId);
284
+ }
285
+
286
+ /**
287
+ * Get current timestamp in ISO 8601 format
288
+ * TODO: Use proper NTP time if available
289
+ */
290
+ String getISO8601Timestamp() {
291
+ // For now, use epoch-based timestamp
292
+ // In production, this should use actual NTP-synced time
293
+ unsigned long ms = millis();
294
+ char timestamp[32];
295
+ snprintf(timestamp, sizeof(timestamp), "2025-10-12T%02lu:%02lu:%02luZ",
296
+ (ms / 3600000) % 24,
297
+ (ms / 60000) % 60,
298
+ (ms / 1000) % 60);
299
+ return String(timestamp);
300
+ }
301
+ };
302
+
303
+ #endif // MESH_TOPOLOGY_REPORTER_HPP