Commit 3761c4b0 authored by vertighel's avatar vertighel
Browse files

loop-toggle: generalizza il bus in sync-bus.js, collega Exptime/Binning



Nuovo sync-bus.js: bus pub/sub generico broadcast(topic, camId, detail) /
onSync(topic, handler), estratto da loop-toggle.js (che ora lo usa per il
topic 'loop', nessun cambio di comportamento).

Nuovo uso: quando Camera nel pannello Guider è una scicam passiva, il
guider non pilota mai il proprio exptime/binning (vedi PASSIVE_CAMERAS in
guider.py — i valori reali sono quelli dell'Expose widget). I campi
Exptime/Binning del Guider si disabilitano e si allineano in tempo reale
a quelli dell'Expose (topic 'exptime'/'binning' sul bus); tornano
editabili quando Camera è una teccam.

Co-Authored-By: default avatarClaude Sonnet 5 <noreply@anthropic.com>
parent 69f9bb4a
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

import { showToast, setInputState } from './ui.js';
import { wireLoopToggle, notifyLoopMayHaveChanged } from './loop-toggle.js';
import { broadcast } from './sync-bus.js';

// Map station → { panel id suffix, FITS viewer combo, sequencer template, camera device, cam_id }
const MODES = {
@@ -177,6 +178,22 @@ document.addEventListener('DOMContentLoaded', () => {
        () => parseFloat(document.querySelector('[name="exptime"][form="form-spectro"]')?.value) || 1.0,
    );

    // -----------------------------------------------------------------------
    // EXPOSE — broadcast Exptime/Binning so the Guider panel can mirror them
    // when its Camera selector points at this scicam (see guider-panel.js).
    // -----------------------------------------------------------------------
    function wireFieldBroadcast(el, topic, camId) {
        if (!el || !camId) return;
        const emit = () => broadcast(topic, camId, { value: el.value });
        el.addEventListener('input', emit);
        el.addEventListener('change', emit);
    }

    wireFieldBroadcast(document.querySelector('[name="exptime"][form="form-imaging"]'), 'exptime', MODES.station1.camId);
    wireFieldBroadcast(document.getElementById('binning-sel-imaging'),                  'binning', MODES.station1.camId);
    wireFieldBroadcast(document.querySelector('[name="exptime"][form="form-spectro"]'), 'exptime', MODES.station2.camId);
    wireFieldBroadcast(document.getElementById('binning-sel-spectro'),                  'binning', MODES.station2.camId);

    // -----------------------------------------------------------------------
    // Init
    // -----------------------------------------------------------------------
+40 −5
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@ import { get as getViewer } from './viewer/viewer-registry.js';
import { resolveValue }        from './ui-core.js';
import { computeTargetCoords } from './guider-target-coords.js';
import { wireLoopToggle, notifyLoopMayHaveChanged } from './loop-toggle.js';
import { onSync }              from './sync-bus.js';

// --- Constants ---

@@ -26,16 +27,35 @@ const ACTUATOR_PARAMS = {
    ao_x_offload: { actuator: 'ao_x', ao_offload: true  },
};

// --- 1. Camera → exptime ---
// --- 1. Camera → exptime/binning ---

/**
 * No-op for now: exptime enable/disable is driven by scicam telemetry globally.
 * Kept as a hook for future per-camera UI changes on camera switch.
 * When Camera is a passive scicam, the guider never drives its own
 * exptime/binning (see PASSIVE_CAMERAS in guider.py — the real values are
 * whatever the Expose widget has set, since the guider only reads the last
 * FITS Expose wrote). Mirror them read-only in that case; re-enable
 * independent editing when Camera is a teccam, which the guider does drive.
 *
 * Relies on the guider panel and the Expose widget sharing the same
 * panelId/camera_id prefix ("imaging"/"spectro") for their form fields.
 *
 * @param {string} panelId
 * @param {string} camera
 * @param {string} camera - device name from the Camera <select> (e.g. "cam1").
 */
function onCameraChange(panelId, camera) {  // eslint-disable-line no-unused-vars
function onCameraChange(panelId, camera) {
    const exptimeEl = document.getElementById(`guider-exptime-val-${panelId}`);
    const binningEl = document.getElementById(`guider-binning-sel-${panelId}`);
    if (!exptimeEl || !binningEl) return;

    const isScicam = PASSIVE_CAMERAS.has(camera);
    exptimeEl.disabled = isScicam;
    binningEl.disabled = isScicam;
    if (!isScicam) return;

    const exposeExptime = document.querySelector(`[name="exptime"][form="form-${panelId}"]`);
    const exposeBinning = document.getElementById(`binning-sel-${panelId}`);
    if (exposeExptime) exptimeEl.value = exposeExptime.value;
    if (exposeBinning) binningEl.value = exposeBinning.value;
}

// --- 2. Target → coord fields ---
@@ -532,6 +552,21 @@ function initPanel(panelId) {
        () => parseFloat(document.getElementById(`guider-exptime-val-${panelId}`)?.value) || 1.0,
    );

    // Live-mirror Exptime/Binning from the Expose widget while Camera is a
    // passive scicam (see onCameraChange for the enable/disable + initial pull).
    onSync('exptime', ({ camId, value }) => {
        const sel = getSelectedCamera(panelId);
        if (camId !== sel.camId || !PASSIVE_CAMERAS.has(sel.device)) return;
        const el = document.getElementById(`guider-exptime-val-${panelId}`);
        if (el) el.value = value;
    });
    onSync('binning', ({ camId, value }) => {
        const sel = getSelectedCamera(panelId);
        if (camId !== sel.camId || !PASSIVE_CAMERAS.has(sel.device)) return;
        const el = document.getElementById(`guider-binning-sel-${panelId}`);
        if (el) el.value = value;
    });

    cameraEl?.addEventListener('change', e => {
        onCameraChange(panelId, e.target.value);
        loopCtrl?.refresh();
+9 −13
Original line number Diff line number Diff line
@@ -6,16 +6,12 @@
//
// The same cam_id can be watched by more than one switch at once (e.g. the
// Guider panel's Camera selector currently pointing at scicam1, and the
// imaging Expose widget's own switch for scicam1). A DOM event keeps every
// switch watching a given cam_id in sync whenever any of them changes it —
// including indirectly, e.g. Expose/Guide forcing looping off server-side.
// imaging Expose widget's own switch for scicam1). The 'loop' topic on the
// shared sync-bus keeps every switch watching a given cam_id in sync
// whenever any of them changes it — including indirectly, e.g. Expose/Guide
// forcing looping off server-side.

const LOOP_EVENT = 'noctua-loop-changed';

function broadcastLoopChange(camId) {
    if (!camId) return;
    document.dispatchEvent(new CustomEvent(LOOP_EVENT, { detail: { camId } }));
}
import { broadcast, onSync } from './sync-bus.js';

/**
 * Call after any action that may have changed a camera's loop state
@@ -27,7 +23,7 @@ function broadcastLoopChange(camId) {
 * @param {string|null} camId
 */
export function notifyLoopMayHaveChanged(camId) {
    broadcastLoopChange(camId);
    broadcast('loop', camId);
}

/**
@@ -72,14 +68,14 @@ export function wireLoopToggle(el, getCamId, getExptime) {
        try {
            const res = await fetch(`/api/${camId}/loop`, options);
            if (!res.ok) el.checked = !wantOn;
            else broadcastLoopChange(camId);
            else broadcast('loop', camId);
        } catch {
            el.checked = !wantOn;
        }
    });

    document.addEventListener(LOOP_EVENT, e => {
        if (e.detail?.camId === getCamId()) refresh();
    onSync('loop', ({ camId }) => {
        if (camId === getCamId()) refresh();
    });

    refresh();
+27 −0
Original line number Diff line number Diff line
// sync-bus.js
// Generic cross-widget value-change bus, keyed by (topic, cam_id).
// Lets independent UI widgets that happen to show the same camera stay
// in sync without knowing about each other directly — e.g. the Guider
// panel's Loop switch and the Expose widget's Loop switch for scicam1,
// or the Guider panel's Exptime/Binning fields mirroring the Expose
// widget's when Camera is a passive scicam.

/**
 * @param {string} topic - e.g. 'loop', 'exptime', 'binning'
 * @param {string|null} camId
 * @param {object} [detail] - extra data delivered to listeners
 */
export function broadcast(topic, camId, detail = {}) {
    if (!camId) return;
    document.dispatchEvent(new CustomEvent(`noctua-sync:${topic}`, {
        detail: { camId, ...detail },
    }));
}

/**
 * @param {string} topic
 * @param {function({camId: string, [key: string]: any}): void} handler
 */
export function onSync(topic, handler) {
    document.addEventListener(`noctua-sync:${topic}`, e => handler(e.detail));
}