Commit 9d758b32 authored by vertighel's avatar vertighel
Browse files

feat: configurable telemetry display — data-map, data-display, resolveValue



Introduce a two-axis per-element telemetry configuration system:

  data-display="pill|plain|raw"
    pill  — colored Bootstrap badge (default for inline status badges)
    plain — plain monospace text with semantic color (default for info_list)
    raw   — no-op (SVG elements, special cases)

  data-map="bool_yesno|bool_onoff|open_state|camera_state|..."
    Translates raw API values to display strings before styling.
    Mirrors noctua.config.constants. Resolves the 0/1 ASCOM integer
    ambiguity (e.g. dome shutter: 0=Open, 1=Closed via open_state map).

ui-core.js:
  - Add noctuaMaps, applyMap(), resolveValue() (shared resolution
    algorithm, eliminates copy-paste between status-stream and status-view)
  - applyVarStatusStyles() now owns the full class (pill vs plain mode)
  - UI_CONFIG.COLORS gains text_pill / text_plain per state

status-stream.js: use resolveValue(); apply map then transform pipeline
status-view.js:   use resolveValue(); add data-transform support;
                  dynamically created <var> elements get data-display="plain"
widgets.html:     emit data-display and data-map from config/item dicts
init.html:        annotate boolean fields (bool_yesno), focuser (plain),
                  dome shutter (open_state)
