Commit 9345f255 authored by vertighel's avatar vertighel
Browse files

Synoptic

parent 5434a3f2
Loading
Loading
Loading
Loading
+356 −278

File changed.

Preview size limit exceeded, changes collapsed.

+342 −220
Original line number Diff line number Diff line

// Use the global variable defined in the HTML template
const svgUrl = window.SYNOPTIC_SVG_URL;
const container = document.getElementById('svg-container');

let refGeom = null;
let telGeom = null;
let stageRefGeom = null;
let stageMirrorGeom = null;

// --- Drawing Calibration ---
// If in Inkscape you drew the dome slit facing NORTH (Bottom), leave this at 0.
// If you drew it facing SOUTH (Top), set it to 180.
const DOME_BASE_ROTATION = 180; 

// Maximum shutter opening distance in pixels for each half
const SHUTTER_MAX_OPEN_PX = 8;

// Maximum opening distance in pixels for each telescope petal
const PETALS_MAX_OPEN_PX = 2;


/**
     * Parse SVG bounding boxes to establish the coordinate system.
 * Parse SVG bounding boxes to establish the coordinate systems.
 * Sets transform-origins for CSS manipulation.
 *
 * Returns
@@ -19,16 +31,12 @@

function initializeGeometry() {
    
    // --- Dome & Telescope Geometry ---
    const refEl = document.getElementById('structure-reference');
    const telEl = document.getElementById('structure-telescope');
    const domeEl = document.getElementById('structure-dome');

        if (!refEl || !telEl) {
            console.warn("Synoptic: Missing #structure-reference or #structure-telescope in SVG.");
            return;
        }

        // getBBox() restituisce le coordinate non-trasformate locali dell'SVG
    if (refEl && telEl) {
        const refBBox = refEl.getBBox();
        refGeom = {
            cx: refBBox.x + refBBox.width / 2,
@@ -42,12 +50,92 @@
            cy: telBBox.y + telBBox.height / 2
        };

        // Fissa il perno di rotazione della cupola esattamente al centro del cerchio di riferimento
        if (domeEl) {
            domeEl.style.transformOrigin = `${refGeom.cx}px ${refGeom.cy}px`;
        }
    }

    // --- Linear Stage Geometry ---
    const stageRefEl = document.getElementById('stage-reference');
    const stageMirrorEl = document.getElementById('structure-stage-mirror');

    if (stageRefEl && stageMirrorEl) {
        const sRefBBox = stageRefEl.getBBox();
        // For the stage, we map the real-world 0-150mm range to the width of the reference box
        stageRefGeom = {
            startX: sRefBBox.x,                      // 0 mm
            endX: sRefBBox.x + sRefBBox.width,       // 150 mm
            widthPx: sRefBBox.width
        };

        const mBBox = stageMirrorEl.getBBox();
        stageMirrorGeom = {
            cx: mBBox.x + mBBox.width / 2
        };
    }
}


/**
 * Rotate the dome graphic using CSS to preserve Inkscape base transforms.
 *
 * Parameters
 * ----------
 * az : number
 *     The azimuth of the dome in degrees.
 *
 * Returns
 * -------
 * void
 */

function animateDome(az) {
    
    const domeEl = document.getElementById('structure-dome');
    if (!domeEl || !refGeom) return;

    // Direction: North (Bottom) -> East (Right) = Counter-Clockwise = Negative Rotation in SVG.
    const svgRotation = -az + DOME_BASE_ROTATION;
    
    domeEl.style.transform = `rotate(${svgRotation}deg)`;
}


/**
 * Translate the telescope graphic using CSS relative to its original drawn position.
 *
 * Parameters
 * ----------
 * alt : number
 *     Telescope altitude in degrees.
 * az : number
 *     Telescope azimuth in degrees.
 *
 * Returns
 * -------
 * void
 */

function animateTelescope(alt, az) {
    
    const telEl = document.getElementById('structure-telescope');
    if (!telEl || !refGeom || !telGeom) return;

    // Radius: Alt 90 = center, Alt 0 = outer edge
    const radius = refGeom.rMax * (1 - (alt / 90.0));

    const azRad = az * (Math.PI / 180.0);
    
    // North at bottom (+Y), East at right (+X)
    const targetX = refGeom.cx + radius * Math.sin(azRad);
    const targetY = refGeom.cy + radius * Math.cos(azRad);

    const deltaX = targetX - telGeom.cx;
    const deltaY = targetY - telGeom.cy;

    telEl.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
}


