95 lines
3.8 KiB
Python
95 lines
3.8 KiB
Python
"""The Qt-free contract the web layer depends on.
|
|
|
|
The web layer (``app``/``routes``/``streaming``) is deliberately free of any Qt
|
|
or hardware knowledge: it talks only to a :class:`WebController`. The embedded
|
|
bridge in ``gui/controllers/web_controller.py`` implements this protocol by
|
|
dispatching actions to the shared GUI controllers and exposing read-only
|
|
snapshots. The same controller methods serve desktop buttons and the browser.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Protocol, runtime_checkable
|
|
|
|
|
|
class WebActionError(Exception):
|
|
"""A web-triggered desktop action failed, carrying the operator-facing reason.
|
|
|
|
Control methods run the matching desktop action *synchronously* and raise this
|
|
when that action reports an error (the same message the desktop would show), so
|
|
the HTTP layer can return it instead of a misleading "ok". Distinct from a plain
|
|
``ValueError`` (rejected by web-side validation before the action even runs).
|
|
"""
|
|
|
|
|
|
@runtime_checkable
|
|
class WebController(Protocol):
|
|
"""Control + read surface the web layer needs; implemented by the Qt bridge.
|
|
|
|
Control methods are *synchronous*: each runs the corresponding desktop action on
|
|
the GUI thread and only returns once it has completed, raising
|
|
:class:`WebActionError` if the action surfaced an error. This is what lets the
|
|
browser show real failures (e.g. a save into an existing directory) instead of a
|
|
blind success.
|
|
"""
|
|
|
|
def start(self) -> None:
|
|
"""Start a continuous run (the desktop "Start" button)."""
|
|
|
|
def single_capture(self) -> None:
|
|
"""Run a single-capture acquisition (the desktop "Single Capture" button)."""
|
|
|
|
def stop(self) -> None:
|
|
"""Stop the running pipeline (the desktop "Stop" button)."""
|
|
|
|
def start_recording(self, path: str, name: str, count: int) -> None:
|
|
"""Start a run (if stopped) and record the next ``count`` measurements to disk.
|
|
|
|
``path``/``name`` mirror the shared save destination fields (blank ``path``
|
|
keeps the configured one); ``count`` sets how many measurements are written.
|
|
Raises :class:`WebActionError` if the destination already exists.
|
|
"""
|
|
|
|
def capture_tmp_reference(self) -> None:
|
|
"""Capture and select a temporary reference (the desktop button)."""
|
|
|
|
def remove_last_measurement(self) -> None:
|
|
"""Remove the most recent measurement (the desktop button)."""
|
|
|
|
def clear_history(self) -> None:
|
|
"""Clear all runtime measurements (the desktop button)."""
|
|
|
|
def apply_live_settings(self, fields: dict) -> list:
|
|
"""Apply live processor settings; returns the current settings schema."""
|
|
...
|
|
|
|
def load_config(self, name: str) -> None:
|
|
"""Load a run-config profile by file name (the desktop "Load Config" button)."""
|
|
|
|
def save_dataset(self, path: str, name: str) -> None:
|
|
"""Save up to 1000 recent measurements to ``path``/``name``."""
|
|
|
|
def select_combo(self, input_pos: int, output_pos: int) -> None:
|
|
"""Select the B-scan input/output pair shown by the desktop."""
|
|
|
|
def next_combo(self) -> None:
|
|
"""Advance the B-scan display to the next available input/output pair."""
|
|
|
|
def current_live_settings(self) -> list:
|
|
"""Return the live-settings schema (built from the Qt widgets)."""
|
|
...
|
|
|
|
def list_configs(self) -> list[str]:
|
|
"""Return the run-config file names available to load."""
|
|
...
|
|
|
|
def status(self) -> dict:
|
|
"""Return a snapshot of pipeline/run state."""
|
|
...
|
|
|
|
def peek_frame(self) -> dict | None:
|
|
"""Read a cached PNG frame without rendering or blocking; initially ``None``."""
|
|
|
|
def set_frame_streaming(self, enabled: bool) -> None:
|
|
"""Enable plot capture only while at least one WebSocket client is connected."""
|