Build Argo V2 control mappings and telemetry dashboard

Add configurable Xbox motion and action bindings, frame-aware six-axis MAVLink controls, sensor logging, camera viewing and recording, and a faster startup path. Synchronize both app copies and document companion hardware setup and verification limits.
This commit is contained in:
2026-10-22 20:22:05 +02:00
parent 5e91c28f1e
commit 8ccd7689f7
38 changed files with 4295 additions and 1897 deletions
+35 -15
View File
@@ -3,8 +3,9 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sub Control</title>
<link rel="stylesheet" href="styles.css?v=20261017-engage-center-v2">
<title>Argo V2</title>
<link rel="stylesheet" href="styles.css?v=20261022-v2">
<link rel="stylesheet" href="v2.css?v=20261022-v2">
</head>
<body>
<main class="app-shell">
@@ -34,7 +35,7 @@
<!-- Telemetry / Status Zone -->
<section class="telemetry-zone" aria-label="Vehicle Telemetry and Status">
<div class="deck-card model-card">
<div class="rov-stage" aria-label="Top view of three-thruster ROV">
<div class="rov-stage" aria-label="Vehicle model">
<canvas id="model-canvas" aria-label="Interactive STL vehicle model"></canvas>
<div id="model-status" class="model-status">Initializing…</div>
<div class="model-tools">
@@ -71,7 +72,7 @@
<section class="controls-workspace" aria-label="Primary Vehicle Controls">
<!-- Frameless Drag & Window Controls Header -->
<div class="workspace-topbar">
<div class="input-mode-controls"><div class="input-mode-switch" role="group" aria-label="Control input"><button type="button" id="input-keyboard" aria-pressed="true">Keyboard</button><button type="button" id="input-joystick" aria-pressed="false">Joystick</button></div><button type="button" id="controller-settings" class="controller-settings" data-open="controller-modal" hidden>Setup</button></div>
<div class="input-mode-controls"><div class="input-mode-switch" role="group" aria-label="Control input"><button type="button" id="input-keyboard" aria-pressed="true">Keyboard</button><button type="button" id="input-joystick" aria-pressed="false">Joystick</button></div><button type="button" id="controller-settings" class="controller-settings" data-open="controller-modal">Mapping</button></div>
<div class="workspace-drag-area pywebview-drag-region"></div>
<div class="window-actions">
<button id="window-min" aria-label="Minimize" title="Minimize"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12h14"/></svg></button>
@@ -80,7 +81,18 @@
</div>
</div>
<div class="workspace-nav"><div class="view-switch" role="group" aria-label="Workspace"><button id="view-drive" type="button" aria-pressed="true">Drive</button><button id="view-dashboard" type="button" aria-pressed="false">Dashboard</button></div><span class="version-mark">V2</span><button class="quiet-button" data-open="station-modal" type="button">Vehicle</button></div>
<p id="controller-status" class="controller-status" role="status">Keyboard + touch</p>
<section id="dashboard" class="dashboard" aria-label="Vehicle dashboard" hidden>
<div class="camera-stage">
<video id="camera-video" muted autoplay playsinline hidden></video><img id="camera-image" alt="Camera stream" hidden><canvas id="camera-demo" aria-label="Simulated inspection camera" hidden></canvas>
<div id="camera-empty" class="camera-empty"><svg viewBox="0 0 48 48" aria-hidden="true"><rect x="5" y="12" width="27" height="24" rx="6"/><path d="m32 20 11-6v20l-11-6"/></svg><span id="camera-state">Camera off</span></div>
<div class="camera-overlay"><span id="camera-badge">Preview</span><span id="recording-badge" hidden>● REC</span><span id="stream-clock"></span></div>
</div>
<div class="dashboard-actions"><button id="camera-toggle" type="button">Start</button><button id="camera-record" type="button" aria-pressed="false" disabled>Record</button><button id="lights-toggle" type="button" aria-pressed="false" disabled>Lights</button><button id="camera-power" type="button" aria-pressed="false" disabled>Camera power</button><button id="sensor-record" type="button" aria-pressed="false">Log data</button></div>
<div class="telemetry-strip" aria-label="Live telemetry"><div><span>Depth</span><strong id="live-depth">—</strong></div><div><span>Heading</span><strong id="live-heading">—</strong></div><div><span>Current</span><strong id="live-current">—</strong></div><div><span>Pitch</span><strong id="live-pitch">—</strong></div><div><span>Roll</span><strong id="live-roll">—</strong></div><div><span>Power</span><strong id="live-power">—</strong></div></div>
<details class="sensor-details"><summary>Sensors <span id="sensor-state">Offline</span></summary><div class="sensor-values"><div><span>Acceleration</span><strong id="live-accel">—</strong></div><div><span>Gyroscope</span><strong id="live-gyro">—</strong></div><div><span>Magnetometer</span><strong id="live-mag">—</strong></div><div><span>Temperature</span><strong id="live-temperature">—</strong></div><div><span>Pressure</span><strong id="live-pressure">—</strong></div><div><span>Charge used</span><strong id="live-charge">—</strong></div></div></details>
</section>
<section class="primary-control-zone" aria-label="Primary Control Zone">
<!-- Reserved Engage / Disarm row -->
<div class="engage-header-zone">
@@ -146,6 +158,7 @@
</label>
<button class="button subtle config-btn" data-open="motors-modal">Motor setup</button>
</div>
<div id="extra-controls" class="extra-controls" hidden><label data-motion="lateral"><span>Strafe</span><input type="range" min="-100" max="100" value="0" aria-label="Strafe demand"><output>0</output></label><label data-motion="pitch"><span>Pitch</span><input type="range" min="-100" max="100" value="0" aria-label="Pitch demand"><output>0</output></label><label data-motion="roll"><span>Roll</span><input type="range" min="-100" max="100" value="0" aria-label="Roll demand"><output>0</output></label></div>
</section>
</section>
<aside id="console-dock" class="console-dock" aria-labelledby="console-title" hidden>
@@ -218,28 +231,35 @@
<div class="motor-scale-heading"><span>Output</span><label class="switch-control"><input id="motor-scale-independent" type="checkbox"><span class="switch-track" aria-hidden="true"></span><span>Independent</span></label></div>
<div id="motor-list" class="motor-list"></div>
</dialog>
<dialog id="controller-modal" class="modal" aria-labelledby="controller-title">
<dialog id="controller-modal" class="modal controller-modal" aria-labelledby="controller-title">
<button class="modal-close" type="button" data-close aria-label="Close window"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button><h2 id="controller-title">Joystick setup</h2>
<form id="controller-form" title="Center sticks after changing input mode or reconnecting before movement resumes.">
<label><span>Controller</span><select id="controller-device"></select></label>
<p id="controller-axes" class="note">Move a stick or press a button to detect your controller.</p>
<div class="controller-mapping-head"><span>Movement</span><span>Axis</span><span>Invert</span></div>
<div class="controller-mapping-row"><span>Turn</span><select name="turn" aria-label="Turn axis"></select><input name="invertTurn" type="checkbox" aria-label="Invert Turn"></div>
<div class="controller-mapping-row"><span>Forward / back</span><select name="forward" aria-label="Forward / back axis"></select><input name="invertForward" type="checkbox" aria-label="Invert Forward / back"></div>
<div class="controller-mapping-row"><span>Rise / dive</span><select name="vertical" aria-label="Rise / dive axis"></select><input name="invertVertical" type="checkbox" aria-label="Invert Rise / dive"></div>
<label class="controller-deadzone"><span>Dead zone · %</span><input name="deadZone" type="number" min="0" max="30" step="1" required></label>
<form id="controller-form">
<div class="form-grid two"><label><span>Controller</span><select id="controller-device"></select></label><label><span>Preset</span><select id="controller-preset"><option value="triggers3">Triggers · 3</option><option value="sixaxis">Six axes</option><option value="classic">Classic sticks</option><option value="custom">Custom</option></select></label></div>
<p id="controller-axes" class="note" hidden>Xbox · offline</p>
<div class="binding-head"><span>Motion</span><span>Negative</span><span>Positive</span><span></span><span>Limit %</span></div>
<div id="controller-bindings"></div>
<div class="form-grid two mapping-tuning"><label><span>Dead zone %</span><input name="deadZone" type="number" min="0" max="30" step="1" required></label><label><span>Expo %</span><input name="expo" type="number" min="0" max="100" step="1" required></label></div>
<details class="mapping-actions"><summary>Buttons</summary><div class="form-grid three"><label><span>Lights</span><select name="lights"></select></label><label><span>Stream</span><select name="stream"></select></label><label><span>Record</span><select name="record"></select></label></div></details>
<p class="form-feedback" role="alert" hidden></p>
<div class="modal-actions"><button type="button" class="button subtle" data-close>Cancel</button><button type="submit" class="button modal-primary">Save mapping</button></div>
</form>
</dialog>
<dialog id="station-modal" class="modal station-modal" aria-labelledby="station-title"><button class="modal-close" type="button" data-close aria-label="Close window"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 6 12 12M18 6 6 18"/></svg></button><h2 id="station-title">Vehicle</h2><form id="station-form">
<div class="form-grid two"><label><span>Preview frame</span><select name="frame"><option value="4">SimpleROV · 3</option><option value="0">BlueROV1 · 6</option><option value="1">Vectored · 6</option><option value="2">Full 6DOF · 8</option><option value="3">6DOF 90° · 8</option><option value="5">SimpleROV · 4</option><option value="6">SimpleROV · 5</option></select></label><div class="detected-frame"><span>FC frame</span><strong id="detected-frame">Offline</strong></div></div>
<section class="settings-section"><h3>Camera</h3><label><span>Stream type</span><select name="video_mode"><option value="whep">WebRTC · WHEP</option><option value="video">HTTP video</option><option value="mjpeg">MJPEG</option></select></label><label><span>Stream URL</span><input name="video_url" type="url" placeholder="http://192.168.2.2:8889/cam/whep" autocomplete="off"></label></section>
<section class="settings-section"><h3>Accessories</h3><div class="form-grid two"><label><span>Lights relay</span><select name="lights_relay"></select></label><label><span>Camera relay</span><select name="camera_relay"></select></label></div></section>
<p class="form-feedback" role="alert" hidden></p><div class="modal-actions"><button type="button" class="button subtle" data-close>Cancel</button><button type="submit" class="button modal-primary">Save</button></div>
</form></dialog>
<script src="model-viewer.js?v=20260826-controls-v2"></script>
<script src="joystick-3d.js?v=20261002-spacing-motion-v2"></script>
<script src="control-state.js?v=20260827-state-v1"></script>
<script src="controller-input.js?v=20261001-controller-v1"></script>
<script src="controller-input.js?v=20261022-v2"></script>
<script src="dashboard.js?v=20261022-v2"></script>
<script src="console-view.js?v=20261002-console-v2"></script>
<script src="app.js?v=20261017-engage-center-v2"></script>
<script src="app.js?v=20261022-v2"></script>
<script src="cursor-light.js?v=20260827-directional-v10"></script>
</body>
</html>