/**
 * Animate the dome shutter halves based on the open state.
@@ -69,17 +157,15 @@

    if (!dome1El || !dome2El) return;

        // Ensure state is clamped between 0 and 1
    const clampedState = Math.max(0, Math.min(1, state));

        // Calculate translation in pixels (state 0 = 0px, state 1 = 8px)
    const offsetPx = clampedState * SHUTTER_MAX_OPEN_PX;

        // dome1 moves Left (negative X), dome2 moves Right (positive X)
    // dome1 moves Left (-X), dome2 moves Right (+X)
    dome1El.style.transform = `translate(-${offsetPx}px, 0px)`;
    dome2El.style.transform = `translate(${offsetPx}px, 0px)`;
}


/**
 * Animate the telescope cover petals based on the open state.
 * Moves four petals diagonally outward.
@@ -103,10 +189,7 @@

    if (!cover1El || !cover2El || !cover3El || !cover4El) return;

        // Ensure state is clamped between 0 and 1
    const clampedState = Math.max(0, Math.min(1, state));

        // Calculate translation in pixels (state 0 = 0px, state 1 = 2px)
    const offsetPx = clampedState * PETALS_MAX_OPEN_PX;

    // Cover 1: Left and Down (-X, +Y)
@@ -122,67 +205,103 @@
    cover4El.style.transform = `translate(${offsetPx}px, -${offsetPx}px)`;
}


/**
     * Rotate the dome graphic using CSS to preserve Inkscape base transforms.
 * Translate the mirror graphic along the linear stage axis.
 * Maps 0-150mm to the physical pixel width of the reference box.
 *
 * Parameters
 * ----------
     * az : number
     *     The azimuth of the dome in degrees.
 * posMm : number
 *     Stage position in millimeters (0 to 150).
 *
 * Returns
 * -------
 * void
 */

    function animateDome(az) {
function animateStage(posMm) {
    
        const domeEl = document.getElementById('structure-dome');
        if (!domeEl || !refGeom) return;
    const mirrorEl = document.getElementById('structure-stage-mirror');
    if (!mirrorEl || !stageRefGeom || !stageMirrorGeom) return;

        // Direzione: Nord (Basso) -> Est (Destra) = Antiorario = Rotazione Negativa in SVG.
        const svgRotation = -az + DOME_BASE_ROTATION;
    // Clamp value to physical limits
    const clampedPos = Math.max(0, Math.min(150, posMm));

        // Usiamo style.transform invece di setAttribute per non rompere il posizionamento di Inkscape
        domeEl.style.transform = `rotate(${svgRotation}deg)`;
    }
    // Calculate percentage along the rail (0.0 to 1.0)
    const percentage = clampedPos / 150.0;

    // Map percentage to target X pixel on screen
    const targetX = stageRefGeom.startX + (percentage * stageRefGeom.widthPx);

    // Calculate CSS translation relative to where the mirror was originally drawn
    const deltaX = targetX - stageMirrorGeom.cx;

    // Translate only on X axis
    mirrorEl.style.transform = `translate(${deltaX}px, 0px)`;
}
/**
     * Translate the telescope graphic using CSS relative to its original drawn position.
 * Forcefully apply fill color to an element and its inline styles.
 */
function applyFillForcefully(el, color) {
    el.setAttribute('fill', color);
    
    let style = el.getAttribute('style') || '';
    if (style.match(/fill\s*:/i)) {
        // Replace existing fill with the new color and force !important
        style = style.replace(/fill\s*:[^;]+;?/gi, `fill: ${color} !important;`);
    } else {
        // Append fill if it doesn't exist
        style += `; fill: ${color} !important;`;
    }
    el.setAttribute('style', style);
}

/**
 * Update the fill color of a PDU indicator SVG element.
 * Properly handles string anomalies and Inkscape Grouping <g>.
 *
 * Parameters
 * ----------
     * alt : number
     *     Telescope altitude in degrees.
     * az : number
     *     Telescope azimuth in degrees.
 * el : SVGElement
 *     The DOM element to colorize.
 * state : string or boolean or number
 *     'On', 'Off', True, False, 1, 0.
 *
 * Returns
 * -------
 * void
 */
