231 lines
7.9 KiB
HTML
231 lines
7.9 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
============================================================
|
|
ARGONAUT 3 — MISSION STATUS
|
|
Widget 2
|
|
============================================================
|
|
Purpose : Show the current mission state, progress bar, and
|
|
active waypoint during autonomous operation.
|
|
Data : Reads two NAMED_VALUE variables from the Cockpit
|
|
data lake (published by the ROS2 → MAVLink bridge):
|
|
rov_mission_state (INT) 0=IDLE, 1=RUNNING,
|
|
2=PAUSED, 3=COMPLETE,
|
|
4=ABORTED
|
|
rov_mission_progress (FLOAT) 0.0 to 1.0
|
|
Import : Cockpit → Edit mode → Add DIY Widget →
|
|
gear icon → Import → select this file
|
|
Note : Shows "--" placeholders when bridge is not running.
|
|
Version : 1.0
|
|
============================================================
|
|
-->
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
:root {
|
|
--bg: #09111a;
|
|
--bg2: #0e1d2e;
|
|
--border: #1e3050;
|
|
--text0: #d8eeff;
|
|
--text1: #6a9bbf;
|
|
--green: #00e09a;
|
|
--amber: #ffb830;
|
|
--red: #ff3a5a;
|
|
--blue: #00c8f0;
|
|
--dim: #1a2d42;
|
|
--mono: 'Courier New', monospace;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text0);
|
|
font-family: var(--mono);
|
|
height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
padding: 10px 14px;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* ── State label (IDLE / RUNNING / etc.) ────────────────── */
|
|
#state-label {
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
color: var(--text1); /* overridden per state */
|
|
}
|
|
|
|
#state-label.idle { color: var(--text1); }
|
|
#state-label.running { color: var(--green); }
|
|
#state-label.paused { color: var(--amber); }
|
|
#state-label.complete { color: var(--blue); }
|
|
#state-label.aborted { color: var(--red); }
|
|
#state-label.waiting { color: var(--dim); }
|
|
|
|
/* ── Progress bar ─────────────────────────────────────────── */
|
|
#bar-wrap {
|
|
width: 100%;
|
|
height: 8px;
|
|
background: var(--dim);
|
|
border-radius: 4px;
|
|
border: 1px solid var(--border);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* The filled portion */
|
|
#bar-fill {
|
|
height: 100%;
|
|
width: 0%; /* set dynamically via JS */
|
|
border-radius: 4px;
|
|
background: var(--green); /* overridden per state */
|
|
transition: width 0.5s ease, background 0.3s;
|
|
}
|
|
|
|
/* ── Percentage label ────────────────────────────────────── */
|
|
#pct-label {
|
|
font-size: 11px;
|
|
color: var(--text1);
|
|
align-self: flex-end;
|
|
margin-top: -4px;
|
|
}
|
|
|
|
/* ── Waypoint info line ──────────────────────────────────── */
|
|
#waypoint {
|
|
font-size: 10px;
|
|
color: var(--text1);
|
|
text-align: center;
|
|
}
|
|
|
|
/* ── Data source footer ──────────────────────────────────── */
|
|
#footer {
|
|
font-size: 9px;
|
|
color: #1e3050;
|
|
margin-top: 2px;
|
|
}
|
|
</style>
|
|
|
|
<body>
|
|
|
|
<div id="state-label" class="waiting">—</div>
|
|
|
|
<div id="bar-wrap">
|
|
<div id="bar-fill"></div>
|
|
</div>
|
|
|
|
<div id="pct-label">—%</div>
|
|
|
|
<div id="waypoint">Waypoint: —</div>
|
|
|
|
<div id="footer">Waiting for rov_mission_state…</div>
|
|
|
|
</body>
|
|
|
|
<script>
|
|
// ── Config ────────────────────────────────────────────────
|
|
const VAR_STATE = 'rov_mission_state'; // INT 0-4
|
|
const VAR_PROGRESS = 'rov_mission_progress'; // FLOAT 0.0-1.0
|
|
// Future: VAR_WAYPOINT could be a string variable once
|
|
// Cockpit supports NAMED_VALUE_STR. For now the waypoint
|
|
// label is derived from state + progress.
|
|
const POLL_MS = 500;
|
|
|
|
// State constants matching the MissionStatus ROS2 message
|
|
const STATE = {
|
|
0: 'IDLE',
|
|
1: 'RUNNING',
|
|
2: 'PAUSED',
|
|
3: 'COMPLETE',
|
|
4: 'ABORTED',
|
|
};
|
|
|
|
// CSS class and bar colour per state
|
|
const STATE_META = {
|
|
0: { cls: 'idle', barColor: '#1a2d42' }, /* dim — no mission */
|
|
1: { cls: 'running', barColor: '#00e09a' }, /* green — active */
|
|
2: { cls: 'paused', barColor: '#ffb830' }, /* amber — paused */
|
|
3: { cls: 'complete', barColor: '#00c8f0' }, /* blue — done */
|
|
4: { cls: 'aborted', barColor: '#ff3a5a' }, /* red — aborted */
|
|
};
|
|
|
|
// ── DOM references ────────────────────────────────────────
|
|
const stateLabelEl = document.getElementById('state-label');
|
|
const barFillEl = document.getElementById('bar-fill');
|
|
const pctLabelEl = document.getElementById('pct-label');
|
|
const waypointEl = document.getElementById('waypoint');
|
|
const footerEl = document.getElementById('footer');
|
|
|
|
// ── Render ────────────────────────────────────────────────
|
|
function render(state, progress) {
|
|
const stateStr = STATE[state] ?? '?';
|
|
const meta = STATE_META[state] ?? { cls: 'waiting', barColor: '#1a2d42' };
|
|
const pct = Math.round((progress ?? 0) * 100);
|
|
|
|
// State label
|
|
stateLabelEl.className = meta.cls;
|
|
stateLabelEl.textContent = stateStr;
|
|
|
|
// Progress bar width + colour
|
|
barFillEl.style.width = `${pct}%`;
|
|
barFillEl.style.background = meta.barColor;
|
|
|
|
// Percentage text — suppress when idle or complete/aborted at 0
|
|
pctLabelEl.textContent = (state === 1 || state === 2)
|
|
? `${pct}%`
|
|
: (state === 3 ? '100%' : '—%');
|
|
|
|
// Waypoint info — simple derivation until a string variable is available
|
|
if (state === 1 || state === 2) {
|
|
waypointEl.textContent = `Progress ${pct}% complete`;
|
|
} else if (state === 3) {
|
|
waypointEl.textContent = 'Mission complete';
|
|
} else if (state === 4) {
|
|
waypointEl.textContent = 'Mission aborted — vehicle holding';
|
|
} else {
|
|
waypointEl.textContent = 'No mission active';
|
|
}
|
|
|
|
footerEl.textContent = `Updated ${new Date().toLocaleTimeString()}`;
|
|
}
|
|
|
|
// ── Poll the data lake ────────────────────────────────────
|
|
function poll() {
|
|
try {
|
|
if (typeof window.cockpit === 'undefined' ||
|
|
typeof window.cockpit.getDataLakeValue !== 'function') {
|
|
stateLabelEl.className = 'waiting';
|
|
stateLabelEl.textContent = '—';
|
|
footerEl.textContent = 'Cockpit API not ready';
|
|
return;
|
|
}
|
|
|
|
const stateRaw = window.cockpit.getDataLakeValue(VAR_STATE);
|
|
const progressRaw = window.cockpit.getDataLakeValue(VAR_PROGRESS);
|
|
|
|
if (stateRaw === null || stateRaw === undefined) {
|
|
stateLabelEl.className = 'waiting';
|
|
stateLabelEl.textContent = '—';
|
|
footerEl.textContent = `Waiting for ${VAR_STATE}`;
|
|
return;
|
|
}
|
|
|
|
const state = parseInt(stateRaw, 10);
|
|
const progress = parseFloat(progressRaw ?? 0);
|
|
|
|
render(state, progress);
|
|
|
|
} catch (err) {
|
|
stateLabelEl.className = 'waiting';
|
|
stateLabelEl.textContent = 'ERR';
|
|
footerEl.textContent = 'Poll error — see console';
|
|
console.error('[Mission Status Widget]', err);
|
|
}
|
|
}
|
|
|
|
// ── Start ─────────────────────────────────────────────────
|
|
setTimeout(poll, 300);
|
|
setInterval(poll, POLL_MS);
|
|
</script>
|