UI updates
This commit is contained in:
@@ -9,7 +9,12 @@ from python_app.gui.runtime.history import build_run_history_signature, record_r
|
||||
from python_app.hardware_full.librevna_service import LibreVnaService
|
||||
from python_app.models.dataset_model import ComboKey, ResultCollection, SweepCollection
|
||||
from python_app.models.run_config_model import RunConfigModel
|
||||
from python_app.orchestration.preprocess_assets import PREPROCESS_ASSET_KEYS, PREPROCESS_ASSET_SPECS, preprocess_asset_model
|
||||
from python_app.orchestration.preprocess_assets import (
|
||||
PREPROCESS_ASSET_SPECS,
|
||||
REQUIRED_PREPROCESS_ASSET_KEYS,
|
||||
preprocess_asset_model,
|
||||
runtime_preprocess_asset_keys,
|
||||
)
|
||||
from python_app.orchestration.shm_reader import ShmRingReader
|
||||
|
||||
|
||||
@@ -23,10 +28,13 @@ class AppWindowPipelineMixin:
|
||||
def _start_run(self, *, single_capture: bool = False) -> None:
|
||||
"""Start pipeline processes and ring readers."""
|
||||
if self._capture_session is not None:
|
||||
self._show_error("Cannot start pipeline during active capture sequence")
|
||||
self._show_error(
|
||||
"Cannot start pipeline during active capture sequence",
|
||||
details=self._capture_state_details(),
|
||||
)
|
||||
return
|
||||
if self._supervisor.is_running():
|
||||
self._show_error("Pipeline is already running")
|
||||
self._show_error("Pipeline is already running", details=self._process_state_details())
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -41,7 +49,7 @@ class AppWindowPipelineMixin:
|
||||
|
||||
missing_assets = [
|
||||
PREPROCESS_ASSET_SPECS[key].display_name
|
||||
for key in PREPROCESS_ASSET_KEYS
|
||||
for key in REQUIRED_PREPROCESS_ASSET_KEYS
|
||||
if not preprocess_asset_model(config, key).set_name
|
||||
]
|
||||
if missing_assets:
|
||||
@@ -51,8 +59,14 @@ class AppWindowPipelineMixin:
|
||||
)
|
||||
|
||||
combo_keys = [ComboKey(input_pos=combo.input, output_pos=combo.output) for combo in config.combos]
|
||||
active_preprocess_keys = runtime_preprocess_asset_keys(config)
|
||||
preprocess_summary = "; ".join(
|
||||
f"{PREPROCESS_ASSET_SPECS[key].display_name}={preprocess_asset_model(config, key).set_name}"
|
||||
for key in active_preprocess_keys
|
||||
)
|
||||
self._log(f"Active preprocess assets for run: {preprocess_summary}")
|
||||
|
||||
for key in PREPROCESS_ASSET_KEYS:
|
||||
for key in active_preprocess_keys:
|
||||
spec = PREPROCESS_ASSET_SPECS[key]
|
||||
asset = preprocess_asset_model(config, key)
|
||||
if not self._store.has_combo_coverage(spec.set_kind, radar_key, asset.set_name, combo_keys):
|
||||
@@ -65,6 +79,14 @@ class AppWindowPipelineMixin:
|
||||
self._prepare_radar_for_native_acquisition(config)
|
||||
|
||||
config_path = self._config_writer.write(config, self._project_root / "python_app/runtime/run_config.json")
|
||||
combo_preview = ", ".join(f"in{combo.input}/out{combo.output}" for combo in config.combos[:6])
|
||||
if len(config.combos) > 6:
|
||||
combo_preview += ", ..."
|
||||
self._log(
|
||||
f"Starting pipeline: mode={'single_capture' if single_capture else 'continuous'}, "
|
||||
f"config={config_path}, combos={len(config.combos)}"
|
||||
f"{', ' + combo_preview if combo_preview else ''}, radar_key={radar_key}"
|
||||
)
|
||||
|
||||
if not single_capture:
|
||||
should_reset_history = (
|
||||
@@ -75,7 +97,7 @@ class AppWindowPipelineMixin:
|
||||
self._log("History reset because run settings changed")
|
||||
self._history_run_signature = run_signature
|
||||
|
||||
self._supervisor.start(config_path)
|
||||
self._supervisor.start(config_path, allow_clean_orchestrator_exit=single_capture)
|
||||
self._close_readers()
|
||||
self._raw_reader = ShmRingReader(config.rings.raw_tap.name)
|
||||
self._pre_reader = ShmRingReader(config.rings.preprocessed_tap.name)
|
||||
@@ -88,25 +110,31 @@ class AppWindowPipelineMixin:
|
||||
# Always drop unread payloads for all stages so single-capture starts
|
||||
# from a clean boundary and does not retain stale results-only tail.
|
||||
self._drop_pending_ring_payloads(include_results=True)
|
||||
self._last_reader_error_signature = None
|
||||
if single_capture:
|
||||
self._single_capture_start_ns = time.monotonic_ns()
|
||||
|
||||
pid_map = self._supervisor.pids()
|
||||
pid_text = ", ".join(f"{name}={pid}" for name, pid in sorted(pid_map.items())) or "none"
|
||||
if single_capture:
|
||||
self._status_label.setText("Status: single capture running")
|
||||
self._log("Single capture started")
|
||||
self._log(f"Single capture started; managed processes: {pid_text}")
|
||||
else:
|
||||
self._status_label.setText("Status: running")
|
||||
self._log("Pipeline started")
|
||||
self._log(f"Pipeline started; managed processes: {pid_text}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._single_capture_active = False
|
||||
self._single_capture_start_ns = None
|
||||
self._stop_all_processes()
|
||||
self._show_error(f"Failed to start pipeline: {exc}")
|
||||
self._show_exception("Failed to start pipeline", exc)
|
||||
|
||||
def _apply_radar_settings(self) -> None:
|
||||
"""Apply current radar settings by preconfiguring native device."""
|
||||
if self._capture_session is not None:
|
||||
self._show_error("Finish or abort capture sequence before applying radar settings")
|
||||
self._show_error(
|
||||
"Finish or abort capture sequence before applying radar settings",
|
||||
details=self._capture_state_details(),
|
||||
)
|
||||
return
|
||||
|
||||
was_running = self._supervisor.is_running()
|
||||
@@ -118,9 +146,16 @@ class AppWindowPipelineMixin:
|
||||
self._refresh_radar_limits_from_device()
|
||||
config = self._build_config()
|
||||
self._prepare_radar_for_native_acquisition(config)
|
||||
self._log("Radar settings applied")
|
||||
self._log(
|
||||
"Radar settings applied: "
|
||||
f"start={config.radar.sweep.start_hz:g} Hz, "
|
||||
f"stop={config.radar.sweep.stop_hz:g} Hz, "
|
||||
f"points={config.radar.sweep.points}, "
|
||||
f"ifbw={config.radar.sweep.if_bandwidth_hz:g} Hz, "
|
||||
f"power={config.radar.sweep.power_dbm:g} dBm"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._show_error(f"Failed to apply radar settings: {exc}")
|
||||
self._show_exception("Failed to apply radar settings", exc)
|
||||
finally:
|
||||
if was_running:
|
||||
self._start_run()
|
||||
@@ -197,9 +232,12 @@ class AppWindowPipelineMixin:
|
||||
|
||||
def _poll_rings(self) -> None:
|
||||
"""Poll readers, ingest history, and trigger rendering."""
|
||||
for report in self._supervisor.collect_crash_reports():
|
||||
for report in self._supervisor.collect_exit_reports():
|
||||
if report.level == "INFO":
|
||||
self._log(report.format())
|
||||
continue
|
||||
self._status_label.setText("Status: error")
|
||||
self._log(report)
|
||||
self._log_error(report.format())
|
||||
|
||||
try:
|
||||
if self._raw_reader is not None:
|
||||
@@ -207,6 +245,7 @@ class AppWindowPipelineMixin:
|
||||
self._read_all_preprocessed()
|
||||
result_latest = self._read_all_results() if self._result_reader is not None else None
|
||||
self._update_history_indicator()
|
||||
self._last_reader_error_signature = None
|
||||
|
||||
if self._single_capture_active:
|
||||
if self._finish_single_capture_if_ready():
|
||||
@@ -215,7 +254,11 @@ class AppWindowPipelineMixin:
|
||||
|
||||
self._draw_preferred_collection(result_latest=result_latest)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._log(f"Reader error: {exc}")
|
||||
signature = (type(exc).__name__, str(exc))
|
||||
if self._last_reader_error_signature == signature:
|
||||
return
|
||||
self._last_reader_error_signature = signature
|
||||
self._log_exception("Reader poll failed", exc, level="ERROR")
|
||||
|
||||
def _finish_single_capture_if_ready(self) -> bool:
|
||||
"""Finalize single capture when the exact target result becomes available."""
|
||||
|
||||
Reference in New Issue
Block a user