- Add widgets/w6_altitude.json (Ping2 downward sonar altitude, W6) - Remove widgets/diagnostics/w_probe_api.json (dev API probe tool, no longer needed)
10 lines
11 KiB
JSON
10 lines
11 KiB
JSON
{
|
|
"html": "<div id=\"w6-root\"><div id=\"w6-header\"><div id=\"w6-title\">ALTITUDE</div><div id=\"w6-subtitle\">Ping2 Sonar \u00b7 Downward</div></div><div id=\"w6-body\"><div id=\"w6-bar-track\"><div id=\"w6-bar-caution-zone\"></div><div id=\"w6-bar-critical-zone\"></div><div id=\"w6-bar-fill\"></div><div id=\"w6-bar-label-caution\">1.5</div><div id=\"w6-bar-label-critical\">0.5</div></div><div id=\"w6-readout\"><div id=\"w6-value\">--</div><div id=\"w6-unit\">metres</div></div></div><div id=\"w6-status\">NO SONAR</div></div>",
|
|
|
|
"css": "/* ============================================================\n W6 — Altitude Widget\n Displays downward Ping2 sonar altitude in metres.\n Colour bands: GREEN >1.5m / AMBER 0.5-1.5m / RED <0.5m\n Shows NO SONAR when no data is available (sentinel -1).\n Style: matches W1-W5 dark theme.\n ============================================================ */\n\n/* Root container — fills the Cockpit widget panel */\n#w6-root {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: space-between;\n height: 100%;\n padding: 10px 8px;\n box-sizing: border-box;\n background: #0d1117;\n font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;\n color: #c9d1d9;\n user-select: none;\n}\n\n/* Header block — title and subtitle */\n#w6-header {\n text-align: center;\n width: 100%;\n}\n\n/* Widget title */\n#w6-title {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #8b949e;\n margin-bottom: 2px;\n}\n\n/* Widget subtitle — sensor identification */\n#w6-subtitle {\n font-size: 9px;\n color: #484f58;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n\n/* Body — bar gauge + numeric readout side by side */\n#w6-body {\n display: flex;\n flex-direction: row;\n align-items: flex-end;\n justify-content: center;\n gap: 12px;\n flex: 1;\n width: 100%;\n padding: 8px 0;\n}\n\n/* Vertical bar track — the background of the altitude bar */\n#w6-bar-track {\n position: relative;\n width: 18px;\n height: 90px;\n background: #161b22;\n border: 1px solid #30363d;\n border-radius: 3px;\n overflow: hidden;\n}\n\n/* Colour zone overlays — these are static backgrounds showing\n the threshold bands even when no data is present.\n Critical zone: bottom 10% of bar (0 - 0.5m)\n Caution zone: 10-30% of bar (0.5 - 1.5m)\n Good zone: 30-100% (above 1.5m) is the default dark track */\n#w6-bar-critical-zone {\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 10%; /* Represents 0 - 0.5m on the 0-5m scale */\n background: rgba(239, 68, 68, 0.15); /* Faint red tint */\n border-top: 1px dashed rgba(239, 68, 68, 0.3);\n}\n\n#w6-bar-caution-zone {\n position: absolute;\n bottom: 10%;\n left: 0;\n width: 100%;\n height: 20%; /* Represents 0.5m - 1.5m on the 0-5m scale */\n background: rgba(245, 158, 11, 0.10); /* Faint amber tint */\n border-top: 1px dashed rgba(245, 158, 11, 0.3);\n}\n\n/* Animated fill bar — height driven by JS based on altitude value */\n#w6-bar-fill {\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 0%; /* Updated by JS */\n background: #00e5a0; /* Default green; updated by JS based on state */\n border-radius: 2px 2px 0 0;\n transition: height 0.4s ease, background 0.3s ease; /* Smooth transitions */\n}\n\n/* Threshold labels on the bar — 1.5m and 0.5m markers */\n#w6-bar-label-caution,\n#w6-bar-label-critical {\n position: absolute;\n right: -22px; /* Float labels to the right of the bar */\n font-size: 7px;\n color: #484f58;\n font-family: monospace;\n white-space: nowrap;\n}\n\n#w6-bar-label-caution { bottom: calc(30% - 4px); } /* At 1.5m threshold */\n#w6-bar-label-critical { bottom: calc(10% - 4px); } /* At 0.5m threshold */\n\n/* Numeric readout block — large altitude value */\n#w6-readout {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-end;\n}\n\n/* Large altitude number — colour updated by JS */\n#w6-value {\n font-size: 36px;\n font-weight: 700;\n font-variant-numeric: tabular-nums; /* Prevents layout shift as digits change */\n color: #8b949e; /* Default grey; updated by JS */\n line-height: 1;\n letter-spacing: -0.02em;\n transition: color 0.3s ease;\n}\n\n/* Unit label below the number */\n#w6-unit {\n font-size: 10px;\n color: #484f58;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n margin-top: 3px;\n}\n\n/* Status label at the bottom — GOOD / CAUTION / CRITICAL / NO SONAR */\n#w6-status {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #484f58; /* Default grey for NO SONAR; updated by JS */\n transition: color 0.3s ease;\n padding-top: 4px;\n}",
|
|
|
|
"js": "// ============================================================\n// W6 — Altitude Widget\n// Argonaut 3 — Cockpit DIY Widget\n//\n// Reads the downward Ping2 sonar altitude from the Cockpit\n// data lake via cockpit_bridge and displays it as a numeric\n// readout with a colour-coded vertical bar gauge.\n//\n// Data lake variable: external/rov-altitude\n// Source topic: /ping2/altitude (sensor_msgs/Range)\n// Published by: cockpit_bridge (ROS2 node on RPi5)\n//\n// Thresholds:\n// GOOD (GREEN) : altitude > 1.5m\n// CAUTION (AMBER) : 0.5m <= altitude <= 1.5m\n// CRITICAL (RED) : altitude < 0.5m\n// NO SONAR (GREY) : sentinel value -1.0 (no Ping2 data)\n//\n// Bar scale: linear, 0m (bottom) to 5m (top).\n// Altitudes above 5m fill the bar to 100%.\n//\n// Confirmed working Cockpit data lake read method:\n// window.cockpit.getDataLakeVariableData(variableId)\n// (confirmed via probe widget, documented UI Design v1.5)\n// ============================================================\n\n(function () {\n 'use strict';\n\n // ----------------------------------------------------------\n // Constants\n // ----------------------------------------------------------\n\n // Cockpit data lake variable ID populated by cockpit_bridge\n var DATA_LAKE_VAR = 'external/rov-altitude';\n\n // Altitude thresholds in metres\n var THRESHOLD_CRITICAL_M = 0.5; // Below this: RED (CRITICAL)\n var THRESHOLD_CAUTION_M = 1.5; // Below this: AMBER (CAUTION); above: GREEN (GOOD)\n\n // Bar gauge upper limit in metres.\n // Altitudes at or above this fill the bar to 100%.\n // Set to 5m — the operationally relevant near-structure range.\n var BAR_MAX_M = 5.0;\n\n // Sentinel value published by cockpit_bridge when no Ping2\n // data has been received (no sonar connected or no return).\n var SENTINEL_NO_DATA = -1.0;\n\n // Poll rate — 2Hz, matching cockpit_bridge broadcast rate\n var POLL_INTERVAL_MS = 500;\n\n // Colours — match W1-W5 project palette\n var COLOUR_GOOD = '#00e5a0'; // Green — safe altitude\n var COLOUR_CAUTION = '#f59e0b'; // Amber — low altitude warning\n var COLOUR_CRITICAL = '#ef4444'; // Red — critical altitude\n var COLOUR_NO_DATA = '#484f58'; // Grey — no sonar\n\n // ----------------------------------------------------------\n // DOM element references\n // Resolved once on load — the elements are guaranteed to\n // exist because this JS runs after the HTML is injected.\n // ----------------------------------------------------------\n var elValue = document.getElementById('w6-value');\n var elStatus = document.getElementById('w6-status');\n var elBarFill = document.getElementById('w6-bar-fill');\n\n // ----------------------------------------------------------\n // update()\n // Called at POLL_INTERVAL_MS. Reads the current altitude\n // from the data lake and updates the display accordingly.\n // ----------------------------------------------------------\n function update() {\n // Guard: Cockpit API must be present\n if (typeof window.cockpit === 'undefined' ||\n typeof window.cockpit.getDataLakeVariableData !== 'function') {\n setNoData('NO API');\n return;\n }\n\n // Read current altitude value from the data lake.\n // Returns a string representation of the float or null\n // if the variable has not yet been published.\n var raw = window.cockpit.getDataLakeVariableData(DATA_LAKE_VAR);\n\n // Parse to float — handles string '3.45', number 3.45, null, undefined\n var metres = parseFloat(raw);\n\n // ----------------------------------------------------------\n // Case 1: No data — sentinel value, NaN, or null\n // Shown when no Ping2 is connected or no echo returned.\n // ----------------------------------------------------------\n if (isNaN(metres) || metres <= SENTINEL_NO_DATA) {\n setNoData('NO SONAR');\n return;\n }\n\n // ----------------------------------------------------------\n // Case 2: Data received — determine status band and colour\n // ----------------------------------------------------------\n var colour, statusText;\n\n if (metres < THRESHOLD_CRITICAL_M) {\n // RED — vehicle is dangerously close to the structure/seafloor\n colour = COLOUR_CRITICAL;\n statusText = 'CRITICAL';\n } else if (metres < THRESHOLD_CAUTION_M) {\n // AMBER — altitude is low; operator awareness required\n colour = COLOUR_CAUTION;\n statusText = 'CAUTION';\n } else {\n // GREEN — altitude is safe for normal operations\n colour = COLOUR_GOOD;\n statusText = 'GOOD';\n }\n\n // ----------------------------------------------------------\n // Update DOM elements\n // ----------------------------------------------------------\n\n // Numeric readout — 2 decimal places, colour-coded\n elValue.textContent = metres.toFixed(2);\n elValue.style.color = colour;\n\n // Status label\n elStatus.textContent = statusText;\n elStatus.style.color = colour;\n\n // Bar fill height — linear scale from 0m to BAR_MAX_M\n // Clamped to [0, 100]% so values above BAR_MAX_M stay at 100%\n var fillPct = Math.min(Math.max(metres / BAR_MAX_M, 0), 1.0) * 100;\n elBarFill.style.height = fillPct.toFixed(1) + '%';\n elBarFill.style.background = colour;\n }\n\n // ----------------------------------------------------------\n // setNoData(label)\n // Resets all display elements to the no-data / no-sonar state.\n // Called when the sentinel value is read or the API is absent.\n // ----------------------------------------------------------\n function setNoData(label) {\n elValue.textContent = '--';\n elValue.style.color = COLOUR_NO_DATA;\n elStatus.textContent = label;\n elStatus.style.color = COLOUR_NO_DATA;\n elBarFill.style.height = '0%';\n elBarFill.style.background = COLOUR_NO_DATA;\n }\n\n // ----------------------------------------------------------\n // Startup\n // Run one immediate update so the widget does not show a\n // blank state for the first 500ms after load.\n // Then start the 2Hz poll loop.\n // ----------------------------------------------------------\n update();\n setInterval(update, POLL_INTERVAL_MS);\n\n}());",
|
|
|
|
"inheritCockpitStyles": false
|
|
}
|