UI updates

This commit is contained in:
Ayzen
2026-04-01 20:21:16 +03:00
parent 4abc95c372
commit 669205d8f8
43 changed files with 2055 additions and 973 deletions
@@ -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."""