Commit 159153e4 authored by vertighel's avatar vertighel
Browse files

Web: lock controls while acquiring; guider telemetry

Guider telemetry: /guider was never polled (it is not in api.ini), so
the guider aside and the Guide button lock never received data.
- new read-only GET /guider/status, polled by a "guider" telemetry loop
- aside keys guider-status-active, guider-status-last_correction-0/1

busy-guard.js greys out and locks the controls of a section while its
acquisition runs, from server telemetry (holds after reload, in other
browsers, and for OBs started by the scheduler):
- scicam section: Expose running -> all but Stop; loop on -> all but
  its loop switch
- teccam section: loop on -> all but its loop switch
- guider (now in its own section): Guide active -> all but Stop
Controls are locked with `inert`, not `disabled`, so the guard does not
interfere with controls disabled for other reasons. GET /sequencer/run
now reports the cam_id the template runs on. Replaces the ad-hoc Expose
and Guide button locks.

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 11162188
Loading
Loading
Loading
Loading
Loading
+4 −1
Changes for noctua/api/__init__.py: 4 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ from noctua.utils.logger import log
from .blocks import blocks_api
from .defaults import defaults_api
from .sequencer import sequencer_api, BobRun
from .guider import guider_api, Guider, Calibrate
from .guider import guider_api, Guider, GuiderStatus, Calibrate
from .guider_instance import guider

api_blueprint = Blueprint('api', __name__)
@@ -35,6 +35,9 @@ api_blueprint.register_blueprint(guider_api, url_prefix='/guider')

resource_registry['/sequencer/run'] = BobRun(seq)
resource_registry['/guider'] = Guider(guider)
# Polled by the "guider" telemetry loop (see web/__init__.py): the loop
# only picks up paths under /guider/, not /guider itself.
resource_registry['/guider/status'] = GuiderStatus(guider)
resource_registry['/guider/calibrate'] = Calibrate(guider)


+10 −0
Changes for noctua/api/guider.py: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -50,6 +50,15 @@ class Guider(BaseResource):
        return self.make_response(self.dev.status)


class GuiderStatus(BaseResource):
    """Read-only guider status, polled by the telemetry loop."""

    async def get(self):
        """Retrieve the guider status."""

        return self.make_response(self.dev.status)


class Calibrate(BaseResource):
    """Run AO-X calibration."""

@@ -93,4 +102,5 @@ class Calibrate(BaseResource):

# --- ROUTING RULES ---
guider_api.add_url_rule('/', view_func=Guider.as_view('guider', dev=guider))
guider_api.add_url_rule('/status', view_func=GuiderStatus.as_view('guider_status', dev=guider))
guider_api.add_url_rule('/calibrate', view_func=Calibrate.as_view('guider_calibrate', dev=guider))
+6 −0
Changes for noctua/api/sequencer.py: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
from quart import Blueprint

# Custom modules
from noctua import devices
from noctua.api.sequencer_instance import seq
from noctua.utils.data_access_object import ObservationBlockObject as DAO
from .baseresource import BaseResource, expects
@@ -30,12 +31,17 @@ class BobRun(BaseResource):
                "output": s.output,
                "error": s.error,
                "filename": s.filename,
                # cam_id (e.g. "scicam1") of the camera the template runs
                # on: the web page greys out that camera's controls.
                "cam_id": getattr(getattr(devices, str(self.dev.params.get("camera")), None),
                                  "_viewer_key", None),
            }
            err = self.dev.error
        else:
            data = {
                "name": None, "paused": None, "quitting": None,
                "output": None, "error": None, "filename": None,
                "cam_id": None,
            }
            err = "No tpl object"

+3 −0
Changes for noctua/web/__init__.py: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -83,6 +83,9 @@ async def start_background_tasks():
    # subsystems
    subsystems = set()
    subsystems.add("sequencer")
    # The guider is not in api.ini (its routes are registered by hand in
    # api/__init__.py), so its telemetry loop must be added explicitly.
    subsystems.add("guider")
    for section in ends.sections():
        parts = section.strip('/').split('/')
        if parts:
+22 −6
Changes for noctua/web/pages/control.html: 22 added lines, 6 removed lines.
Original line number Diff line number Diff line
@@ -163,7 +163,8 @@

    <div id="mode-panel-imaging" class="tab-pane fade show active">

      <section data-subsystem="scicam1">
      <section data-subsystem="scicam1"
               data-busy-on="scicam1-loop scicam1-sequencer">

        {{ ctrl.widget_frame("imaging", "scicam1",
           [("Full","full"),("Half","half"),("2'×2'","small")])
@@ -177,12 +178,14 @@

      <h4 class="mt-2 mb-2">Teccam</h4>

      <section data-subsystem="teccam1">
      <section data-subsystem="teccam1" data-busy-on="teccam1-loop">
        {{ ctrl.teccam_panel("imaging", "teccam1", has_gain=False) }}
      </section>

      <h4 class="mt-2 mb-2">Guider</h4>

      <section data-busy-on="guider">

        {{ ctrl.guider_camera_actuator("imaging",
                             [("cam1","scicam1","ctrl-combo1-sci"),
                              ("tec1","teccam1","ctrl-combo1-tec")],
@@ -193,11 +196,14 @@
                              ("tec1","teccam1","ctrl-combo1-tec")],
                             "scicam1") }}

      </section>

    </div>

    <div id="mode-panel-spectro" class="tab-pane fade">

      <section data-subsystem="scicam2">
      <section data-subsystem="scicam2"
               data-busy-on="scicam2-loop scicam2-sequencer">

        {{ ctrl.widget_frame("spectro", "scicam2",
                            [("Full","full"),("Half","half"),("Stripe","small")],
@@ -211,21 +217,26 @@

      <h4 class="mt-2 mb-2">Teccam</h4>

      <section data-subsystem="teccam2">
      <section data-subsystem="teccam2" data-busy-on="teccam2-loop">
        {{ ctrl.teccam_panel("spectro", "teccam2", has_gain=True) }}
      </section>

      <h4 class="mt-2 mb-2">Guider</h4>

      <section data-busy-on="guider">

        {{ ctrl.guider_panel("spectro",
                             [("tec2","teccam2","ctrl-combo2-tec")],
                             "scicam2") }}

      </section>

    </div>

    <div id="mode-panel-echelle" class="tab-pane fade">

      <section data-subsystem="scicam3">
      <section data-subsystem="scicam3"
               data-busy-on="scicam3-loop scicam3-sequencer">

        {{ ctrl.widget_frame("echelle", "scicam3",
                            [("Full","full"),("Half","half"),("2'×2'","small")],
@@ -239,16 +250,20 @@

      <h4 class="mt-2 mb-2">Teccam</h4>

      <section data-subsystem="teccam3">
      <section data-subsystem="teccam3" data-busy-on="teccam3-loop">
        {{ ctrl.teccam_panel("echelle", "teccam3", has_gain=True) }}
      </section>

      <h4 class="mt-2 mb-2">Guider</h4>

      <section data-busy-on="guider">

        {{ ctrl.guider_panel("echelle",
                             [("tec3","teccam3","ctrl-combo3-tec")],
                             "scicam3") }}

      </section>

    </div>

    </div>
@@ -309,6 +324,7 @@
{% block scripts %}
    <script type="module" src="{{ url_for('web.static', filename='js/control.js') }}"></script>
    <script type="module" src="{{ url_for('web.static', filename='js/guider-panel.js') }}"></script>
    <script type="module" src="{{ url_for('web.static', filename='js/busy-guard.js') }}"></script>
    <script type="module">
    import { FitsViewer }  from "{{ url_for('web.static', filename='js/viewer/fits-viewer.js') }}";
    import { register as registerViewer } from "{{ url_for('web.static', filename='js/viewer/viewer-registry.js') }}";
Loading