Skip to content

Commit 1fdab91

Browse files
committed
TelemetryDashboard: allow for heartbeat send and signing
and add cli_test.js
1 parent 7d9cdc9 commit 1fdab91

File tree

3 files changed

+206
-14
lines changed

3 files changed

+206
-14
lines changed

TelemetryDashboard/TelemetryDashboard.js

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ function setup_connect(button_svg, button_color) {
4343

4444
const url_input = tip_div.querySelector(`input[id="target_url"]`)
4545

46+
const heartbeat_checkbox = tip_div.querySelector(`input[id="send_heartbeat"]`)
47+
const passphrase_input = tip_div.querySelector(`input[id="signing_passphrase"]`)
48+
4649
const connect_button = tip_div.querySelector(`input[id="connection_button"]`)
4750
const disconnect_button = tip_div.querySelector(`input[id="disconnection_button"]`)
4851

52+
const system_id_input = tip_div.querySelector(`input[id="system_id"]`);
53+
const component_id_input = tip_div.querySelector(`input[id="component_id"]`);
54+
4955
// Websocket object
5056
let ws = null
5157
let expecting_close = false
@@ -60,6 +66,17 @@ function setup_connect(button_svg, button_color) {
6066
}
6167
set_inputs(false)
6268

69+
function apply_ids() {
70+
// clamp to valid ranges; fall back to GCS-like defaults 255/190
71+
let sid = parseInt(system_id_input?.value || "255", 10);
72+
let cid = parseInt(component_id_input?.value || "190", 10);
73+
if (!Number.isFinite(sid) || sid < 1 || sid > 255) sid = 255;
74+
if (!Number.isFinite(cid) || cid < 0 || cid > 255) cid = 190;
75+
76+
MAVLink.srcSystem = sid;
77+
MAVLink.srcComponent = cid;
78+
}
79+
6380
// Connect to WebSocket server
6481
function connect(target, auto_connect) {
6582
// Make sure we are not connected to something else
@@ -82,6 +99,9 @@ function setup_connect(button_svg, button_color) {
8299
ws.onopen = () => {
83100
button_color("green")
84101

102+
// setup system IDs
103+
apply_ids();
104+
85105
// Hide tip
86106
tip.hide()
87107

@@ -92,8 +112,47 @@ function setup_connect(button_svg, button_color) {
92112
url_input.value = target
93113

94114
// Have been connected
95-
been_connected = true
96-
}
115+
been_connected = true;
116+
setup_passphase = false;
117+
118+
heartbeat_interval = setInterval(() => {
119+
try {
120+
if (!setup_passphase) {
121+
const passphrase = passphrase_input.value.trim();
122+
if (passphrase.length > 0) {
123+
setup_passphase = true;
124+
const enc = new TextEncoder();
125+
const data = enc.encode(passphrase);
126+
const hash = mavlink20.sha256(data);
127+
MAVLink.signing.secret_key = new Uint8Array(hash);
128+
MAVLink.signing.sign_outgoing = true;
129+
}
130+
}
131+
132+
if (heartbeat_checkbox.checked) {
133+
const msg = new mavlink20.messages.heartbeat(
134+
6, // MAV_TYPE_GCS
135+
8, // MAV_AUTOPILOT_INVALID
136+
0, // base_mode
137+
0, // custom_mode
138+
4 // MAV_STATE_ACTIVE
139+
);
140+
const pkt = msg.pack(MAVLink);
141+
ws.send(Uint8Array.from(pkt));
142+
//console.log("Sent HEARTBEAT", pkt);
143+
}
144+
} catch (e) {
145+
console.error("Error sending HEARTBEAT:", e.message);
146+
console.error(e.stack);
147+
148+
if (heartbeat_interval !== null) {
149+
clearInterval(heartbeat_interval)
150+
heartbeat_interval = null
151+
console.warn("Heartbeat disabled; will retry on reconnect.")
152+
}
153+
}
154+
}, 1000);
155+
}
97156

98157
ws.onclose = () => {
99158
if ((auto_connect === true) && !been_connected) {
@@ -115,10 +174,15 @@ function setup_connect(button_svg, button_color) {
115174
}
116175

117176
ws.onmessage = (msg) => {
118-
// Feed data to MAVLink parser and forward messages
119-
for (const char of new Uint8Array(msg.data)) {
120-
const m = MAVLink.parseChar(char)
121-
if ((m != null) && (m._id != -1)) {
177+
// Feed data to MAVLink parser and forward messages
178+
MAVLink.pushBuffer(new Uint8Array(msg.data));
179+
while (true) {
180+
const m = MAVLink.parseChar(null);
181+
if (m === null) {
182+
break;
183+
}
184+
if (m._id != -1) {
185+
//console.log(m);
122186
// Got message with known ID
123187
// Sent to each Widget
124188
for (const widget of grid.getGridItems()) {
@@ -130,7 +194,6 @@ function setup_connect(button_svg, button_color) {
130194
}
131195
}
132196
}
133-
134197
}
135198

136199
// Disconnect from WebSocket server

TelemetryDashboard/cli_test.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//!/usr/bin/env node
2+
3+
const WebSocket = require('ws');
4+
5+
global.jspack = new (require('./MAVLink/local_modules/jspack/jspack.js')).default;
6+
7+
const mavlib = require('./MAVLink/mavlink.js'); // Use local MAVLink definition
8+
9+
if (process.argv.length < 3) {
10+
console.error("Usage: node ws_mavlink.js <WebSocket URL>");
11+
process.exit(1);
12+
}
13+
14+
//process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
15+
16+
const url = process.argv[2];
17+
const ws = new WebSocket(url);
18+
ws.binaryType = "arraybuffer";
19+
20+
// Create a MAVLink v2 parser
21+
parser = new MAVLink20Processor()
22+
23+
let signing_passphrase = null;
24+
if (process.argv.length > 3) {
25+
signing_passphrase = process.argv[3];
26+
}
27+
28+
const crypto = require('crypto');
29+
30+
function passphrase_to_key(passphrase) {
31+
return crypto.createHash('sha256').update(Buffer.from(passphrase, 'ascii')).digest();
32+
}
33+
34+
if (signing_passphrase) {
35+
parser.signing.secret_key = passphrase_to_key(signing_passphrase);
36+
parser.signing.sign_outgoing = true;
37+
}
38+
39+
let heartbeat_interval;
40+
41+
ws.on('open', () => {
42+
console.log(`Connected to ${url}`);
43+
44+
heartbeat_interval = setInterval(() => {
45+
try {
46+
const msg = new mavlib.mavlink20.messages.heartbeat(
47+
6, // MAV_TYPE_GCS
48+
8, // MAV_AUTOPILOT_INVALID
49+
0, // base_mode
50+
0, // custom_mode
51+
4 // MAV_STATE_ACTIVE
52+
);
53+
const pkt = msg.pack(parser);
54+
ws.send(pkt);
55+
console.log("Sent HEARTBEAT");
56+
} catch (e) {
57+
console.error("Error sending HEARTBEAT:", e.message);
58+
console.error(e.stack);
59+
}
60+
}, 1000);
61+
});
62+
63+
function mav_pretty(msg) {
64+
// console.log(JSON.stringify(msg, null, 2));
65+
66+
if (!msg || !msg._name || !msg.fieldnames) {
67+
return "<invalid MAVLink message>";
68+
}
69+
70+
const name = msg._name;
71+
const fields = msg.fieldnames
72+
.map(fn => {
73+
let val = msg[fn];
74+
if (typeof val === "string") {
75+
val = `"${val}"`;
76+
} else if (Array.isArray(val)) {
77+
val = "[" + val.join(", ") + "]";
78+
}
79+
return `${fn}=${val}`;
80+
})
81+
.join(", ");
82+
83+
return `${name} { ${fields} }`;
84+
}
85+
86+
87+
ws.on('message', (data) => {
88+
const buf = (data instanceof ArrayBuffer) ? Buffer.from(data) :
89+
(Buffer.isBuffer(data) ? data : Buffer.from(data.buffer || data));
90+
91+
console.log(`Received ${buf.length} bytes: [${buf.slice(0, 10).toString('hex')}...]`);
92+
93+
for (const b of buf) {
94+
try {
95+
const msg = parser.parseChar(b);
96+
if (msg) {
97+
console.log(`MAVLink message ID: ${msg._id}`);
98+
console.log(mav_pretty(msg));
99+
}
100+
} catch (e) {
101+
console.warn(`Parser error on byte 0x${byte.toString(16)}: ${e.message}`);
102+
}
103+
}
104+
});
105+
106+
ws.on('close', () => {
107+
console.log("WebSocket closed");
108+
clearInterval(heartbeat_interval);
109+
});
110+
111+
ws.on('error', (err) => {
112+
console.error("WebSocket error:", err.message);
113+
});

TelemetryDashboard/index.html

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,21 @@ <h6>Dashboard settings</h6>
176176
<input id="target_url" type="url" placeholder="ws://127.0.0.1:5863" required="true" pattern="^(ws|wss)://.*">
177177
<br>
178178
<br>
179+
<div style="display: flex; align-items: center;">
180+
<input id="send_heartbeat" type="checkbox" style="margin-right: 0.5em;">
181+
<label for="send_heartbeat">Send Heartbeat</label>
182+
</div>
183+
<div style="display:flex; gap:.75em; align-items:center;">
184+
<label for="system_id" style="min-width:7em;">System ID</label>
185+
<input id="system_id" type="number" min="1" max="255" step="1" value="255" style="width:6em;">
186+
<label for="component_id" style="min-width:7em;">Component ID</label>
187+
<input id="component_id" type="number" min="0" max="255" step="1" value="190" style="width:6em;">
188+
</div>
189+
<br>
190+
<br>
191+
<label for="signing_passphrase">Signing passphrase</label>
192+
<input id="signing_passphrase" type="text" placeholder="Enter passphrase" style="width: 100%;">
193+
<br><br>
179194
<input id="connection_button" type="button" value="Connect">
180195
<input id="disconnection_button" type="button" value="Disconnect">
181196
</div>
@@ -200,20 +215,21 @@ <h6>Dashboard settings</h6>
200215
})
201216

202217
// Load grid
203-
let grid
204-
let grid_changed = false
205-
load_default_grid()
218+
let grid;
219+
let grid_changed = false;
206220

207221
// MAVLink parsing
208-
MAVLink = new MAVLink20Processor()
222+
let MAVLink = new MAVLink20Processor();
223+
224+
load_default_grid();
209225

210226
// Setup editor for use later
211-
init_editor()
227+
init_editor();
212228

213229
// Setup widget pallet
214-
init_pallet()
230+
init_pallet();
215231

216232
// Bind unload event to allow prompt for save
217-
window.addEventListener('beforeunload', handle_unload)
233+
window.addEventListener('beforeunload', handle_unload);
218234

219235
</script>

0 commit comments

Comments
 (0)