function updateSwitchColor(el, state) {
    if (!el) return;

    function animateTelescope(alt, az) {
        
        const telEl = document.getElementById('structure-telescope');
        if (!telEl || !refGeom || !telGeom) return;
    const COLOR_OFF = "rgba(0, 255, 255, 0.3)";
    const COLOR_ON = "rgba(0, 255, 0, 0.9)";
    const COLOR_UNKNOWN = "rgba(128, 128, 128, 0.5)";

        // Raggio: Alt 90 = centro, Alt 0 = bordo esterno
        const radius = refGeom.rMax * (1 - (alt / 90.0));
    let targetColor = COLOR_UNKNOWN;

        const azRad = az * (Math.PI / 180.0);
    // 1. Normalize the state to avoid case-sensitivity or hidden spaces
    let normState = state;
    if (typeof state === 'string') {
        normState = state.trim().toLowerCase();
    }

        // Nord in basso (+Y), Est a destra (+X)
        const targetX = refGeom.cx + radius * Math.sin(azRad);
        const targetY = refGeom.cy + radius * Math.cos(azRad);
    // 2. Evaluate normalized state
    if (normState === "on" || normState === "yes" || normState === "true" || normState === 1 || normState === true) {
        targetColor = COLOR_ON;
    } else if (normState === "off" || normState === "no" || normState === "false" || normState === 0 || normState === false) {
        targetColor = COLOR_OFF;
    }

        // Calcola di quanti pixel deve spostarsi il telescopio rispetto a dove lo hai disegnato
        const deltaX = targetX - telGeom.cx;
        const deltaY = targetY - telGeom.cy;
    // 3. Apply color to the parent element
    applyFillForcefully(el, targetColor);

        // Applica lo spostamento relativo
        telEl.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
    // 4. Apply color to all graphical children (Crucial if the SVG ID is on a <g> Group)
    const children = el.querySelectorAll('path, rect, circle, ellipse, polygon');
    children.forEach(child => {
        applyFillForcefully(child, targetColor);
    });
}

// 1. Fetch and inject the SVG
@@ -190,7 +309,6 @@
    .then(response => response.text())
    .then(svgText => {
        container.innerHTML = svgText;
            // Allow the browser time to render the SVG before calculating Bounding Boxes
        setTimeout(initializeGeometry, 100);
    })
    .catch(err => console.error("Error loading SVG:", err));
@@ -198,10 +316,11 @@

// 2. Listen to Telemetry and update UI
document.addEventListener('noctua-telemetry', (e) => {
    
    const { name, data } = e.detail;
    const subsystem = name.replace('all-', '');

        // ---- A. Update Text Elements ----
    // ---- A. Update Text and Color Elements ----
    const svgElements = document.querySelectorAll(`[id^="telemetry:${subsystem}:"]`);

    svgElements.forEach(el => {
@@ -212,6 +331,14 @@
        const key = parts[3];

        if (data[device] && data[device].response !== undefined) {
            
            // Handle Color Indicators (Switches/PDUs)
            if (key === "indicator") {
                updateSwitchColor(el, data[device].response);
                return; // Stop here, do not change textContent
            }

            // Handle Text Values
            let value = "???";

            if (typeof data[device].response === 'object' && data[device].response !== null) {
@@ -232,31 +359,22 @@
        }
    });


    // ---- B. Animate Graphics ----
        if (subsystem === 'dome') {
    
            // Dome rotation
    if (subsystem === 'dome') {
        if (data.position && data.position.response) {
            const domeAz = data.position.response.azimuth;
                if (typeof domeAz === 'number') {
                    animateDome(domeAz);
                }
            if (typeof domeAz === 'number') animateDome(domeAz);
        }

            // Dome shutter opening
        if (data.shutter && data.shutter.response !== undefined) {
            const shutterState = data.shutter.response;
                // Assuming telemetry sends a float between 0 and 1
                if (typeof shutterState === 'number') {
                    animateShutter(shutterState);
                }
            if (typeof shutterState === 'number') animateShutter(shutterState);
        }
    }

        // Telescope movement
    if (subsystem === 'telescope') {
            
            // Telescope translation (Alt/Az mapping)
        if (data.coordinates && data.coordinates.response) {
            const altaz = data.coordinates.response.altaz;
            if (Array.isArray(altaz) && altaz.length === 2) {
@@ -264,13 +382,17 @@
            }
        }

            // Telescope cover petals opening
        if (data.cover && data.cover.response !== undefined) {
            const coverState = data.cover.response;
                // Assuming telemetry sends a float between 0 and 1
                if (typeof coverState === 'number') {
                    animatePetals(coverState);
            if (typeof coverState === 'number') animatePetals(coverState);
        }
    }

    if (subsystem === 'stage') {
        if (data.position && data.position.response !== undefined) {
            const stageMm = data.position.response;
            if (typeof stageMm === 'number') animateStage(stageMm);
        }
    }

});