control.html:     same for focus, dome shutter, stage position

Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 15079327
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@
      {{ w.widget_standard({
      "label": "Focus",
      "info": "telescope-focuser-position",
      "display": "plain",
      "transform": "round_0",
      "inputs": [{"value": 0}],
      "unit": "µm",
@@ -93,6 +94,7 @@
      {{ w.widget_shutter({
      "label":    "Dome",
      "info":     "/dome/shutter",
      "map":      "open_state",
      "move_url": "/dome/shutter/movement",
      }) }}
      
@@ -114,6 +116,8 @@
      {{ w.widget_standard({
      "label": "Position",
      "info": "stage-position",
      "display": "plain",
      "transform": "round_1",
      "inputs": [{"value": 1}],
      "unit": "mm",
      "buttons": [{"label": "Go", "endpoint": "/stage/position", "method": "PUT"}],
+14 −2
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
            {{ w.widget_standard({
                "label": "Power",
                "info": "telescope-power",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "On",  "endpoint": "/telescope/power", "val": true,  "method": "PUT"},
                    {"label": "Off", "endpoint": "/telescope/power", "val": false, "method": "PUT"}
@@ -23,6 +24,7 @@
            {{ w.widget_standard({
                "label": "Parked",
                "info": "telescope-coordinates-movement-atpark",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "Unpark", "endpoint": "/telescope/coordinates/movement/unpark", "method": "POST"},
                    {"label": "Park",   "endpoint": "/telescope/coordinates/movement/park",   "method": "POST"}
@@ -32,6 +34,7 @@
            {{ w.widget_standard({
                "label": "Tracking",
                "info": "telescope-coordinates-tracking",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "Point webcam",  "endpoint": "/telescope/coordinates/movement/altaz",  "val": [25, 85],  "method": "POST"},
                    {"label": "Track Polaris", "endpoint": "/telescope/coordinates/movement/radec",  "val": "Polaris", "method": "POST"}
@@ -41,6 +44,7 @@
            {{ w.widget_standard({
                "label": "Petals",
                "info": "telescope-cover",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "Open",  "endpoint": "/telescope/cover/movement", "val": true,  "method": "POST"},
                    {"label": "Close", "endpoint": "/telescope/cover/movement", "val": false, "method": "POST"}
@@ -60,8 +64,9 @@

            {{ w.widget_standard({
                "label": "Focus",
                "transform": "round_0",            
                "info": "telescope-focuser-position",
                "display": "plain",
                "transform": "round_0",
                "inputs": [{"value": 0}],
                "unit": "µm",
                "buttons": [{"label": "Set", "endpoint": "/telescope/focuser/movement", "method": "PUT"}],
@@ -116,6 +121,7 @@
            {{ w.widget_standard({
                "label": "Parked",
                "info": "dome-position-parked",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "Park", "endpoint": "/dome/position/movement/park", "method": "POST"},
                    {"label": "Stop", "endpoint": "/dome/position/movement",      "method": "DELETE"}
@@ -125,6 +131,7 @@
            {{ w.widget_shutter({
                "label":    "Shutter",
                "info":     "/dome/shutter",
                "map":      "open_state",
                "move_url": "/dome/shutter/movement",
            }) }}
        </section>
@@ -150,6 +157,7 @@
            {{ w.widget_standard({
                "label": "Power",
                "info": "stage-power",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "On",  "endpoint": "/stage/power", "val": true,  "method": "PUT"},
                    {"label": "Off", "endpoint": "/stage/power", "val": false, "method": "PUT"}
@@ -176,6 +184,7 @@
            {{ w.widget_standard({
                "label": "Power",
                "info": "camera-power",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "On",  "endpoint": "/camera/power", "val": true,  "method": "PUT"},
                    {"label": "Off", "endpoint": "/camera/power", "val": false, "method": "PUT"}
@@ -185,6 +194,7 @@
            {{ w.widget_standard({
                "label": "Cooling",
                "info": "camera-cooler",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "On",  "endpoint": "/camera/cooler", "val": true,  "method": "PUT"},
                    {"label": "Off", "endpoint": "/camera/cooler", "val": false, "method": "PUT"}
@@ -210,6 +220,7 @@
            {{ w.widget_standard({
                "label": "Power",
                "info": "camera2-power",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "On",  "endpoint": "/camera2/power", "val": true,  "method": "PUT"},
                    {"label": "Off", "endpoint": "/camera2/power", "val": false, "method": "PUT"}
@@ -219,6 +230,7 @@
            {{ w.widget_standard({
                "label": "Cooling",
                "info": "camera2-cooler",
                "map": "bool_yesno",
                "buttons": [
                    {"label": "On",  "endpoint": "/camera2/cooler", "val": true,  "method": "PUT"},
                    {"label": "Off", "endpoint": "/camera2/cooler", "val": false, "method": "PUT"}
+17 −8
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@
<fieldset class="widget-universal" id="onoff-{{ safe_id }}">
    <div class="input-group input-group-sm">
      <span class="col-md input-group-text status-container">
        <var class="badge bg-secondary w-100 font-monospace" data-status="{{ status }}" >Unk</var>
        <var class="badge bg-secondary w-100 font-monospace" data-status="{{ status }}" data-display="pill">Unk</var>
      </span>
      <button class="col-md btn btn-outline-primary btn-universal btn-onoff"
              data-method="PUT"
@@ -31,7 +31,11 @@
    <div class="input-group input-group-sm">
      {% if config.info %}
      <span class="col-md-2 input-group-text status-container">
        <var class="badge bg-secondary w-100 font-monospace" data-status="{{ config.info.strip('/') | replace('/', '-') }}"{% if config.transform %} data-transform="{{ config.transform }}"{% endif %}>Unk</var>
        <var class="badge bg-secondary w-100 font-monospace"
             data-status="{{ config.info.strip('/') | replace('/', '-') }}"
             data-display="{{ config.display | default('pill') }}"
             {% if config.map %} data-map="{{ config.map }}"{% endif %}
             {% if config.transform %} data-transform="{{ config.transform }}"{% endif %}>Unk</var>
      </span>
      {% endif %}

@@ -86,7 +90,11 @@

      {% if config.info %}
      <span class="col-md-2 input-group-text status-container">
        <var class="badge bg-secondary w-100 font-monospace" data-status="{{ config.info.strip('/') | replace('/', '-') }}"{% if config.transform %} data-transform="{{ config.transform }}"{% endif %}>Unk</var>
        <var class="badge bg-secondary w-100 font-monospace"
             data-status="{{ config.info.strip('/') | replace('/', '-') }}"
             data-display="{{ config.display | default('pill') }}"
             {% if config.map %} data-map="{{ config.map }}"{% endif %}
             {% if config.transform %} data-transform="{{ config.transform }}"{% endif %}>Unk</var>
      </span>
      {% endif %}

@@ -148,10 +156,11 @@
{% for item in items %}
<small class="col d-flex gap-2 mt-1 text-success">
  {% if item.label %}{{ item.label }}:
  <var class="status-var text-end font-monospace"
  <var class="font-monospace text-light"
       data-status="{{ item.status }}"
       {% if item.transform %} data-transform="{{ item.transform }}"{% endif %}
       style="opacity: 1;">N/A</var>{% endif %}
       data-display="{{ item.display | default('plain') }}"
       {% if item.map %} data-map="{{ item.map }}"{% endif %}
       {% if item.transform %} data-transform="{{ item.transform }}"{% endif %}>N/A</var>{% endif %}
</small>
{% endfor %}
{% endmacro %}
+22 −49
Original line number Diff line number Diff line
// status-stream.js
// Standard telemetry listener updating control panels with data-status attributes.

import { parseStatusKey, applyVarStatusStyles } from './ui-core.js';
import { applyVarStatusStyles, resolveValue, applyMap } from './ui-core.js';

document.addEventListener('DOMContentLoaded', () => {
    const previousState = {};

    document.addEventListener('noctua-telemetry', (event) => {
        const msg = event.detail;

        if (!msg || !msg.name.startsWith('all-')) return;

        const subsystem = msg.name.replace('all-', '');
@@ -19,35 +18,19 @@ document.addEventListener('DOMContentLoaded', () => {
        const elements = document.querySelectorAll(`[data-status^="${subsystem}-"]`);

        elements.forEach(el => {
            // SCELTA REGISTRATA: Bypass completo per gli elementi SVG nel flusso di telemetria
            if (el instanceof SVGElement) return;

            const statusKey = el.dataset.status;

            // --- ALGORITMO DI PARSING A RITROSO (CENTRALIZZATO) ---
            const parsed = parseStatusKey(statusKey);
            if (parsed.subsystem !== subsystem) return;

            let endpoint = '';
            let subProperty = '';
            const resolved = resolveValue(data, statusKey);
            if (!resolved) return;
            if (resolved.parsed.subsystem !== subsystem) return;

            if (data[parsed.fullPath] !== undefined) {
                endpoint = parsed.fullPath;
            } else if (parsed.segments.length > 1) {
                endpoint = parsed.segments.slice(0, -1).join('-');
                subProperty = parsed.segments[parsed.segments.length - 1];
            } else {
                endpoint = parsed.fullPath;
            }
            const { endpointData } = resolved;

            const endpointData = data[endpoint];
            if (endpointData === undefined) return;

            // Gestione dei badge di errore (confronto atomico dello stato di errore)
            // Error badge: keyed as "<subsystem>-<endpoint>-error"
            if (statusKey.endsWith('-error')) {
                const errors = endpointData.error;
                
                // Verifica di errore robusta sia per array che per stringhe piatte
                const hasErrors = errors && (Array.isArray(errors) ? errors.length > 0 : String(errors).trim() !== '');
                const displayValue = hasErrors ? 'ERR' : 'OK';

@@ -62,9 +45,7 @@ document.addEventListener('DOMContentLoaded', () => {

                el.textContent = displayValue;
                el.setAttribute('class', `badge ${hasErrors ? 'bg-danger' : 'bg-success'}`);
                
                if (hasErrors) {
                    // RISOLUZIONE BUG: Esegue il join solo se si tratta di un array di errori
                    const errorText = Array.isArray(errors) ? errors.join(', ') : String(errors);
                    el.setAttribute('title', errorText);
                } else {
@@ -73,40 +54,32 @@ document.addEventListener('DOMContentLoaded', () => {
                return;
            }

            let core = (endpointData && typeof endpointData === 'object' && endpointData.response !== undefined) 
                       ? endpointData.response 
                       : endpointData;
            let finalValue = resolved.value;

            let finalValue = core;

            if (subProperty && core && typeof core === 'object') {
                finalValue = core[subProperty];
            }

            if (parsed.index !== null && Array.isArray(finalValue)) {
                finalValue = finalValue[parsed.index];
            }
            // 1. Apply semantic map (data-map) — e.g. bool_yesno, open_state
            finalValue = applyMap(finalValue, el.dataset.map);

            // 2. Apply numeric transform (data-transform) — e.g. round_0, deg_to_arcsec
            const transformName = el.dataset.transform;
            if (transformName && window.noctuaTransforms && typeof window.noctuaTransforms[transformName] === 'function') {
            if (transformName && window.noctuaTransforms?.[transformName]) {
                try {
                    if (finalValue !== null && finalValue !== undefined) {
                        finalValue = window.noctuaTransforms[transformName](finalValue);
                    }
                } catch (e) {
                    console.warn(`status-stream: Transform '${transformName}' failed on value ${finalValue}`, e);
                    console.warn(`status-stream: transform '${transformName}' failed on`, finalValue, e);
                }
            }

            // 3. Format for display
            let displayValue = (finalValue === null || finalValue === undefined) ? 'N/A' : finalValue;
            
            if (typeof displayValue === 'boolean') {
            // Legacy fallback: convert bare booleans when no data-map is set
            if (!el.dataset.map && typeof displayValue === 'boolean') {
                displayValue = displayValue ? 'Yes' : 'No';
            }

            const valStr = displayValue.toString();

            // 1. Aggiornamento del Contenuto Testuale
            // 4. Update DOM
            if (el.tagName.toLowerCase() === 'input') {
                if (el.type === 'checkbox') el.checked = (valStr === 'Yes');
                else el.value = valStr;
@@ -114,14 +87,14 @@ document.addEventListener('DOMContentLoaded', () => {
                el.textContent = valStr;
            }

            // 2. Applicazione dello stile dinamico
            // 5. Apply visual style (pill / plain, driven by data-display)
            applyVarStatusStyles(el, finalValue);

            // 3. Rilevamento dei Cambiamenti Atomici (FOGLIA) e Animazione Pulse
            // 6. Pulse animation on change (tracked against raw value before map/transform)
            const rawValue = resolved.value;
            const hasChanged = previousState[subsystem][statusKey] !== undefined &&
                               previousState[subsystem][statusKey] !== finalValue;
            
            previousState[subsystem][statusKey] = finalValue;
                               previousState[subsystem][statusKey] !== rawValue;
            previousState[subsystem][statusKey] = rawValue;

            if (hasChanged) {
                el.classList.add('pulse-update');
+72 −121

File changed.

Preview size limit exceeded, changes collapsed.

Loading