Commit 2b05e73e authored by vertighel's avatar vertighel
Browse files

dev

parent 96a9c456
Loading
Loading
Loading
Loading
Loading
+44 −44
Original line number Diff line number Diff line
{% extends "base.html" %}
{% import "macros/widgets.html" as w %}
{% import "macros/sections/control_panel.html" as ctrl %}
{% import "macros/sections/mode_panels.html" as mode %}
{% import "macros/sections/webcam_panel.html" as webcam %}
{% import "macros/sections/synoptic_panel.html" as synoptic %}

@@ -20,9 +21,7 @@
                "buttons": [{"label": "Set", "endpoint": "/telescope/focuser", "method": "PUT"}],
                "info_list": [
                    {"label": "moving", "status": "telescope-focuser-movement"},
            {"label": "pos", "status": "telescope-focuser-position", "transform": "round_1"},


                    {"label": "pos", "status": "telescope-focuser-position", "transform": "round_1"}
                ]
            }) }}
        </div>
@@ -32,26 +31,22 @@
        </div>
    </section>

    <!-- CENTER: Mode Selection & Camera -->
    <!-- CENTER: Mode selector + per-mode framing & observation forms -->
    <section class="col-xl-4 col-lg-6">
        <!-- The Mode Selector dictates which station/camera is active -->
        {{ ctrl.observing_mode_selector() }}

        {{ ctrl.camera_windowing() }}
        {{ ctrl.observing_mode_selector() }}

        {{ ctrl.camera_exposure() }}
        {{ mode.imaging_panel() }}
        {{ mode.spectro_panel() }}
        {{ mode.echelle_panel() }}

        <div class="card bg-black border-secondary p-2 small font-monospace text-success">
            <div>Mode: <span id="current-mode-label" class="text-info fw-bold">Imaging</span></div>
            <div>Camera State: <var data-status="camera-snapshot-state" data-status-suffix="-snapshot-state">N/A</var></div>
            <div>Cooler: <var data-status="camera-cooler" data-status-suffix="-cooler">N/A</var> (<var data-status="camera-settings-temperature" data-status-suffix="-settings-temperature">N/A</var>°C)</div>
        </div>
    </section>

    <!-- RIGHT: Monitors -->
    <section class="col-xl-4 col-12">
        <div class="card bg-dark border-secondary h-100 shadow-sm">
            <div class="card-header p-0 border-secondary d-flex justify-content-between align-items-center">
      <div class="card bg-dark border-secondary shadow-sm">
        
        <div class="card-header p-0 border-secondary">
          <ul class="nav nav-tabs border-0" role="tablist">
            <li class="nav-item"><button class="nav-link active py-2" data-bs-toggle="tab" data-bs-target="#mon-fits">FITS Viewer</button></li>
            <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-webcam">Webcam</button></li>
@@ -60,26 +55,31 @@
          </ul>
          <small class="text-muted px-3" id="active-station-id">STATION1</small>
        </div>
            <div class="card-body p-0 bg-black position-relative" style="min-height: 500px;">
                <div class="tab-content h-100">
                    <div class="tab-pane fade show active h-100" id="mon-fits">
        
        <div class="tab-content">

          <div class="tab-pane fade show active" id="mon-fits">
            <div id="fits-viewer-container" class="h-100 overflow-auto p-2">
              {{ ctrl.fits_viewer_section('/api') }}
            </div>
          </div>
                    <div class="tab-pane fade show active" id="mon-webcam">

          <div class="tab-pane fade" id="mon-webcam">
            {{ webcam.webcam_panel() }}
          </div>

          <div class="tab-pane fade" id="mon-synoptic">
            {{ synoptic.synoptic_panel() }}
          </div>

          <div class="tab-pane fade" id="mon-output">
            <div id="sequencer-output-display" class="p-3 text-info font-monospace small">
              Waiting for output data...
            </div>
          </div>

        </div>
            </div>
        
      </div>
    </section>

