@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +380 -143
  2. package/LICENSE +674 -674
  3. package/README.md +477 -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 +77 -70
  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/getting-started/first-mesh.md +409 -409
  25. package/docs/getting-started/installation.md +274 -274
  26. package/docs/getting-started/quickstart.md +157 -157
  27. package/docs/improvements/FEATURE_PROPOSALS.md +337 -0
  28. package/docs/improvements/PHASE1_IMPLEMENTATION.md +325 -0
  29. package/docs/improvements/PHASE2_IMPLEMENTATION.md +567 -0
  30. package/docs/improvements/README.md +86 -68
  31. package/docs/improvements/ota-and-status-enhancements.md +911 -0
  32. package/docs/improvements/ota-status-architecture-diagrams.md +658 -0
  33. package/docs/improvements/ota-status-quick-reference.md +284 -0
  34. package/docs/platformio-publishing.md +255 -0
  35. package/docs/platformio-setup-summary.md +121 -0
  36. package/docs/troubleshooting/common-issues.md +520 -520
  37. package/docs/troubleshooting/faq.md +472 -472
  38. package/docs/tutorials/basic-examples.md +717 -717
  39. package/docs/wiki/API-Reference.md +245 -245
  40. package/docs/wiki/Complete-Documentation.md +122 -122
  41. package/examples/alteriom/README.md +139 -81
  42. package/examples/alteriom/alteriom.ino +186 -185
  43. package/examples/alteriom/alteriom_sensor_package.hpp +240 -127
  44. package/examples/alteriom/platformio.ini +24 -24
  45. package/examples/alteriomImproved/alteriom_sensor_package.hpp +224 -0
  46. package/examples/{alteriom → alteriomImproved}/improved_sensor_node.ino +245 -245
  47. package/examples/alteriomImproved/platformio.ini +25 -0
  48. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +224 -0
  49. package/examples/alteriomPhase1/phase1_features.ino +242 -0
  50. package/examples/alteriomPhase1/platformio.ini +25 -0
  51. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +224 -0
  52. package/examples/alteriomPhase2/phase2_features.ino +186 -0
  53. package/examples/alteriomPhase2/platformio.ini +25 -0
  54. package/examples/{alteriom → alteriomSensorNode}/alteriom_sensor_node.ino +183 -183
  55. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +224 -0
  56. package/examples/alteriomSensorNode/platformio.ini +25 -0
  57. package/examples/basic/basic.ino +66 -66
  58. package/examples/basic/platformio.ini +25 -25
  59. package/examples/bridge/bridge.ino +51 -51
  60. package/examples/bridge/mesh_event_publisher.hpp +253 -0
  61. package/examples/bridge/mesh_topology_reporter.hpp +303 -0
  62. package/examples/bridge/mqtt_command_bridge.hpp +459 -0
  63. package/examples/bridge/mqtt_status_bridge.hpp +519 -0
  64. package/examples/bridge/platformio.ini +25 -25
  65. package/examples/echoNode/echoNode.ino +33 -33
  66. package/examples/echoNode/platformio.ini +25 -25
  67. package/examples/logClient/logClient.ino +109 -109
  68. package/examples/logClient/platformio.ini +25 -25
  69. package/examples/logServer/logServer.ino +81 -81
  70. package/examples/logServer/platformio.ini +25 -25
  71. package/examples/meshCommandNode/alteriom_sensor_package.hpp +235 -0
  72. package/examples/meshCommandNode/meshCommandNode.ino +263 -0
  73. package/examples/meshCommandNode/platformio.ini +25 -0
  74. package/examples/mqttBridge/mqttBridge.ino +118 -118
  75. package/examples/mqttBridge/platformio.ini +26 -26
  76. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +235 -0
  77. package/examples/mqttCommandBridge/mesh_event_publisher.hpp +253 -0
  78. package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +303 -0
  79. package/examples/mqttCommandBridge/mqttCommandBridge.ino +252 -0
  80. package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +459 -0
  81. package/examples/mqttCommandBridge/platformio.ini +26 -0
  82. package/examples/mqttStatusBridge/mqttStatusBridge.ino +216 -0
  83. package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +522 -0
  84. package/examples/mqttStatusBridge/platformio.ini +26 -0
  85. package/examples/mqttTopologyTest/README.md +467 -0
  86. package/examples/mqttTopologyTest/mqttTopologyTest.ino +748 -0
  87. package/examples/mqttTopologyTest/platformio.ini +26 -0
  88. package/examples/namedMesh/namedMesh.ino +97 -97
  89. package/examples/namedMesh/platformio.ini +25 -25
  90. package/examples/otaReceiver/otaReceiver.ino +79 -79
  91. package/examples/otaReceiver/platformio.ini +25 -25
  92. package/examples/otaSender/otaSender.ino +160 -151
  93. package/examples/otaSender/platformio.ini +25 -25
  94. package/examples/startHere/platformio.ini +25 -25
  95. package/examples/startHere/startHere.ino +159 -159
  96. package/examples/webServer/platformio.ini +27 -27
  97. package/examples/webServer/webServer.ino +89 -89
  98. package/keywords.txt +48 -48
  99. package/library.json +55 -34
  100. package/library.properties +10 -10
  101. package/package.json +86 -78
  102. package/src/AlteriomPainlessMesh.h +97 -97
  103. package/src/arduino/wifi.hpp +365 -365
  104. package/src/boost/asynctcp.hpp +279 -279
  105. package/src/painlessMesh.h +70 -70
  106. package/src/painlessMeshSTA.cpp +236 -236
  107. package/src/painlessMeshSTA.h +58 -58
  108. package/src/painlessTaskOptions.h +4 -4
  109. package/src/painlessmesh/base64.hpp +111 -111
  110. package/src/painlessmesh/buffer.hpp +229 -229
  111. package/src/painlessmesh/callback.hpp +91 -91
  112. package/src/painlessmesh/configuration.hpp +77 -77
  113. package/src/painlessmesh/connection.hpp +192 -192
  114. package/src/painlessmesh/layout.hpp +188 -188
  115. package/src/painlessmesh/logger.hpp +158 -158
  116. package/src/painlessmesh/memory.hpp +119 -119
  117. package/src/painlessmesh/mesh.hpp +761 -560
  118. package/src/painlessmesh/metrics.hpp +322 -322
  119. package/src/painlessmesh/ntp.hpp +263 -263
  120. package/src/painlessmesh/ota.hpp +582 -553
  121. package/src/painlessmesh/plugin.hpp +188 -188
  122. package/src/painlessmesh/protocol.hpp +813 -813
  123. package/src/painlessmesh/router.hpp +322 -322
  124. package/src/painlessmesh/tcp.hpp +71 -71
  125. package/src/painlessmesh/validation.hpp +238 -238
  126. package/src/plugin/performance.hpp +214 -214
  127. package/src/plugin/remote.hpp +64 -64
  128. package/src/scheduler.cpp +10 -10
  129. package/src/wifi.cpp +2 -2
