@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.
- package/CHANGELOG.md +435 -144
- package/LICENSE +674 -674
- package/README.md +491 -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 +130 -71
- 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/archive/DOCUSAURUS_DEPLOYMENT.md +166 -0
- package/docs/archive/FEATURE_PROPOSALS.md +337 -0
- package/docs/archive/LIBRARY_JSON_FIX.md +98 -0
- package/docs/archive/LIBRARY_STRUCTURE_FIX.md +215 -0
- package/docs/archive/PHASE1_IMPLEMENTATION.md +325 -0
- package/docs/archive/PHASE2_IMPLEMENTATION.md +567 -0
- package/docs/archive/RELEASE_SUMMARY.md +173 -0
- package/docs/archive/SCONS_BUILD_FIX.md +313 -0
- package/docs/archive/TRIGGER_RELEASE.md +280 -0
- package/docs/archive/VECTOR_INCLUDE_FIX.md +129 -0
- package/docs/archive/ota-and-status-enhancements.md +911 -0
- package/docs/archive/ota-status-architecture-diagrams.md +658 -0
- package/docs/archive/ota-status-quick-reference.md +284 -0
- package/docs/development/ARDUINO_COMPLIANCE_SUMMARY.md +71 -0
- package/docs/development/CODE_REFACTORING_RECOMMENDATIONS.md +1011 -0
- package/docs/development/DOCKER_TESTING.md +196 -0
- package/docs/development/PLATFORMIO_USAGE.md +180 -0
- package/docs/development/TESTING_SUMMARY.md +126 -0
- package/docs/development/contributing.md +301 -0
- package/docs/development/documentation.md +583 -0
- 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/FUTURE_PROPOSALS.md +1016 -0
- package/docs/improvements/IMPLEMENTATION_HISTORY.md +1091 -0
- package/docs/improvements/OTA_STATUS_ENHANCEMENTS.md +709 -0
- package/docs/improvements/README.md +212 -69
- package/docs/platformio-publishing.md +255 -0
- package/docs/platformio-setup-summary.md +121 -0
- package/docs/releases/FEATURE_HISTORY.md +543 -0
- package/docs/releases/PATCH_v1.7.3.md +262 -0
- package/docs/releases/PHASE1_SUMMARY.md +246 -0
- package/docs/releases/PHASE2_SUMMARY.md +499 -0
- package/docs/releases/RELEASE_NOTES_1.7.0.md +539 -0
- package/docs/troubleshooting/common-issues.md +520 -520
- package/docs/troubleshooting/debugging.md +455 -0
- 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 +338 -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,992 @@
|
|
|
1
|
+
# Mesh Topology Visualization Guide
|
|
2
|
+
|
|
3
|
+
**Version:** 1.0.0
|
|
4
|
+
**Date:** October 14, 2025
|
|
5
|
+
**Schema:** @alteriom/mqtt-schema v0.5.0
|
|
6
|
+
|
|
7
|
+
A comprehensive guide for building web dashboards and monitoring tools to visualize painlessMesh network topology using MQTT.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
1. [Quick Start](#quick-start)
|
|
14
|
+
2. [D3.js Force-Directed Graph](#d3js-force-directed-graph)
|
|
15
|
+
3. [Cytoscape.js Network View](#cytoscapejs-network-view)
|
|
16
|
+
4. [Real-Time Dashboard](#real-time-dashboard)
|
|
17
|
+
5. [Python Monitoring](#python-monitoring)
|
|
18
|
+
6. [Node-RED Flows](#node-red-flows)
|
|
19
|
+
7. [Troubleshooting](#troubleshooting)
|
|
20
|
+
8. [Performance Considerations](#performance-considerations)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
|
|
28
|
+
- MQTT broker (Mosquitto, HiveMQ, etc.)
|
|
29
|
+
- painlessMesh gateway publishing topology
|
|
30
|
+
- Modern web browser (Chrome, Firefox, Edge)
|
|
31
|
+
- Basic JavaScript/HTML knowledge
|
|
32
|
+
|
|
33
|
+
### Test Your Setup
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Subscribe to topology messages
|
|
37
|
+
mosquitto_sub -h localhost -t "alteriom/mesh/+/topology" -v
|
|
38
|
+
|
|
39
|
+
# You should see messages like:
|
|
40
|
+
# alteriom/mesh/MESH-001/topology {"event":"mesh_topology",...}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## D3.js Force-Directed Graph
|
|
46
|
+
|
|
47
|
+
### Complete HTML Example
|
|
48
|
+
|
|
49
|
+
Save as `mesh-viewer-d3.html`:
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<!DOCTYPE html>
|
|
53
|
+
<html lang="en">
|
|
54
|
+
<head>
|
|
55
|
+
<meta charset="UTF-8">
|
|
56
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
57
|
+
<title>Alteriom Mesh Viewer - D3.js</title>
|
|
58
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
59
|
+
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
|
|
60
|
+
<style>
|
|
61
|
+
body {
|
|
62
|
+
margin: 0;
|
|
63
|
+
font-family: Arial, sans-serif;
|
|
64
|
+
background: #1a1a2e;
|
|
65
|
+
color: #eee;
|
|
66
|
+
}
|
|
67
|
+
#container {
|
|
68
|
+
display: flex;
|
|
69
|
+
height: 100vh;
|
|
70
|
+
}
|
|
71
|
+
#graph {
|
|
72
|
+
flex: 1;
|
|
73
|
+
background: #16213e;
|
|
74
|
+
}
|
|
75
|
+
#sidebar {
|
|
76
|
+
width: 300px;
|
|
77
|
+
background: #0f3460;
|
|
78
|
+
padding: 20px;
|
|
79
|
+
overflow-y: auto;
|
|
80
|
+
}
|
|
81
|
+
.node {
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
.node-gateway { fill: #00d4ff; }
|
|
85
|
+
.node-sensor { fill: #4ecca3; }
|
|
86
|
+
.node-repeater { fill: #f39c12; }
|
|
87
|
+
.node-offline { fill: #e74c3c; }
|
|
88
|
+
.link { stroke: #555; stroke-opacity: 0.6; }
|
|
89
|
+
.link-excellent { stroke: #4ecca3; }
|
|
90
|
+
.link-good { stroke: #f39c12; }
|
|
91
|
+
.link-poor { stroke: #e74c3c; }
|
|
92
|
+
.node-label {
|
|
93
|
+
font-size: 10px;
|
|
94
|
+
fill: #eee;
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
}
|
|
97
|
+
#stats {
|
|
98
|
+
background: #16213e;
|
|
99
|
+
padding: 15px;
|
|
100
|
+
margin-bottom: 15px;
|
|
101
|
+
border-radius: 5px;
|
|
102
|
+
}
|
|
103
|
+
.stat-item {
|
|
104
|
+
display: flex;
|
|
105
|
+
justify-content: space-between;
|
|
106
|
+
margin: 5px 0;
|
|
107
|
+
}
|
|
108
|
+
#event-log {
|
|
109
|
+
background: #16213e;
|
|
110
|
+
padding: 15px;
|
|
111
|
+
border-radius: 5px;
|
|
112
|
+
max-height: 300px;
|
|
113
|
+
overflow-y: auto;
|
|
114
|
+
}
|
|
115
|
+
.event {
|
|
116
|
+
padding: 8px;
|
|
117
|
+
margin: 5px 0;
|
|
118
|
+
border-left: 3px solid #00d4ff;
|
|
119
|
+
background: #0f3460;
|
|
120
|
+
font-size: 12px;
|
|
121
|
+
}
|
|
122
|
+
.event-join { border-left-color: #4ecca3; }
|
|
123
|
+
.event-leave { border-left-color: #e74c3c; }
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<div id="container">
|
|
128
|
+
<div id="graph"></div>
|
|
129
|
+
<div id="sidebar">
|
|
130
|
+
<h2>Mesh Network</h2>
|
|
131
|
+
<div id="stats">
|
|
132
|
+
<div class="stat-item">
|
|
133
|
+
<span>Total Nodes:</span>
|
|
134
|
+
<span id="stat-total">0</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="stat-item">
|
|
137
|
+
<span>Online:</span>
|
|
138
|
+
<span id="stat-online">0</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="stat-item">
|
|
141
|
+
<span>Avg Quality:</span>
|
|
142
|
+
<span id="stat-quality">0%</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="stat-item">
|
|
145
|
+
<span>Network Diameter:</span>
|
|
146
|
+
<span id="stat-diameter">0</span>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<h3>Recent Events</h3>
|
|
150
|
+
<div id="event-log"></div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<script>
|
|
155
|
+
// Configuration
|
|
156
|
+
const MQTT_BROKER = 'ws://localhost:9001'; // WebSocket port
|
|
157
|
+
const MESH_ID = 'MESH-001';
|
|
158
|
+
|
|
159
|
+
// D3 Setup
|
|
160
|
+
const width = window.innerWidth - 300;
|
|
161
|
+
const height = window.innerHeight;
|
|
162
|
+
|
|
163
|
+
const svg = d3.select('#graph')
|
|
164
|
+
.append('svg')
|
|
165
|
+
.attr('width', width)
|
|
166
|
+
.attr('height', height);
|
|
167
|
+
|
|
168
|
+
const g = svg.append('g');
|
|
169
|
+
|
|
170
|
+
// Zoom behavior
|
|
171
|
+
svg.call(d3.zoom()
|
|
172
|
+
.extent([[0, 0], [width, height]])
|
|
173
|
+
.scaleExtent([0.1, 8])
|
|
174
|
+
.on('zoom', (event) => g.attr('transform', event.transform)));
|
|
175
|
+
|
|
176
|
+
// Force simulation
|
|
177
|
+
const simulation = d3.forceSimulation()
|
|
178
|
+
.force('link', d3.forceLink().id(d => d.id).distance(150))
|
|
179
|
+
.force('charge', d3.forceManyBody().strength(-400))
|
|
180
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
181
|
+
.force('collision', d3.forceCollide().radius(50));
|
|
182
|
+
|
|
183
|
+
let link = g.append('g').selectAll('line');
|
|
184
|
+
let node = g.append('g').selectAll('g');
|
|
185
|
+
let label = g.append('g').selectAll('text');
|
|
186
|
+
|
|
187
|
+
// MQTT Connection
|
|
188
|
+
const client = mqtt.connect(MQTT_BROKER);
|
|
189
|
+
|
|
190
|
+
client.on('connect', () => {
|
|
191
|
+
console.log('✅ Connected to MQTT');
|
|
192
|
+
client.subscribe(`alteriom/mesh/${MESH_ID}/topology`);
|
|
193
|
+
client.subscribe(`alteriom/mesh/${MESH_ID}/events`);
|
|
194
|
+
addEvent('Connected to MQTT broker', 'join');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
client.on('message', (topic, message) => {
|
|
198
|
+
const data = JSON.parse(message.toString());
|
|
199
|
+
|
|
200
|
+
if (data.event === 'mesh_topology') {
|
|
201
|
+
updateGraph(data);
|
|
202
|
+
} else if (data.event === 'mesh_event') {
|
|
203
|
+
handleEvent(data);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
function updateGraph(topology) {
|
|
208
|
+
// Update statistics
|
|
209
|
+
if (topology.metrics) {
|
|
210
|
+
document.getElementById('stat-total').textContent = topology.metrics.total_nodes;
|
|
211
|
+
document.getElementById('stat-online').textContent = topology.metrics.online_nodes;
|
|
212
|
+
document.getElementById('stat-quality').textContent =
|
|
213
|
+
topology.metrics.avg_connection_quality + '%';
|
|
214
|
+
document.getElementById('stat-diameter').textContent = topology.metrics.network_diameter;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Prepare nodes and links
|
|
218
|
+
const nodes = topology.nodes.map(n => ({
|
|
219
|
+
id: n.node_id,
|
|
220
|
+
role: n.role,
|
|
221
|
+
status: n.status,
|
|
222
|
+
memory: n.free_memory_kb,
|
|
223
|
+
connections: n.connection_count
|
|
224
|
+
}));
|
|
225
|
+
|
|
226
|
+
const links = topology.connections.map(c => ({
|
|
227
|
+
source: c.from_node,
|
|
228
|
+
target: c.to_node,
|
|
229
|
+
quality: c.quality,
|
|
230
|
+
rssi: c.rssi,
|
|
231
|
+
latency: c.latency_ms
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
// Update D3 visualization
|
|
235
|
+
link = link.data(links, d => `${d.source}-${d.target}`);
|
|
236
|
+
link.exit().remove();
|
|
237
|
+
link = link.enter()
|
|
238
|
+
.append('line')
|
|
239
|
+
.attr('class', d => {
|
|
240
|
+
if (d.quality > 80) return 'link link-excellent';
|
|
241
|
+
if (d.quality > 50) return 'link link-good';
|
|
242
|
+
return 'link link-poor';
|
|
243
|
+
})
|
|
244
|
+
.attr('stroke-width', d => d.quality / 20)
|
|
245
|
+
.merge(link);
|
|
246
|
+
|
|
247
|
+
node = node.data(nodes, d => d.id);
|
|
248
|
+
node.exit().remove();
|
|
249
|
+
|
|
250
|
+
const nodeEnter = node.enter()
|
|
251
|
+
.append('g')
|
|
252
|
+
.attr('class', 'node')
|
|
253
|
+
.call(d3.drag()
|
|
254
|
+
.on('start', dragstarted)
|
|
255
|
+
.on('drag', dragged)
|
|
256
|
+
.on('end', dragended));
|
|
257
|
+
|
|
258
|
+
nodeEnter.append('circle')
|
|
259
|
+
.attr('r', 20)
|
|
260
|
+
.attr('class', d => {
|
|
261
|
+
if (d.status === 'offline') return 'node-offline';
|
|
262
|
+
return `node-${d.role}`;
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
nodeEnter.append('title')
|
|
266
|
+
.text(d => `${d.id}\nRole: ${d.role}\nMemory: ${d.memory}KB`);
|
|
267
|
+
|
|
268
|
+
node = nodeEnter.merge(node);
|
|
269
|
+
|
|
270
|
+
// Update labels
|
|
271
|
+
label = label.data(nodes, d => d.id);
|
|
272
|
+
label.exit().remove();
|
|
273
|
+
label = label.enter()
|
|
274
|
+
.append('text')
|
|
275
|
+
.attr('class', 'node-label')
|
|
276
|
+
.attr('text-anchor', 'middle')
|
|
277
|
+
.attr('dy', 35)
|
|
278
|
+
.text(d => d.id.substring(4, 10))
|
|
279
|
+
.merge(label);
|
|
280
|
+
|
|
281
|
+
// Restart simulation
|
|
282
|
+
simulation.nodes(nodes);
|
|
283
|
+
simulation.force('link').links(links);
|
|
284
|
+
simulation.alpha(1).restart();
|
|
285
|
+
|
|
286
|
+
simulation.on('tick', () => {
|
|
287
|
+
link
|
|
288
|
+
.attr('x1', d => d.source.x)
|
|
289
|
+
.attr('y1', d => d.source.y)
|
|
290
|
+
.attr('x2', d => d.target.x)
|
|
291
|
+
.attr('y2', d => d.target.y);
|
|
292
|
+
|
|
293
|
+
node.attr('transform', d => `translate(${d.x},${d.y})`);
|
|
294
|
+
label.attr('x', d => d.x).attr('y', d => d.y);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function handleEvent(event) {
|
|
299
|
+
const type = event.event_type;
|
|
300
|
+
const nodes = event.affected_nodes.join(', ');
|
|
301
|
+
let message = '';
|
|
302
|
+
|
|
303
|
+
switch(type) {
|
|
304
|
+
case 'node_join':
|
|
305
|
+
message = `Node ${nodes} joined mesh`;
|
|
306
|
+
break;
|
|
307
|
+
case 'node_leave':
|
|
308
|
+
message = `Node ${nodes} left mesh`;
|
|
309
|
+
break;
|
|
310
|
+
default:
|
|
311
|
+
message = `Event: ${type}`;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
addEvent(message, type);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function addEvent(message, type = '') {
|
|
318
|
+
const log = document.getElementById('event-log');
|
|
319
|
+
const event = document.createElement('div');
|
|
320
|
+
event.className = `event event-${type}`;
|
|
321
|
+
event.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
|
322
|
+
log.insertBefore(event, log.firstChild);
|
|
323
|
+
|
|
324
|
+
// Keep only last 20 events
|
|
325
|
+
while (log.children.length > 20) {
|
|
326
|
+
log.removeChild(log.lastChild);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function dragstarted(event, d) {
|
|
331
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
332
|
+
d.fx = d.x;
|
|
333
|
+
d.fy = d.y;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function dragged(event, d) {
|
|
337
|
+
d.fx = event.x;
|
|
338
|
+
d.fy = event.y;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function dragended(event, d) {
|
|
342
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
343
|
+
d.fx = null;
|
|
344
|
+
d.fy = null;
|
|
345
|
+
}
|
|
346
|
+
</script>
|
|
347
|
+
</body>
|
|
348
|
+
</html>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Usage
|
|
352
|
+
|
|
353
|
+
1. **Start MQTT broker with WebSocket support:**
|
|
354
|
+
```bash
|
|
355
|
+
# Mosquitto with WebSocket on port 9001
|
|
356
|
+
mosquitto -c mosquitto.conf
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
2. **Configure mosquitto.conf:**
|
|
360
|
+
```
|
|
361
|
+
listener 1883
|
|
362
|
+
protocol mqtt
|
|
363
|
+
|
|
364
|
+
listener 9001
|
|
365
|
+
protocol websockets
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
3. **Open in browser:**
|
|
369
|
+
```bash
|
|
370
|
+
# Simply open the HTML file
|
|
371
|
+
open mesh-viewer-d3.html
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Cytoscape.js Network View
|
|
377
|
+
|
|
378
|
+
### Complete HTML Example
|
|
379
|
+
|
|
380
|
+
Save as `mesh-viewer-cytoscape.html`:
|
|
381
|
+
|
|
382
|
+
```html
|
|
383
|
+
<!DOCTYPE html>
|
|
384
|
+
<html>
|
|
385
|
+
<head>
|
|
386
|
+
<title>Alteriom Mesh - Cytoscape</title>
|
|
387
|
+
<script src="https://unpkg.com/cytoscape/dist/cytoscape.min.js"></script>
|
|
388
|
+
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
|
|
389
|
+
<style>
|
|
390
|
+
body {
|
|
391
|
+
margin: 0;
|
|
392
|
+
font-family: Arial, sans-serif;
|
|
393
|
+
}
|
|
394
|
+
#cy {
|
|
395
|
+
width: 100%;
|
|
396
|
+
height: 100vh;
|
|
397
|
+
background: #1a1a2e;
|
|
398
|
+
}
|
|
399
|
+
#controls {
|
|
400
|
+
position: absolute;
|
|
401
|
+
top: 20px;
|
|
402
|
+
right: 20px;
|
|
403
|
+
background: rgba(15, 52, 96, 0.9);
|
|
404
|
+
padding: 15px;
|
|
405
|
+
border-radius: 8px;
|
|
406
|
+
color: white;
|
|
407
|
+
min-width: 200px;
|
|
408
|
+
}
|
|
409
|
+
button {
|
|
410
|
+
width: 100%;
|
|
411
|
+
padding: 10px;
|
|
412
|
+
margin: 5px 0;
|
|
413
|
+
background: #00d4ff;
|
|
414
|
+
border: none;
|
|
415
|
+
border-radius: 5px;
|
|
416
|
+
color: #1a1a2e;
|
|
417
|
+
cursor: pointer;
|
|
418
|
+
font-weight: bold;
|
|
419
|
+
}
|
|
420
|
+
button:hover {
|
|
421
|
+
background: #00a8cc;
|
|
422
|
+
}
|
|
423
|
+
</style>
|
|
424
|
+
</head>
|
|
425
|
+
<body>
|
|
426
|
+
<div id="cy"></div>
|
|
427
|
+
<div id="controls">
|
|
428
|
+
<h3>Controls</h3>
|
|
429
|
+
<button onclick="cy.fit()">Fit to Screen</button>
|
|
430
|
+
<button onclick="cy.layout({name: 'cose'}).run()">Re-layout</button>
|
|
431
|
+
<button onclick="highlightGateway()">Highlight Gateway</button>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<script>
|
|
435
|
+
const cy = cytoscape({
|
|
436
|
+
container: document.getElementById('cy'),
|
|
437
|
+
style: [
|
|
438
|
+
{
|
|
439
|
+
selector: 'node',
|
|
440
|
+
style: {
|
|
441
|
+
'background-color': '#00d4ff',
|
|
442
|
+
'label': 'data(label)',
|
|
443
|
+
'width': 40,
|
|
444
|
+
'height': 40,
|
|
445
|
+
'font-size': 10,
|
|
446
|
+
'color': '#fff',
|
|
447
|
+
'text-valign': 'bottom',
|
|
448
|
+
'text-halign': 'center',
|
|
449
|
+
'text-margin-y': 5
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
selector: 'node[role="gateway"]',
|
|
454
|
+
style: {
|
|
455
|
+
'background-color': '#00d4ff',
|
|
456
|
+
'width': 60,
|
|
457
|
+
'height': 60,
|
|
458
|
+
'border-width': 3,
|
|
459
|
+
'border-color': '#fff'
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
selector: 'node[role="sensor"]',
|
|
464
|
+
style: {
|
|
465
|
+
'background-color': '#4ecca3'
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
selector: 'node[role="repeater"]',
|
|
470
|
+
style: {
|
|
471
|
+
'background-color': '#f39c12'
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
selector: 'node[status="offline"]',
|
|
476
|
+
style: {
|
|
477
|
+
'background-color': '#e74c3c',
|
|
478
|
+
'opacity': 0.5
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
selector: 'edge',
|
|
483
|
+
style: {
|
|
484
|
+
'width': 'data(width)',
|
|
485
|
+
'line-color': 'data(color)',
|
|
486
|
+
'target-arrow-shape': 'triangle',
|
|
487
|
+
'target-arrow-color': 'data(color)',
|
|
488
|
+
'curve-style': 'bezier'
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
],
|
|
492
|
+
layout: {
|
|
493
|
+
name: 'cose',
|
|
494
|
+
animate: true,
|
|
495
|
+
animationDuration: 1000
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const client = mqtt.connect('ws://localhost:9001');
|
|
500
|
+
|
|
501
|
+
client.on('connect', () => {
|
|
502
|
+
console.log('Connected to MQTT');
|
|
503
|
+
client.subscribe('alteriom/mesh/+/topology');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
client.on('message', (topic, message) => {
|
|
507
|
+
const data = JSON.parse(message.toString());
|
|
508
|
+
if (data.event === 'mesh_topology') {
|
|
509
|
+
updateCytoscape(data);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
function updateCytoscape(topology) {
|
|
514
|
+
cy.elements().remove();
|
|
515
|
+
|
|
516
|
+
// Add nodes
|
|
517
|
+
topology.nodes.forEach(n => {
|
|
518
|
+
cy.add({
|
|
519
|
+
group: 'nodes',
|
|
520
|
+
data: {
|
|
521
|
+
id: n.node_id,
|
|
522
|
+
label: n.node_id.substring(4, 10),
|
|
523
|
+
role: n.role,
|
|
524
|
+
status: n.status,
|
|
525
|
+
memory: n.free_memory_kb
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// Add edges
|
|
531
|
+
topology.connections.forEach(c => {
|
|
532
|
+
const color = c.quality > 80 ? '#4ecca3' :
|
|
533
|
+
c.quality > 50 ? '#f39c12' : '#e74c3c';
|
|
534
|
+
const width = Math.max(1, c.quality / 20);
|
|
535
|
+
|
|
536
|
+
cy.add({
|
|
537
|
+
group: 'edges',
|
|
538
|
+
data: {
|
|
539
|
+
source: c.from_node,
|
|
540
|
+
target: c.to_node,
|
|
541
|
+
width: width,
|
|
542
|
+
color: color,
|
|
543
|
+
quality: c.quality
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
cy.layout({name: 'cose', animate: true}).run();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function highlightGateway() {
|
|
552
|
+
const gateway = cy.nodes('[role="gateway"]');
|
|
553
|
+
cy.fit(gateway, 50);
|
|
554
|
+
gateway.flashClass('highlighted', 2000);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Node click handler
|
|
558
|
+
cy.on('tap', 'node', function(evt) {
|
|
559
|
+
const node = evt.target;
|
|
560
|
+
alert(`Node: ${node.data('id')}\nRole: ${node.data('role')}\nMemory: ${node.data('memory')}KB`);
|
|
561
|
+
});
|
|
562
|
+
</script>
|
|
563
|
+
</body>
|
|
564
|
+
</html>
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## Real-Time Dashboard
|
|
570
|
+
|
|
571
|
+
### Node.js + Express + Socket.IO
|
|
572
|
+
|
|
573
|
+
**server.js:**
|
|
574
|
+
|
|
575
|
+
```javascript
|
|
576
|
+
const express = require('express');
|
|
577
|
+
const http = require('http');
|
|
578
|
+
const socketIO = require('socket.io');
|
|
579
|
+
const mqtt = require('mqtt');
|
|
580
|
+
|
|
581
|
+
const app = express();
|
|
582
|
+
const server = http.createServer(app);
|
|
583
|
+
const io = socketIO(server);
|
|
584
|
+
|
|
585
|
+
// MQTT Configuration
|
|
586
|
+
const mqttClient = mqtt.connect('mqtt://localhost:1883');
|
|
587
|
+
|
|
588
|
+
mqttClient.on('connect', () => {
|
|
589
|
+
console.log('✅ Connected to MQTT broker');
|
|
590
|
+
mqttClient.subscribe('alteriom/mesh/+/topology');
|
|
591
|
+
mqttClient.subscribe('alteriom/mesh/+/events');
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
mqttClient.on('message', (topic, message) => {
|
|
595
|
+
const data = JSON.parse(message.toString());
|
|
596
|
+
|
|
597
|
+
// Forward to all connected web clients
|
|
598
|
+
io.emit('mesh-data', {
|
|
599
|
+
topic: topic,
|
|
600
|
+
data: data
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
app.use(express.static('public'));
|
|
605
|
+
|
|
606
|
+
io.on('connection', (socket) => {
|
|
607
|
+
console.log('Client connected');
|
|
608
|
+
|
|
609
|
+
socket.on('disconnect', () => {
|
|
610
|
+
console.log('Client disconnected');
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const PORT = process.env.PORT || 3000;
|
|
615
|
+
server.listen(PORT, () => {
|
|
616
|
+
console.log(`🚀 Dashboard running on http://localhost:${PORT}`);
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**public/index.html:**
|
|
621
|
+
|
|
622
|
+
```html
|
|
623
|
+
<!DOCTYPE html>
|
|
624
|
+
<html>
|
|
625
|
+
<head>
|
|
626
|
+
<title>Mesh Dashboard</title>
|
|
627
|
+
<script src="/socket.io/socket.io.js"></script>
|
|
628
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
629
|
+
</head>
|
|
630
|
+
<body>
|
|
631
|
+
<h1>Mesh Network Dashboard</h1>
|
|
632
|
+
<div id="stats"></div>
|
|
633
|
+
<canvas id="qualityChart" width="400" height="200"></canvas>
|
|
634
|
+
|
|
635
|
+
<script>
|
|
636
|
+
const socket = io();
|
|
637
|
+
const ctx = document.getElementById('qualityChart').getContext('2d');
|
|
638
|
+
const chart = new Chart(ctx, {
|
|
639
|
+
type: 'line',
|
|
640
|
+
data: {
|
|
641
|
+
labels: [],
|
|
642
|
+
datasets: [{
|
|
643
|
+
label: 'Average Quality',
|
|
644
|
+
data: [],
|
|
645
|
+
borderColor: '#4ecca3',
|
|
646
|
+
tension: 0.1
|
|
647
|
+
}]
|
|
648
|
+
},
|
|
649
|
+
options: {
|
|
650
|
+
scales: {
|
|
651
|
+
y: {
|
|
652
|
+
beginAtZero: true,
|
|
653
|
+
max: 100
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
socket.on('mesh-data', (message) => {
|
|
660
|
+
if (message.data.event === 'mesh_topology') {
|
|
661
|
+
updateDashboard(message.data);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
function updateDashboard(topology) {
|
|
666
|
+
document.getElementById('stats').innerHTML = `
|
|
667
|
+
<p>Nodes: ${topology.metrics.total_nodes}</p>
|
|
668
|
+
<p>Quality: ${topology.metrics.avg_connection_quality}%</p>
|
|
669
|
+
`;
|
|
670
|
+
|
|
671
|
+
// Update chart
|
|
672
|
+
const now = new Date().toLocaleTimeString();
|
|
673
|
+
chart.data.labels.push(now);
|
|
674
|
+
chart.data.datasets[0].data.push(topology.metrics.avg_connection_quality);
|
|
675
|
+
|
|
676
|
+
if (chart.data.labels.length > 20) {
|
|
677
|
+
chart.data.labels.shift();
|
|
678
|
+
chart.data.datasets[0].data.shift();
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
chart.update();
|
|
682
|
+
}
|
|
683
|
+
</script>
|
|
684
|
+
</body>
|
|
685
|
+
</html>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**package.json:**
|
|
689
|
+
|
|
690
|
+
```json
|
|
691
|
+
{
|
|
692
|
+
"name": "mesh-dashboard",
|
|
693
|
+
"version": "1.0.0",
|
|
694
|
+
"dependencies": {
|
|
695
|
+
"express": "^4.18.0",
|
|
696
|
+
"socket.io": "^4.5.0",
|
|
697
|
+
"mqtt": "^4.3.0"
|
|
698
|
+
},
|
|
699
|
+
"scripts": {
|
|
700
|
+
"start": "node server.js"
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
**Run:**
|
|
706
|
+
|
|
707
|
+
```bash
|
|
708
|
+
npm install
|
|
709
|
+
npm start
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Python Monitoring
|
|
715
|
+
|
|
716
|
+
### Simple Console Monitor
|
|
717
|
+
|
|
718
|
+
```python
|
|
719
|
+
#!/usr/bin/env python3
|
|
720
|
+
"""
|
|
721
|
+
Alteriom Mesh Topology Monitor
|
|
722
|
+
Displays mesh network status in terminal
|
|
723
|
+
"""
|
|
724
|
+
|
|
725
|
+
import paho.mqtt.client as mqtt
|
|
726
|
+
import json
|
|
727
|
+
from datetime import datetime
|
|
728
|
+
from rich.console import Console
|
|
729
|
+
from rich.table import Table
|
|
730
|
+
from rich.live import Live
|
|
731
|
+
|
|
732
|
+
console = Console()
|
|
733
|
+
|
|
734
|
+
# Configuration
|
|
735
|
+
BROKER = "localhost"
|
|
736
|
+
PORT = 1883
|
|
737
|
+
MESH_ID = "MESH-001"
|
|
738
|
+
|
|
739
|
+
topology_data = None
|
|
740
|
+
|
|
741
|
+
def on_connect(client, userdata, flags, rc):
|
|
742
|
+
console.print(f"[green]✅ Connected to MQTT broker[/green]")
|
|
743
|
+
client.subscribe(f"alteriom/mesh/{MESH_ID}/topology")
|
|
744
|
+
client.subscribe(f"alteriom/mesh/{MESH_ID}/events")
|
|
745
|
+
|
|
746
|
+
def on_message(client, userdata, msg):
|
|
747
|
+
global topology_data
|
|
748
|
+
data = json.loads(msg.payload.decode())
|
|
749
|
+
|
|
750
|
+
if data.get('event') == 'mesh_topology':
|
|
751
|
+
topology_data = data
|
|
752
|
+
elif data.get('event') == 'mesh_event':
|
|
753
|
+
event_type = data.get('event_type')
|
|
754
|
+
nodes = ', '.join(data.get('affected_nodes', []))
|
|
755
|
+
console.print(f"[yellow]🔔 Event: {event_type} - {nodes}[/yellow]")
|
|
756
|
+
|
|
757
|
+
def create_table():
|
|
758
|
+
if not topology_data:
|
|
759
|
+
return Table(title="Waiting for topology data...")
|
|
760
|
+
|
|
761
|
+
table = Table(title=f"Mesh Network - {datetime.now().strftime('%H:%M:%S')}")
|
|
762
|
+
|
|
763
|
+
table.add_column("Node ID", style="cyan")
|
|
764
|
+
table.add_column("Role", style="magenta")
|
|
765
|
+
table.add_column("Status", style="green")
|
|
766
|
+
table.add_column("Memory", justify="right")
|
|
767
|
+
table.add_column("Connections", justify="right")
|
|
768
|
+
|
|
769
|
+
for node in topology_data.get('nodes', []):
|
|
770
|
+
status_color = "green" if node['status'] == 'online' else "red"
|
|
771
|
+
table.add_row(
|
|
772
|
+
node['node_id'][-12:],
|
|
773
|
+
node['role'],
|
|
774
|
+
f"[{status_color}]{node['status']}[/{status_color}]",
|
|
775
|
+
f"{node.get('free_memory_kb', 0)} KB",
|
|
776
|
+
str(node.get('connection_count', 0))
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
# Add metrics
|
|
780
|
+
metrics = topology_data.get('metrics', {})
|
|
781
|
+
table.add_section()
|
|
782
|
+
table.add_row(
|
|
783
|
+
"[bold]METRICS[/bold]",
|
|
784
|
+
f"Quality: {metrics.get('avg_connection_quality', 0)}%",
|
|
785
|
+
f"Diameter: {metrics.get('network_diameter', 0)}",
|
|
786
|
+
f"Total: {metrics.get('total_nodes', 0)}",
|
|
787
|
+
f"Online: {metrics.get('online_nodes', 0)}"
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
return table
|
|
791
|
+
|
|
792
|
+
def main():
|
|
793
|
+
client = mqtt.Client()
|
|
794
|
+
client.on_connect = on_connect
|
|
795
|
+
client.on_message = on_message
|
|
796
|
+
|
|
797
|
+
client.connect(BROKER, PORT, 60)
|
|
798
|
+
client.loop_start()
|
|
799
|
+
|
|
800
|
+
try:
|
|
801
|
+
with Live(create_table(), refresh_per_second=1) as live:
|
|
802
|
+
while True:
|
|
803
|
+
live.update(create_table())
|
|
804
|
+
except KeyboardInterrupt:
|
|
805
|
+
console.print("\n[red]Disconnecting...[/red]")
|
|
806
|
+
client.loop_stop()
|
|
807
|
+
client.disconnect()
|
|
808
|
+
|
|
809
|
+
if __name__ == "__main__":
|
|
810
|
+
main()
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
**Install dependencies:**
|
|
814
|
+
|
|
815
|
+
```bash
|
|
816
|
+
pip install paho-mqtt rich
|
|
817
|
+
python mesh_monitor.py
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
---
|
|
821
|
+
|
|
822
|
+
## Node-RED Flows
|
|
823
|
+
|
|
824
|
+
### Import this flow into Node-RED:
|
|
825
|
+
|
|
826
|
+
```json
|
|
827
|
+
[
|
|
828
|
+
{
|
|
829
|
+
"id": "mqtt-in",
|
|
830
|
+
"type": "mqtt in",
|
|
831
|
+
"broker": "mqtt-broker",
|
|
832
|
+
"topic": "alteriom/mesh/+/topology",
|
|
833
|
+
"name": "Mesh Topology"
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
"id": "parse-json",
|
|
837
|
+
"type": "json",
|
|
838
|
+
"name": "Parse JSON"
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
"id": "extract-metrics",
|
|
842
|
+
"type": "function",
|
|
843
|
+
"name": "Extract Metrics",
|
|
844
|
+
"func": "msg.payload = {\n total_nodes: msg.payload.metrics.total_nodes,\n avg_quality: msg.payload.metrics.avg_connection_quality,\n online: msg.payload.metrics.online_nodes\n};\nreturn msg;"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
"id": "dashboard-gauge",
|
|
848
|
+
"type": "ui_gauge",
|
|
849
|
+
"group": "mesh-stats",
|
|
850
|
+
"name": "Avg Quality",
|
|
851
|
+
"min": 0,
|
|
852
|
+
"max": 100
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
"id": "mqtt-broker",
|
|
856
|
+
"type": "mqtt-broker",
|
|
857
|
+
"broker": "localhost",
|
|
858
|
+
"port": "1883"
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## Troubleshooting
|
|
866
|
+
|
|
867
|
+
### Problem: No topology messages appearing
|
|
868
|
+
|
|
869
|
+
**Symptoms:**
|
|
870
|
+
- Empty graph
|
|
871
|
+
- No MQTT messages received
|
|
872
|
+
- Console shows "Waiting for data..."
|
|
873
|
+
|
|
874
|
+
**Solutions:**
|
|
875
|
+
|
|
876
|
+
1. **Check MQTT broker:**
|
|
877
|
+
```bash
|
|
878
|
+
mosquitto_sub -h localhost -t "alteriom/mesh/#" -v
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
2. **Verify WebSocket port:**
|
|
882
|
+
```bash
|
|
883
|
+
# Check if port 9001 is open
|
|
884
|
+
netstat -an | grep 9001
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
3. **Check browser console:**
|
|
888
|
+
- Open DevTools (F12)
|
|
889
|
+
- Look for connection errors
|
|
890
|
+
- Verify MQTT connection message
|
|
891
|
+
|
|
892
|
+
4. **Test with mosquitto_pub:**
|
|
893
|
+
```bash
|
|
894
|
+
mosquitto_pub -h localhost -t "alteriom/mesh/MESH-001/topology" -m '{"event":"mesh_topology","nodes":[]}'
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
### Problem: Nodes not appearing in graph
|
|
898
|
+
|
|
899
|
+
**Check:**
|
|
900
|
+
- Node ID format is `ALT-XXXXXXXXXXXX`
|
|
901
|
+
- `nodes` array is not empty
|
|
902
|
+
- `status` field is "online"
|
|
903
|
+
- JSON is valid
|
|
904
|
+
|
|
905
|
+
### Problem: Quality metrics showing 0
|
|
906
|
+
|
|
907
|
+
**Solutions:**
|
|
908
|
+
- Verify `quality` field in connections (0-100)
|
|
909
|
+
- Check `rssi` is negative (-30 to -90)
|
|
910
|
+
- Ensure `latency_ms` is positive
|
|
911
|
+
|
|
912
|
+
### Problem: Events not publishing
|
|
913
|
+
|
|
914
|
+
**Check:**
|
|
915
|
+
- Subscribed to correct topic: `alteriom/mesh/+/events`
|
|
916
|
+
- Event type is one of: `node_join`, `node_leave`, `node_timeout`
|
|
917
|
+
- `affected_nodes` array contains valid node IDs
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## Performance Considerations
|
|
922
|
+
|
|
923
|
+
### Message Frequency
|
|
924
|
+
|
|
925
|
+
**Recommendations:**
|
|
926
|
+
- Full topology: Every 60 seconds
|
|
927
|
+
- Incremental updates: Every 5 seconds
|
|
928
|
+
- Events: Immediate (on change)
|
|
929
|
+
|
|
930
|
+
**For large meshes (50+ nodes):**
|
|
931
|
+
```javascript
|
|
932
|
+
// Throttle updates
|
|
933
|
+
let lastUpdate = 0;
|
|
934
|
+
const UPDATE_INTERVAL = 2000; // 2 seconds
|
|
935
|
+
|
|
936
|
+
client.on('message', (topic, message) => {
|
|
937
|
+
const now = Date.now();
|
|
938
|
+
if (now - lastUpdate < UPDATE_INTERVAL) return;
|
|
939
|
+
lastUpdate = now;
|
|
940
|
+
|
|
941
|
+
updateGraph(JSON.parse(message));
|
|
942
|
+
});
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
### Buffer Sizes
|
|
946
|
+
|
|
947
|
+
**MQTT Client:**
|
|
948
|
+
```javascript
|
|
949
|
+
const client = mqtt.connect('ws://localhost:9001', {
|
|
950
|
+
clientId: 'mesh-viewer-' + Math.random(),
|
|
951
|
+
clean: true,
|
|
952
|
+
reconnectPeriod: 1000,
|
|
953
|
+
keepalive: 60
|
|
954
|
+
});
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
**For ESP32:**
|
|
958
|
+
```cpp
|
|
959
|
+
mqttClient.setBufferSize(2048); // Increase for large topologies
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
### QoS Levels
|
|
963
|
+
|
|
964
|
+
**Recommended:**
|
|
965
|
+
- Topology messages: QoS 0 (fire and forget)
|
|
966
|
+
- Events: QoS 1 (at least once)
|
|
967
|
+
- Commands: QoS 1 (at least once)
|
|
968
|
+
|
|
969
|
+
### Network Scaling
|
|
970
|
+
|
|
971
|
+
| Nodes | Update Frequency | Buffer Size | Notes |
|
|
972
|
+
|-------|-----------------|-------------|-------|
|
|
973
|
+
| 1-10 | 1s | 1KB | Default |
|
|
974
|
+
| 10-30 | 5s | 2KB | Recommended |
|
|
975
|
+
| 30-50 | 10s | 4KB | Increase buffer |
|
|
976
|
+
| 50+ | 30s | 8KB | Consider incremental only |
|
|
977
|
+
|
|
978
|
+
---
|
|
979
|
+
|
|
980
|
+
## Additional Resources
|
|
981
|
+
|
|
982
|
+
- [MQTT Schema Review](./MQTT_SCHEMA_REVIEW.md)
|
|
983
|
+
- [Hardware Test Sketch](../examples/mqttTopologyTest/)
|
|
984
|
+
- [D3.js Documentation](https://d3js.org/)
|
|
985
|
+
- [Cytoscape.js Documentation](https://js.cytoscape.org/)
|
|
986
|
+
- [@alteriom/mqtt-schema Package](https://www.npmjs.com/package/@alteriom/mqtt-schema)
|
|
987
|
+
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
**Created by:** Alteriom Development Team
|
|
991
|
+
**Last Updated:** October 14, 2025
|
|
992
|
+
**License:** MIT
|