Skip to content

Commit be151c0

Browse files
committed
TelemetryDashboard: allow for heartbeat send and signing
and add cli_test.js
1 parent 8d23585 commit be151c0

3 files changed

Lines changed: 176 additions & 15 deletions

File tree

TelemetryDashboard/TelemetryDashboard.js

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ 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

@@ -92,8 +95,41 @@ function setup_connect(button_svg, button_color) {
9295
url_input.value = target
9396

9497
// Have been connected
95-
been_connected = true
96-
}
98+
been_connected = true;
99+
setup_passphase = false;
100+
101+
heartbeat_interval = setInterval(() => {
102+
try {
103+
if (!setup_passphase) {
104+
const passphrase = passphrase_input.value.trim();
105+
if (passphrase.length > 0) {
106+
setup_passphase = true;
107+
const enc = new TextEncoder();
108+
const data = enc.encode(passphrase);
109+
const hash = mavlink20.sha256(data);
110+
MAVLink.signing.secret_key = new Uint8Array(hash);
111+
MAVLink.signing.sign_outgoing = true;
112+
}
113+
}
114+
115+
if (heartbeat_checkbox.checked) {
116+
const msg = new mavlink20.messages.heartbeat(
117+
6, // MAV_TYPE_GCS
118+
8, // MAV_AUTOPILOT_INVALID
119+
0, // base_mode
120+
0, // custom_mode
121+
4 // MAV_STATE_ACTIVE
122+
);
123+
const pkt = msg.pack(MAVLink);
124+
ws.send(Uint8Array.from(pkt));
125+
console.log("Sent HEARTBEAT", pkt);
126+
}
127+
} catch (e) {
128+
console.error("Error sending HEARTBEAT:", e.message);
129+
console.error(e.stack);
130+
}
131+
}, 1000);
132+
}
97133

98134
ws.onclose = () => {
99135
if ((auto_connect === true) && !been_connected) {
@@ -115,10 +151,16 @@ function setup_connect(button_svg, button_color) {
115151
}
116152

117153
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)) {
154+
// Feed data to MAVLink parser and forward messages
155+
console.log(msg.data);
156+
MAVLink.pushBuffer(new Uint8Array(msg.data));
157+
while (true) {
158+
const m = MAVLink.parseChar(null);
159+
if (m === null) {
160+
break;
161+
}
162+
if (m._id != -1) {
163+
console.log(m);
122164
// Got message with known ID
123165
// Sent to each Widget
124166
for (const widget of grid.getGridItems()) {
@@ -130,7 +172,6 @@ function setup_connect(button_svg, button_color) {
130172
}
131173
}
132174
}
133-
134175
}
135176

136177
// Disconnect from WebSocket server
@@ -179,7 +220,7 @@ function setup_connect(button_svg, button_color) {
179220
}
180221

181222
// Try auto connecting to MissionPlanner
182-
connect("ws://127.0.0.1:56781", true)
223+
connect("ws://127.0.0.1:11099", true)
183224

184225
}
185226

TelemetryDashboard/cli_test.js

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

TelemetryDashboard/index.html

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,14 @@ <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+
<br>
184+
<label for="signing_passphrase">Signing passphrase</label>
185+
<input id="signing_passphrase" type="text" placeholder="Enter passphrase" style="width: 100%;">
186+
<br><br>
179187
<input id="connection_button" type="button" value="Connect">
180188
<input id="disconnection_button" type="button" value="Disconnect">
181189
</div>
@@ -200,20 +208,21 @@ <h6>Dashboard settings</h6>
200208
})
201209

202210
// Load grid
203-
let grid
204-
let grid_changed = false
205-
load_default_grid()
211+
let grid;
212+
let grid_changed = false;
206213

207214
// MAVLink parsing
208-
MAVLink = new MAVLink20Processor()
215+
let MAVLink = new MAVLink20Processor();
216+
217+
load_default_grid();
209218

210219
// Setup editor for use later
211-
init_editor()
220+
init_editor();
212221

213222
// Setup widget pallet
214-
init_pallet()
223+
init_pallet();
215224

216225
// Bind unload event to allow prompt for save
217-
window.addEventListener('beforeunload', handle_unload)
226+
window.addEventListener('beforeunload', handle_unload);
218227

219228
</script>

0 commit comments

Comments
 (0)