rov-autonomy/widgets/w2_mission_status.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>