+22 −17
Original line number Diff line number Diff line
@@ -220,14 +220,18 @@

    </section>
    
    <!-- RIGHT: Monitors -->
    <section class="col-xl-4 col-lg-12">
      
      <div class="card bg-dark border-secondary shadow-sm">
        
        <div class="card-header p-0 border-secondary">
          <ul class="nav nav-tabs border-0" role="tablist">
            <li class="nav-item"><button class="nav-link active py-2" data-bs-toggle="tab" data-bs-target="#init-webcam">Webcam</button></li>
            <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#init-synoptic">Synoptic</button></li>
          </ul>
        </div>
        
        <div class="tab-content">
          <div class="tab-pane fade show active" id="init-webcam">
            {{ webcam.webcam_panel() }}
@@ -236,6 +240,7 @@
            {{ synoptic.synoptic_panel() }}
          </div>
        </div>
        
      </div>
    </section>
    
+68 −136
Original line number Diff line number Diff line
// control.js
// Logic for the Control page: Mode switching, Stage movement, Expose payload.
// control.js — Control page: mode switching, stage movement, expose dispatch.

import { showToast, setInputState } from './ui.js';

// ---------------------------------------------------------------------------
// Mode configuration — single source of truth for per-mode camera behaviour.
// To add station3: set enabled=true and fill cameraDevice/cameraPrefix/recenterTarget.
// ---------------------------------------------------------------------------
const MODE_CONFIG = {
    station1: {
        label:          'Imaging',
        cameraPrefix:   '/camera',
        cameraDevice:   'cam',
        viewerCameras:  ['scicam', 'teccam'],
        showFilter:     true,
        recenterTarget: 'scicam',
        template:       'snapshot_imaging',
        enabled:        true,
    },
    station2: {
        label:          'Spectro',
        cameraPrefix:   '/camera2',
        cameraDevice:   'cam2',
        viewerCameras:  ['scicam', 'teccam'],
        showFilter:     false,
        recenterTarget: 'teccam',
        template:       'snapshot_spectro',
        enabled:        true,
    },
    station3: {
        label:          'Échelle',
        cameraPrefix:   '/camera3',   // cam3 — not yet connected
        cameraDevice:   'cam3',
        viewerCameras:  ['scicam', 'teccam'],  // update when camera3/teccam3 added to viewer.ini
        showFilter:     false,
        recenterTarget: 'teccam3',    // not yet connected
        template:       'observation',
        enabled:        false,
    },
// Map station → { panel id suffix, FITS viewer combo, sequencer template, camera device }
const MODES = {
    station1: { panel: 'imaging', combo: 'combo1', template: 'snapshot_imaging', camera: 'cam'  },
    station2: { panel: 'spectro', combo: 'combo2', template: 'snapshot_spectro', camera: 'cam2' },
    station3: { panel: 'echelle', combo: 'combo3', template: null,               camera: null, disabled: true },
};

// Framing presets shared across all modes (path is appended to cameraPrefix).
const FRAMING_PRESETS = [
    { label: 'Full Frame',  path: '/frame/full'  },
    { label: 'Half Frame',  path: '/frame/half'  },
    { label: "Small Frame", path: '/frame/small' },
];

// ---------------------------------------------------------------------------

document.addEventListener('DOMContentLoaded', () => {
    const stationLabel   = document.getElementById('active-station-id');
    const modeLabel      = document.getElementById('current-mode-label');
    const framingPresets = document.getElementById('framing-presets');
    const rowFilter      = document.getElementById('row-filter');
    const spanRecenter   = document.getElementById('span-recenter-target');
    const btnExpose      = document.getElementById('btn-camera-expose');

    let activeStation = 'station1';
    const stationLabel = document.getElementById('active-station-id');

    // Disable mode buttons for stations not yet available
    Object.entries(MODE_CONFIG).forEach(([station, cfg]) => {
        if (cfg.enabled) return;
    // -----------------------------------------------------------------------
    // Disable radio buttons for unavailable modes
    // -----------------------------------------------------------------------
    Object.entries(MODES).forEach(([station, cfg]) => {
        if (!cfg.disabled) return;
        const radio = document.querySelector(`input[name="obs-mode"][value="${station}"]`);
        const label = radio ? document.querySelector(`label[for="${radio.id}"]`) : null;
        if (radio) radio.disabled = true;
@@ -69,70 +25,29 @@ document.addEventListener('DOMContentLoaded', () => {
    });

    // -----------------------------------------------------------------------
    // applyMode — updates all mode-dependent UI regions at once
    // applyMode — show the right mode panel and FITS viewer combo
    // -----------------------------------------------------------------------
    function applyMode(station) {
        const cfg = MODE_CONFIG[station];
        if (!cfg?.enabled) return;

        activeStation = station;

        // 1. Rebuild framing preset buttons with the correct camera prefix
        if (framingPresets) {
            framingPresets.innerHTML = '';
            FRAMING_PRESETS.forEach(preset => {
                const btn = document.createElement('button');
                btn.className      = 'btn btn-outline-primary btn-universal';
                btn.dataset.url    = `${cfg.cameraPrefix}${preset.path}`;
                btn.dataset.method = 'PUT';
                btn.textContent    = preset.label;
                framingPresets.appendChild(btn);
            });
        }

        // 2. Show/hide filter selector
        if (rowFilter) rowFilter.classList.toggle('d-none', !cfg.showFilter);
        const mode = MODES[station];
        if (!mode || mode.disabled) return;

        // 3. Update recenter camera label
        if (spanRecenter) spanRecenter.textContent = cfg.recenterTarget;

        // 4. Update station / mode display labels
        if (stationLabel) stationLabel.textContent = station.toUpperCase();
        if (modeLabel)    modeLabel.textContent     = cfg.label;

        // 5. Switch FITS viewer to the matching combo panel
        switchViewer(station);

        // 6. Keep expose button aware of active station
        if (btnExpose) btnExpose.dataset.activeStation = station;

        // 7. Update data-subsystem on camera cards so dependency-guard can track them
        const cameraSubsystem = cfg.cameraPrefix.replace(/^\//, '').split('/')[0];
        framingPresets?.closest('.card.bg-dark')?.setAttribute('data-subsystem', cameraSubsystem);
        btnExpose?.closest('.card.bg-dark')?.setAttribute('data-subsystem', cameraSubsystem);

        // 8. Update data-status on elements whose status key depends on the active camera
        document.querySelectorAll('[data-status-suffix]').forEach(el => {
            el.dataset.status = `${cameraSubsystem}${el.dataset.statusSuffix}`;
        document.querySelectorAll('.mode-panel').forEach(el => {
            el.classList.toggle('d-none', el.id !== `mode-panel-${mode.panel}`);
        });
    }

    const comboMap = { station1: 'combo1', station2: 'combo2', station3: 'combo3' };

    function switchViewer(station) {
        const target = comboMap[station];
        document.querySelectorAll('[data-combo-view]').forEach(el => {
            el.classList.toggle('d-none', el.dataset.comboView !== target);
            el.classList.toggle('d-none', el.dataset.comboView !== mode.combo);
        });

        if (stationLabel) stationLabel.textContent = station.toUpperCase();
    }

    // Mode radio buttons
    document.querySelectorAll('input[name="obs-mode"]').forEach(radio => {
        radio.addEventListener('change', e => applyMode(e.target.value));
    });

    // -----------------------------------------------------------------------
    // CHECK target — resolve name/coords and update input in place
    // CHECK target — resolve name/coords and update input
    // -----------------------------------------------------------------------
    const inputRadec = document.getElementById('in-radec');
    const btnCheck   = document.getElementById('btn-check-target');
@@ -140,11 +55,9 @@ document.addEventListener('DOMContentLoaded', () => {
    btnCheck?.addEventListener('click', async () => {
        const target = inputRadec?.value?.trim();
        if (!target) return;

        const origHtml    = btnCheck.innerHTML;
        btnCheck.disabled = true;
        btnCheck.innerHTML = '<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>';

        try {
            const res  = await fetch('/api/telescope/coordinates/resolve', {
                method:  'POST',
@@ -152,7 +65,6 @@ document.addEventListener('DOMContentLoaded', () => {
                body:    JSON.stringify(target),
            });
            const data = await res.json();

            if (data.response) {
                inputRadec.value = data.response;
                setInputState(inputRadec, 'valid');
@@ -167,14 +79,13 @@ document.addEventListener('DOMContentLoaded', () => {
        }
    });

    // Reset border when the user starts typing again
    inputRadec?.addEventListener('input', () => setInputState(inputRadec, 'reset'));

    // -----------------------------------------------------------------------
    // Tab-aware Slew button: switch URL and inputs when pointing tab changes
    // Tab-aware Slew button
    // -----------------------------------------------------------------------
    const btnSlew = document.getElementById('btn-mount-slew');
    document.getElementById('pointing-tabs')?.addEventListener('shown.bs.tab', (e) => {
    document.getElementById('pointing-tabs')?.addEventListener('shown.bs.tab', e => {
        if (!btnSlew) return;
        const isAltAz = e.target.dataset.bsTarget === '#tab-altaz';
        btnSlew.dataset.url    = isAltAz ? '/telescope/coordinates/movement/altaz'
@@ -187,7 +98,7 @@ document.addEventListener('DOMContentLoaded', () => {
    // -----------------------------------------------------------------------
    const inputRel = document.getElementById('stage-rel-val');

    const moveStageRelative = async (direction) => {
    async function moveStageRelative(direction) {
        const step  = parseFloat(inputRel?.value) || 0;
        const delta = direction * step;
        try {
@@ -202,34 +113,55 @@ document.addEventListener('DOMContentLoaded', () => {
        } catch {
            showToast('Stage relative move failed', 'danger');
        }
    };
    }

    document.getElementById('btn-stage-rel-plus') ?.addEventListener('click', () => moveStageRelative(+1));
    document.getElementById('btn-stage-rel-minus')?.addEventListener('click', () => moveStageRelative(-1));

    // -----------------------------------------------------------------------
    // EXPOSE — build sequencer payload from active mode config + form values
    // EXPOSE helpers
    // -----------------------------------------------------------------------
    if (btnExpose) {
        btnExpose.addEventListener('mousedown', () => {
            const cfg    = MODE_CONFIG[activeStation];
    function readForm(form) {
        const g = name => form.querySelector(`[name="${name}"]`);
        const params = {
                camera:          cfg.cameraDevice,
                recenter_camera: cfg.recenterTarget,
            object:      g('object')?.value    || 'test',
            frametype:   g('frametype')?.value || 'Light',
            exptime:     parseFloat(g('exptime')?.value  || 10),
            repeat:      parseInt(g('repeat')?.value     || 1),
            binning:     parseInt(g('binning')?.value    || 1),
            domeslewing: g('domeslewing')?.checked       || false,
            recenter:    g('recenter')?.checked          || false,
            box:         parseInt(g('box')?.value        || 300),
        };
        const filterEl = g('filter');
        if (filterEl) params.filter = filterEl.value;
        return params;
    }

    async function postExpose(template, camera, params) {
        try {
            await fetch('/api/sequencer/run', {
                method:  'POST',
                headers: { 'Content-Type': 'application/json' },
                body:    JSON.stringify({ template, params: { camera, ...params } }),
            });
        } catch {
            showToast('Sequencer POST failed', 'danger');
        }
    }

            document.querySelectorAll('[data-parameter^="observation-"]').forEach(input => {
                const key = input.dataset.parameter.replace('observation-', '');
                if (key === 'filter' && !cfg.showFilter) return;
                params[key] = (input.type === 'checkbox')
                    ? input.checked
                    : (isNaN(input.value) || input.value === '' ? input.value : parseFloat(input.value));
    document.getElementById('btn-imaging-expose')?.addEventListener('click', () => {
        const form = document.getElementById('form-imaging');
        if (form) postExpose('snapshot_imaging', 'cam', readForm(form));
    });

            btnExpose.dataset.payload = JSON.stringify({ template: cfg.template, params });
    document.getElementById('btn-spectro-expose')?.addEventListener('click', () => {
        const form = document.getElementById('form-spectro');
        if (form) postExpose('snapshot_spectro', 'cam2', readForm(form));
    });
    }

    // -----------------------------------------------------------------------
    // Init
    // -----------------------------------------------------------------------
    applyMode('station1');
});