@@ -0,0 +1,748 @@
1
+ /**
2
+ * @file mqttTopologyTest.ino
3
+ * @brief Hardware test sketch for painlessMesh topology reporting with MQTT
4
+ *
5
+ * This sketch tests mesh topology reporting according to @alteriom/mqtt-schema v0.5.0
6
+ *
7
+ * HARDWARE REQUIREMENTS:
8
+ * - 3x ESP32 boards (or ESP8266, but ESP32 recommended)
9
+ * - MQTT broker accessible on network (e.g., Mosquitto)
10
+ * - USB cables for programming and Serial monitoring
11
+ *
12
+ * TEST CONFIGURATION:
13
+ * - Node 1: Gateway (this sketch with IS_GATEWAY = true)
14
+ * - Node 2: Sensor node (this sketch with IS_GATEWAY = false, role = "sensor")
15
+ * - Node 3: Repeater node (this sketch with IS_GATEWAY = false, role = "repeater")
16
+ *
17
+ * MQTT TOPICS TESTED:
18
+ * - alteriom/mesh/MESH-001/topology (full topology every 60s, incremental every 5s)
19
+ * - alteriom/mesh/MESH-001/events (node join/leave events)
20
+ * - alteriom/mesh/MESH-001/topology/response (GET_TOPOLOGY command response)
21
+ * - alteriom/mesh/MESH-001/command (receive GET_TOPOLOGY commands)
22
+ *
23
+ * SCHEMA COMPLIANCE:
24
+ * - Device ID format: ALT-XXXXXXXXXXXX (uppercase hex)
25
+ * - Quality metrics: 0-100 range
26
+ * - RSSI values: negative dBm
27
+ * - Timestamps: ISO 8601 format
28
+ * - Envelope fields: schema_version, device_id, device_type, timestamp, firmware_version
29
+ *
30
+ * TEST PROCEDURE:
31
+ * 1. Configure WiFi credentials (WIFI_SSID, WIFI_PASSWORD)
32
+ * 2. Configure MQTT broker (MQTT_BROKER, MQTT_PORT)
33
+ * 3. Flash Node 1 with IS_GATEWAY = true
34
+ * 4. Flash Node 2 with IS_GATEWAY = false, NODE_ROLE = "sensor"
35
+ * 5. Flash Node 3 with IS_GATEWAY = false, NODE_ROLE = "repeater"
36
+ * 6. Open Serial monitors for all 3 nodes
37
+ * 7. Wait for mesh formation (watch for "Node joined" messages)
38
+ * 8. Verify full topology published every 60s
39
+ * 9. Verify incremental updates on node join/leave
40
+ * 10. Send GET_TOPOLOGY command and verify response
41
+ *
42
+ * EXPECTED BEHAVIOR:
43
+ * - All nodes join mesh within 30 seconds
44
+ * - Gateway publishes full topology every 60 seconds
45
+ * - Gateway publishes incremental updates within 5 seconds of node join/leave
46
+ * - All messages conform to @alteriom/mqtt-schema v0.5.0
47
+ * - Connection quality metrics are calculated correctly
48
+ *
49
+ * @author Alteriom Development Team
50
+ * @date October 14, 2025
51
+ * @version 1.0.0
52
+ */
53
+
54
+ #include "painlessMesh.h"
55
+ #ifdef ESP8266
56
+ #include <ESP8266WiFi.h>
57
+ #else
58
+ #include <WiFi.h>
59
+ #endif
60
+ #include <PubSubClient.h>
61
+ #include <ArduinoJson.h>
62
+
63
+ // ============================================================================
64
+ // CONFIGURATION - MODIFY THESE FOR YOUR SETUP
65
+ // ============================================================================
66
+
67
+ // Mesh Configuration
68
+ #define MESH_PREFIX "AlteriomMesh"
69
+ #define MESH_PASSWORD "alteriom2025"
70
+ #define MESH_PORT 5555
71
+
72
+ // WiFi Configuration (for gateway only)
73
+ #define WIFI_SSID "YourWiFiSSID"
74
+ #define WIFI_PASSWORD "YourWiFiPassword"
75
+
76
+ // MQTT Configuration (for gateway only)
77
+ #define MQTT_BROKER "192.168.1.100" // Your MQTT broker IP
78
+ #define MQTT_PORT 1883
79
+ #define MQTT_USER "" // Leave empty if no auth
80
+ #define MQTT_PASS "" // Leave empty if no auth
81
+
82
+ // Node Configuration - CHANGE THIS FOR EACH NODE
83
+ #define IS_GATEWAY true // Set to false for sensor/repeater nodes
84
+ #define NODE_ROLE "gateway" // "gateway", "sensor", or "repeater"
85
+ #define MESH_ID "MESH-001"
86
+ #define FIRMWARE_VERSION "TEST 1.0.0"
87
+
88
+ // Topology Publishing Configuration
89
+ #define FULL_TOPOLOGY_INTERVAL 60000 // Full topology every 60 seconds
90
+ #define INCREMENTAL_INTERVAL 5000 // Check for changes every 5 seconds
91
+ #define COMMAND_CHECK_INTERVAL 1000 // Check for commands every 1 second
92
+
93
+ // ============================================================================
94
+ // GLOBAL OBJECTS
95
+ // ============================================================================
96
+
97
+ Scheduler userScheduler;
98
+ painlessMesh mesh;
99
+
100
+ #if IS_GATEWAY
101
+ WiFiClient wifiClient;
102
+ PubSubClient mqttClient(wifiClient);
103
+ #endif
104
+
105
+ // Task declarations
106
+ Task taskSendFullTopology(FULL_TOPOLOGY_INTERVAL, TASK_FOREVER, &sendFullTopology);
107
+ Task taskCheckIncrementalUpdate(INCREMENTAL_INTERVAL, TASK_FOREVER, &checkIncrementalUpdate);
108
+ Task taskCheckCommands(COMMAND_CHECK_INTERVAL, TASK_FOREVER, &checkCommands);
109
+
110
+ // Topology state tracking
111
+ struct NodeState {
112
+ uint32_t nodeId;
113
+ String role;
114
+ bool online;
115
+ unsigned long lastSeen;
116
+ int connectionCount;
117
+ };
118
+
119
+ std::map<uint32_t, NodeState> lastTopology;
120
+ unsigned long lastTopologyUpdate = 0;
121
+
122
+ // ============================================================================
123
+ // DEVICE ID GENERATION
124
+ // ============================================================================
125
+
126
+ /**
127
+ * Generate Alteriom-compliant device ID from chip ID
128
+ * Format: ALT-XXXXXXXXXXXX (uppercase hex)
129
+ */
130
+ String getDeviceId() {
131
+ uint64_t chipid = ESP.getEfuseMac(); // ESP32 unique ID
132
+ char deviceId[17];
133
+ snprintf(deviceId, sizeof(deviceId), "ALT-%012llX", chipid);
134
+ return String(deviceId);
135
+ }
136
+
137
+ // ============================================================================
138
+ // ISO 8601 TIMESTAMP
139
+ // ============================================================================
140
+
141
+ /**
142
+ * Get current timestamp in ISO 8601 format
143
+ * Note: Uses mesh time, not real-world time (no NTP in this test)
144
+ */
145
+ String getTimestamp() {
146
+ uint32_t meshTime = mesh.getNodeTime();
147
+ // Convert microseconds to seconds
148
+ uint32_t seconds = meshTime / 1000000;
149
+
150
+ // Simple ISO 8601 format (no actual date, just offset from boot)
151
+ char timestamp[30];
152
+ snprintf(timestamp, sizeof(timestamp), "2025-10-14T%02d:%02d:%02dZ",
153
+ (seconds / 3600) % 24,
154
+ (seconds / 60) % 60,
155
+ seconds % 60);
156
+ return String(timestamp);
157
+ }
158
+
159
+ // ============================================================================
160
+ // CONNECTION QUALITY CALCULATION
161
+ // ============================================================================
162
+
163
+ /**
164
+ * Calculate connection quality (0-100) based on mesh metrics
165
+ * This is a simplified calculation for testing
166
+ */
167
+ int calculateQuality(uint32_t nodeId) {
168
+ // In a real implementation, this would use:
169
+ // - RSSI measurements
170
+ // - Packet loss rate
171
+ // - Latency measurements
172
+ // For testing, we'll use a simulated value based on node ID
173
+
174
+ // Simulate quality degradation with distance (hop count)
175
+ SimpleList<uint32_t> nodeList = mesh.getNodeList();
176
+ int nodeCount = nodeList.size();
177
+
178
+ // Base quality: 95 for direct connections
179
+ int quality = 95;
180
+
181
+ // Reduce quality based on mesh size (simulate congestion)
182
+ if (nodeCount > 3) quality -= (nodeCount - 3) * 5;
183
+
184
+ // Add some variation based on node ID
185
+ quality -= (nodeId % 10);
186
+
187
+ // Clamp to 0-100 range
188
+ return std::max(0, std::min(100, quality));
189
+ }
190
+
191
+ /**
192
+ * Simulate RSSI (WiFi signal strength in dBm)
193
+ * Real implementation would read actual WiFi RSSI
194
+ */
195
+ int calculateRSSI(uint32_t nodeId) {
196
+ // RSSI ranges from -30 (excellent) to -90 (poor)
197
+ int rssi = -40 - (nodeId % 50); // Simulated based on node ID
198
+ return std::max(-90, std::min(-30, rssi));
199
+ }
200
+
201
+ /**
202
+ * Simulate round-trip latency in milliseconds
203
+ * Real implementation would measure actual ping times
204
+ */
205
+ int calculateLatency(uint32_t nodeId) {
206
+ // Latency increases with hop count
207
+ int baseLatency = 10;
208
+ int variation = nodeId % 20;
209
+ return baseLatency + variation;
210
+ }
211
+
212
+ // ============================================================================
213
+ // MQTT PUBLISHING (GATEWAY ONLY)
214
+ // ============================================================================
215
+
216
+ #if IS_GATEWAY
217
+
218
+ /**
219
+ * Connect to MQTT broker
220
+ */
221
+ void connectMQTT() {
222
+ while (!mqttClient.connected()) {
223
+ Serial.print("Connecting to MQTT broker...");
224
+
225
+ String clientId = "AlteriomGateway-" + getDeviceId();
226
+
227
+ if (mqttClient.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
228
+ Serial.println(" connected!");
229
+
230
+ // Subscribe to command topic
231
+ String commandTopic = "alteriom/mesh/" + String(MESH_ID) + "/command";
232
+ mqttClient.subscribe(commandTopic.c_str());
233
+ Serial.println("Subscribed to: " + commandTopic);
234
+ } else {
235
+ Serial.print(" failed, rc=");
236
+ Serial.print(mqttClient.state());
237
+ Serial.println(" retrying in 5 seconds");
238
+ delay(5000);
239
+ }
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Publish message to MQTT with envelope fields
245
+ */
246
+ bool publishMQTT(const String& topic, JsonDocument& doc) {
247
+ // Add envelope fields
248
+ doc["schema_version"] = 1;
249
+ doc["device_id"] = getDeviceId();
250
+ doc["device_type"] = NODE_ROLE;
251
+ doc["timestamp"] = getTimestamp();
252
+ doc["firmware_version"] = FIRMWARE_VERSION;
253
+
254
+ // Serialize to JSON
255
+ String jsonString;
256
+ serializeJson(doc, jsonString);
257
+
258
+ // Publish
259
+ bool success = mqttClient.publish(topic.c_str(), jsonString.c_str());
260
+
261
+ if (success) {
262
+ Serial.println("✅ Published to " + topic);
263
+ Serial.println(" Size: " + String(jsonString.length()) + " bytes");
264
+ } else {
265
+ Serial.println("❌ Failed to publish to " + topic);
266
+ }
267
+
268
+ return success;
269
+ }
270
+
271
+ /**
272
+ * Publish mesh event (node join/leave)
273
+ */
274
+ void publishMeshEvent(const String& eventType, uint32_t nodeId, const String& reason = "") {
275
+ JsonDocument doc;
276
+
277
+ doc["event"] = "mesh_event";
278
+ doc["mesh_id"] = MESH_ID;
279
+ doc["event_type"] = eventType;
280
+
281
+ JsonArray affectedNodes = doc["affected_nodes"].to<JsonArray>();
282
+ char nodeIdStr[17];
283
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
284
+ affectedNodes.add(nodeIdStr);
285
+
286
+ if (reason != "") {
287
+ JsonObject details = doc["details"].to<JsonObject>();
288
+ details["reason"] = reason;
289
+ details["last_seen"] = getTimestamp();
290
+ }
291
+
292
+ String topic = "alteriom/mesh/" + String(MESH_ID) + "/events";
293
+ publishMQTT(topic, doc);
294
+ }
295
+
296
+ #endif
297
+
298
+ // ============================================================================
299
+ // TOPOLOGY REPORTING
300
+ // ============================================================================
301
+
302
+ /**
303
+ * Build and send full mesh topology
304
+ */
305
+ void sendFullTopology() {
306
+ #if IS_GATEWAY
307
+ Serial.println("\n📊 Generating FULL topology report...");
308
+
309
+ if (!mqttClient.connected()) {
310
+ connectMQTT();
311
+ return;
312
+ }
313
+
314
+ JsonDocument doc;
315
+
316
+ doc["event"] = "mesh_topology";
317
+ doc["mesh_id"] = MESH_ID;
318
+ doc["gateway_node_id"] = getDeviceId();
319
+ doc["update_type"] = "full";
320
+
321
+ // Get node list
322
+ SimpleList<uint32_t> nodeList = mesh.getNodeList();
323
+
324
+ // Add nodes array
325
+ JsonArray nodes = doc["nodes"].to<JsonArray>();
326
+
327
+ // Add gateway node
328
+ JsonObject gatewayNode = nodes.add<JsonObject>();
329
+ gatewayNode["node_id"] = getDeviceId();
330
+ gatewayNode["role"] = NODE_ROLE;
331
+ gatewayNode["status"] = "online";
332
+ gatewayNode["last_seen"] = getTimestamp();
333
+ gatewayNode["firmware_version"] = FIRMWARE_VERSION;
334
+ gatewayNode["uptime_seconds"] = millis() / 1000;
335
+ gatewayNode["free_memory_kb"] = ESP.getFreeHeap() / 1024;
336
+ gatewayNode["connection_count"] = nodeList.size();
337
+
338
+ // Add other nodes
339
+ for (auto nodeId : nodeList) {
340
+ JsonObject node = nodes.add<JsonObject>();
341
+
342
+ char nodeIdStr[17];
343
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
344
+ node["node_id"] = nodeIdStr;
345
+
346
+ // Simulate role based on node ID
347
+ node["role"] = (nodeId % 2 == 0) ? "sensor" : "repeater";
348
+ node["status"] = "online";
349
+ node["last_seen"] = getTimestamp();
350
+ node["firmware_version"] = FIRMWARE_VERSION;
351
+ node["uptime_seconds"] = 0; // Unknown for remote nodes
352
+ node["free_memory_kb"] = 0; // Unknown for remote nodes
353
+ node["connection_count"] = 1; // Simplified
354
+ }
355
+
356
+ // Add connections array
357
+ JsonArray connections = doc["connections"].to<JsonArray>();
358
+
359
+ for (auto nodeId : nodeList) {
360
+ JsonObject conn = connections.add<JsonObject>();
361
+ conn["from_node"] = getDeviceId();
362
+
363
+ char nodeIdStr[17];
364
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
365
+ conn["to_node"] = nodeIdStr;
366
+
367
+ conn["quality"] = calculateQuality(nodeId);
368
+ conn["latency_ms"] = calculateLatency(nodeId);
369
+ conn["rssi"] = calculateRSSI(nodeId);
370
+ conn["hop_count"] = 1; // Direct connection to gateway
371
+ }
372
+
373
+ // Add metrics
374
+ JsonObject metrics = doc["metrics"].to<JsonObject>();
375
+ metrics["total_nodes"] = nodeList.size() + 1; // +1 for gateway
376
+ metrics["online_nodes"] = nodeList.size() + 1;
377
+ metrics["network_diameter"] = 1; // Single hop for this test
378
+
379
+ int avgQuality = 0;
380
+ for (auto nodeId : nodeList) {
381
+ avgQuality += calculateQuality(nodeId);
382
+ }
383
+ if (nodeList.size() > 0) {
384
+ avgQuality /= nodeList.size();
385
+ }
386
+ metrics["avg_connection_quality"] = avgQuality;
387
+ metrics["messages_per_second"] = 0.0; // Not tracked in this test
388
+
389
+ String topic = "alteriom/mesh/" + String(MESH_ID) + "/topology";
390
+ publishMQTT(topic, doc);
391
+
392
+ // Update last topology state
393
+ lastTopology.clear();
394
+ for (auto nodeId : nodeList) {
395
+ NodeState state;
396
+ state.nodeId = nodeId;
397
+ state.role = (nodeId % 2 == 0) ? "sensor" : "repeater";
398
+ state.online = true;
399
+ state.lastSeen = millis();
400
+ state.connectionCount = 1;
401
+ lastTopology[nodeId] = state;
402
+ }
403
+ lastTopologyUpdate = millis();
404
+
405
+ Serial.println("✅ Full topology published");
406
+ Serial.println(" Nodes: " + String(nodeList.size() + 1));
407
+ Serial.println(" Connections: " + String(nodeList.size()));
408
+
409
+ #endif
410
+ }
411
+
412
+ /**
413
+ * Check for topology changes and send incremental update
414
+ */
415
+ void checkIncrementalUpdate() {
416
+ #if IS_GATEWAY
417
+ if (!mqttClient.connected()) {
418
+ connectMQTT();
419
+ return;
420
+ }
421
+
422
+ SimpleList<uint32_t> currentNodes = mesh.getNodeList();
423
+ bool hasChanges = false;
424
+
425
+ // Check for new nodes
426
+ std::vector<uint32_t> newNodes;
427
+ for (auto nodeId : currentNodes) {
428
+ if (lastTopology.find(nodeId) == lastTopology.end()) {
429
+ newNodes.push_back(nodeId);
430
+ hasChanges = true;
431
+ }
432
+ }
433
+
434
+ // Check for removed nodes
435
+ std::vector<uint32_t> removedNodes;
436
+ for (auto& pair : lastTopology) {
437
+ uint32_t nodeId = pair.first;
438
+ bool found = false;
439
+ for (auto currentId : currentNodes) {
440
+ if (currentId == nodeId) {
441
+ found = true;
442
+ break;
443
+ }
444
+ }
445
+ if (!found) {
446
+ removedNodes.push_back(nodeId);
447
+ hasChanges = true;
448
+ }
449
+ }
450
+
451
+ if (hasChanges) {
452
+ Serial.println("\n🔄 Topology changed! Generating incremental update...");
453
+
454
+ JsonDocument doc;
455
+
456
+ doc["event"] = "mesh_topology";
457
+ doc["mesh_id"] = MESH_ID;
458
+ doc["gateway_node_id"] = getDeviceId();
459
+ doc["update_type"] = "incremental";
460
+
461
+ JsonArray nodes = doc["nodes"].to<JsonArray>();
462
+ JsonArray connections = doc["connections"].to<JsonArray>();
463
+
464
+ // Add new nodes
465
+ for (auto nodeId : newNodes) {
466
+ JsonObject node = nodes.add<JsonObject>();
467
+
468
+ char nodeIdStr[17];
469
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
470
+ node["node_id"] = nodeIdStr;
471
+ node["role"] = (nodeId % 2 == 0) ? "sensor" : "repeater";
472
+ node["status"] = "online";
473
+ node["last_seen"] = getTimestamp();
474
+
475
+ JsonObject conn = connections.add<JsonObject>();
476
+ conn["from_node"] = getDeviceId();
477
+ conn["to_node"] = nodeIdStr;
478
+ conn["quality"] = calculateQuality(nodeId);
479
+ conn["latency_ms"] = calculateLatency(nodeId);
480
+ conn["rssi"] = calculateRSSI(nodeId);
481
+ conn["hop_count"] = 1;
482
+
483
+ Serial.println(" + Node joined: " + String(nodeIdStr));
484
+
485
+ // Publish event
486
+ publishMeshEvent("node_join", nodeId);
487
+
488
+ // Update state
489
+ NodeState state;
490
+ state.nodeId = nodeId;
491
+ state.role = (nodeId % 2 == 0) ? "sensor" : "repeater";
492
+ state.online = true;
493
+ state.lastSeen = millis();
494
+ state.connectionCount = 1;
495
+ lastTopology[nodeId] = state;
496
+ }
497
+
498
+ // Mark removed nodes as offline
499
+ for (auto nodeId : removedNodes) {
500
+ JsonObject node = nodes.add<JsonObject>();
501
+
502
+ char nodeIdStr[17];
503
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
504
+ node["node_id"] = nodeIdStr;
505
+ node["status"] = "offline";
506
+ node["last_seen"] = getTimestamp();
507
+
508
+ Serial.println(" - Node left: " + String(nodeIdStr));
509
+
510
+ // Publish event
511
+ publishMeshEvent("node_leave", nodeId, "timeout");
512
+
513
+ // Remove from state
514
+ lastTopology.erase(nodeId);
515
+ }
516
+
517
+ String topic = "alteriom/mesh/" + String(MESH_ID) + "/topology";
518
+ publishMQTT(topic, doc);
519
+
520
+ Serial.println("✅ Incremental update published");
521
+ }
522
+ #endif
523
+ }
524
+
525
+ /**
526
+ * Check for incoming MQTT commands
527
+ */
528
+ void checkCommands() {
529
+ #if IS_GATEWAY
530
+ mqttClient.loop();
531
+ #endif
532
+ }
533
+
534
+ // ============================================================================
535
+ // MQTT COMMAND HANDLER (GATEWAY ONLY)
536
+ // ============================================================================
537
+
538
+ #if IS_GATEWAY
539
+
540
+ /**
541
+ * Handle incoming MQTT messages (commands)
542
+ */
543
+ void mqttCallback(char* topic, byte* payload, unsigned int length) {
544
+ Serial.println("\n📥 MQTT message received:");
545
+ Serial.println(" Topic: " + String(topic));
546
+
547
+ // Parse JSON
548
+ JsonDocument doc;
549
+ DeserializationError error = deserializeJson(doc, payload, length);
550
+
551
+ if (error) {
552
+ Serial.println(" ❌ JSON parse error: " + String(error.c_str()));
553
+ return;
554
+ }
555
+
556
+ String event = doc["event"];
557
+ String command = doc["command"];
558
+ String correlationId = doc["correlation_id"];
559
+
560
+ Serial.println(" Event: " + event);
561
+ Serial.println(" Command: " + command);
562
+ Serial.println(" Correlation ID: " + correlationId);
563
+
564
+ // Handle GET_TOPOLOGY command (command ID 300)
565
+ if (event == "command" && command == "get_topology") {
566
+ Serial.println(" 🔍 Processing GET_TOPOLOGY command...");
567
+
568
+ unsigned long startTime = millis();
569
+
570
+ // Build topology response
571
+ JsonDocument responseDoc;
572
+ responseDoc["event"] = "command_response";
573
+ responseDoc["command"] = "get_topology";
574
+ responseDoc["correlation_id"] = correlationId;
575
+ responseDoc["success"] = true;
576
+
577
+ // Build topology result
578
+ JsonObject result = responseDoc["result"].to<JsonObject>();
579
+ result["event"] = "mesh_topology";
580
+ result["mesh_id"] = MESH_ID;
581
+ result["gateway_node_id"] = getDeviceId();
582
+ result["update_type"] = "full";
583
+
584
+ SimpleList<uint32_t> nodeList = mesh.getNodeList();
585
+
586
+ // Add nodes
587
+ JsonArray nodes = result["nodes"].to<JsonArray>();
588
+
589
+ JsonObject gatewayNode = nodes.add<JsonObject>();
590
+ gatewayNode["node_id"] = getDeviceId();
591
+ gatewayNode["role"] = NODE_ROLE;
592
+ gatewayNode["status"] = "online";
593
+ gatewayNode["connection_count"] = nodeList.size();
594
+
595
+ for (auto nodeId : nodeList) {
596
+ JsonObject node = nodes.add<JsonObject>();
597
+ char nodeIdStr[17];
598
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
599
+ node["node_id"] = nodeIdStr;
600
+ node["role"] = (nodeId % 2 == 0) ? "sensor" : "repeater";
601
+ node["status"] = "online";
602
+ }
603
+
604
+ // Add connections
605
+ JsonArray connections = result["connections"].to<JsonArray>();
606
+ for (auto nodeId : nodeList) {
607
+ JsonObject conn = connections.add<JsonObject>();
608
+ conn["from_node"] = getDeviceId();
609
+ char nodeIdStr[17];
610
+ snprintf(nodeIdStr, sizeof(nodeIdStr), "ALT-%012X", nodeId);
611
+ conn["to_node"] = nodeIdStr;
612
+ conn["quality"] = calculateQuality(nodeId);
613
+ conn["hop_count"] = 1;
614
+ }
615
+
616
+ // Add metrics
617
+ JsonObject metrics = result["metrics"].to<JsonObject>();
618
+ metrics["total_nodes"] = nodeList.size() + 1;
619
+ metrics["online_nodes"] = nodeList.size() + 1;
620
+
621
+ // Calculate latency
622
+ unsigned long latency = millis() - startTime;
623
+ responseDoc["latency_ms"] = latency;
624
+
625
+ // Publish response
626
+ String responseTopic = "alteriom/mesh/" + String(MESH_ID) + "/topology/response";
627
+ publishMQTT(responseTopic, responseDoc);
628
+
629
+ Serial.println(" ✅ GET_TOPOLOGY response sent");
630
+ Serial.println(" Latency: " + String(latency) + "ms");
631
+ }
632
+ }
633
+
634
+ #endif
635
+
636
+ // ============================================================================
637
+ // MESH CALLBACKS
638
+ // ============================================================================
639
+
640
+ void receivedCallback(uint32_t from, String &msg) {
641
+ Serial.printf("Received from %u: %s\n", from, msg.c_str());
642
+ }
643
+
644
+ void newConnectionCallback(uint32_t nodeId) {
645
+ Serial.printf("✅ New connection: %u\n", nodeId);
646
+
647
+ #if IS_GATEWAY
648
+ // Trigger incremental update on next check
649
+ lastTopologyUpdate = 0;
650
+ #endif
651
+ }
652
+
653
+ void changedConnectionCallback() {
654
+ Serial.println("🔄 Connections changed");
655
+ }
656
+
657
+ void nodeTimeAdjustedCallback(int32_t offset) {
658
+ Serial.printf("⏰ Time adjusted: %d us\n", offset);
659
+ }
660
+
661
+ void droppedConnectionCallback(uint32_t nodeId) {
662
+ Serial.printf("❌ Connection dropped: %u\n", nodeId);
663
+
664
+ #if IS_GATEWAY
665
+ // Trigger incremental update on next check
666
+ lastTopologyUpdate = 0;
667
+ #endif
668
+ }
669
+
670
+ // ============================================================================
671
+ // SETUP
672
+ // ============================================================================
673
+
674
+ void setup() {
675
+ Serial.begin(115200);
676
+ delay(1000);
677
+
678
+ Serial.println("\n\n");
679
+ Serial.println("================================");
680
+ Serial.println("Alteriom Mesh Topology Test");
681
+ Serial.println("================================");
682
+ Serial.println("Device ID: " + getDeviceId());
683
+ Serial.println("Role: " + String(NODE_ROLE));
684
+ Serial.println("Firmware: " + String(FIRMWARE_VERSION));
685
+ Serial.println("Gateway: " + String(IS_GATEWAY ? "YES" : "NO"));
686
+ Serial.println("================================\n");
687
+
688
+ // Initialize mesh
689
+ mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
690
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
691
+
692
+ // Set callbacks
693
+ mesh.onReceive(&receivedCallback);
694
+ mesh.onNewConnection(&newConnectionCallback);
695
+ mesh.onChangedConnections(&changedConnectionCallback);
696
+ mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
697
+ mesh.onDroppedConnection(&droppedConnectionCallback);
698
+
699
+ #if IS_GATEWAY
700
+ // Gateway: Connect to WiFi
701
+ Serial.println("Connecting to WiFi: " + String(WIFI_SSID));
702
+ WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
703
+
704
+ while (WiFi.status() != WL_CONNECTED) {
705
+ delay(500);
706
+ Serial.print(".");
707
+ }
708
+
709
+ Serial.println("\n✅ WiFi connected!");
710
+ Serial.println(" IP: " + WiFi.localIP().toString());
711
+
712
+ // Connect to MQTT
713
+ mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
714
+ mqttClient.setCallback(mqttCallback);
715
+ mqttClient.setBufferSize(2048); // Increase buffer for large topology messages
716
+ connectMQTT();
717
+
718
+ // Add topology reporting tasks
719
+ userScheduler.addTask(taskSendFullTopology);
720
+ userScheduler.addTask(taskCheckIncrementalUpdate);
721
+ userScheduler.addTask(taskCheckCommands);
722
+
723
+ taskSendFullTopology.enable();
724
+ taskCheckIncrementalUpdate.enable();
725
+ taskCheckCommands.enable();
726
+
727
+ Serial.println("✅ Topology reporting enabled");
728
+ Serial.println(" Full topology: every " + String(FULL_TOPOLOGY_INTERVAL/1000) + "s");
729
+ Serial.println(" Incremental check: every " + String(INCREMENTAL_INTERVAL/1000) + "s");
730
+ #endif
731
+
732
+ Serial.println("\n🚀 Setup complete! Mesh is running...\n");
733
+ }
734
+
735
+ // ============================================================================
736
+ // LOOP
737
+ // ============================================================================
738
+
739
+ void loop() {
740
+ mesh.update();
741
+
742
+ #if IS_GATEWAY
743
+ // Keep MQTT connected
744
+ if (!mqttClient.connected()) {
745
+ connectMQTT();
746
+ }
747
+ #endif
748
+ }