Refactor radar system

This commit is contained in:
Ayzen
2026-10-05 16:46:28 +03:00
parent 515049d2ed
commit 8f097dfa2e
179 changed files with 8212 additions and 6676 deletions
+22 -46
View File
@@ -23,6 +23,7 @@ from PyQt6.QtGui import QTextCursor
from PyQt6.QtWidgets import QApplication, QMainWindow, QMessageBox
from python_app.gui.controllers.app_window_config_mixin import AppWindowConfigMixin
from python_app.gui.controllers.app_window_config.state_builders import GUI_SAVE_HISTORY_LIMIT
from python_app.gui.controllers.app_window_control_button_mixin import AppWindowControlButtonMixin
from python_app.gui.controllers.app_window_pipeline_mixin import AppWindowPipelineMixin
from python_app.gui.controllers.app_window_plot_mixin import AppWindowPlotMixin
@@ -160,6 +161,9 @@ class AppWindow(
self._pending_startup_log_entries: list[tuple[str, str, str | None]] = []
# Guards closeEvent against re-entrant teardown (e.g. a second signal).
self._closing = False
self._close_after_stop = False
self._stopping_run = False
self._stop_request_seq = 0
def _init_logging(self) -> None:
"""Configure the application logger and the bridge that feeds the GUI panel.
@@ -336,11 +340,10 @@ class AppWindow(
def _init_history_state(self) -> None:
"""Initialize runtime history buffers and render-cache state."""
bscan_cpp_replay_window = self._cpp_bscan_replay_window_from_config()
save_history_limit = self._save_history_limit_from_config()
self._raw_history: deque[SweepCollection] = deque(maxlen=save_history_limit)
self._pre_history: deque[SweepCollection] = deque(maxlen=save_history_limit)
self._result_history: deque[ResultCollection] = deque(maxlen=save_history_limit)
bscan_cpp_replay_window = self._cpp_bscan_replay_window_for_config(self._defaults_config)
self._raw_history: deque[SweepCollection] = deque(maxlen=GUI_SAVE_HISTORY_LIMIT)
self._pre_history: deque[SweepCollection] = deque(maxlen=GUI_SAVE_HISTORY_LIMIT)
self._result_history: deque[ResultCollection] = deque(maxlen=GUI_SAVE_HISTORY_LIMIT)
# Sequence id must survive GUI restarts so history commands stay monotonic.
self._history_command_seq = self._load_history_command_seq(self._live_config_writer.path)
@@ -371,20 +374,11 @@ class AppWindow(
self._gpr_region_contours = []
self._gpr_geometry_signature = None
self._gpr_selected_geometry = None
self._phase_viewbox = None
self._history_run_signature = None
self._processor_run_signature = None
self._active_processing_mode = "pass_through"
self._radar_limits: dict[str, float | int] | None = None
def _cpp_bscan_replay_window_from_config(self) -> int:
"""Return the C++ B-scan replay window for the active config."""
return self._cpp_bscan_replay_window_for_config(self._defaults_config)
def _save_history_limit_from_config(self) -> int:
"""Return maxlen for GUI snapshot-save deques (independent of ring capacities)."""
return self._save_history_limit_for_config(self._defaults_config)
def _init_runtime_limits(self) -> None:
"""Initialize read/drain loop limits used by polling and snapshot code."""
self._max_pop_per_poll = 256
@@ -638,30 +632,6 @@ class AppWindow(
)
return "<div style='margin:0 0 6px 0;'>" + "".join(body_parts) + "</div>"
def _append_log_entry(
self,
level: str,
text: str,
*,
details: str | None = None,
once_key: str | None = None,
) -> None:
"""Append formatted log entry with timestamp and optional details."""
if once_key is not None:
if once_key in self._logged_once_keys:
return
self._logged_once_keys.add(once_key)
cursor = self._log_box.textCursor()
cursor.movePosition(QTextCursor.MoveOperation.End)
self._log_box.setTextCursor(cursor)
self._log_box.insertHtml(self._render_log_entry_html(level, text, details))
self._log_box.insertPlainText("\n")
self._log_box.ensureCursorVisible()
if level.upper() == "ERROR" and hasattr(self, "_status_label"):
self._status_label.setText("Status: error")
def _on_log_level_selected(self, level_text: str) -> None:
"""Apply the chosen log level immediately (sub-level logs stop being generated).
@@ -746,12 +716,8 @@ class AppWindow(
# Missing or malformed file should not block startup.
return 0
raw_value = payload.get("history_command_seq", 0)
if isinstance(raw_value, bool):
return 0
if isinstance(raw_value, (int, float)):
return max(0, int(raw_value))
return 0
raw_value = payload.get("history_command_seq", 0) if isinstance(payload, dict) else 0
return raw_value if type(raw_value) is int and 0 <= raw_value < (1 << 64) - 1 else 0
def _capture_web_action_error(self, message: str) -> bool:
"""Record the first error of an in-flight web action so the browser can show it.
@@ -804,18 +770,24 @@ class AppWindow(
Guarded against re-entrancy so a second close signal (or a ``window.close()``
after the event loop has already returned) does not run teardown twice.
"""
if self._stopping_run:
# A drain pumps Qt events. Finish ingesting/finalizing before teardown
# can close the same readers and recording writer beneath that drain.
self._close_after_stop = True
self._stop_all_requested = True
event.ignore()
return
if self._closing:
# Re-entrant close (second signal, or window.close() after the event loop
# already returned): teardown is in progress or done — do nothing more.
super().closeEvent(event)
return
self._closing = True
self._timer.stop()
self._log("Window closing; shutting down runtime.")
try:
# 0) Stop the web server first so a late request cannot start work.
self._shutdown_web_ui()
# 0) Stop the disk-recording writer thread so it is not orphaned.
self._shutdown_recording()
# 0) Stop the GPIO button watcher so a late press cannot start work.
self._stop_control_button_watcher()
self._resume_pipeline_after_capture = False
@@ -825,11 +797,15 @@ class AppWindow(
self._close_replay_ring_writer()
# 1) Abort active capture first (releases exclusive hardware resources).
self._abort_capture_sequence(resume_pipeline=False)
# Drain acquisition stages before finalizing the last recording chunk.
if self._recording_active:
self._stop_run()
# 2) Stop all managed processes/readers.
self._stop_all_processes()
# 3) Close auxiliary dialog windows.
if self._preprocess_dialog is not None:
self._preprocess_dialog.close()
finally:
self._shutdown_recording()
self._log_debug("Window teardown finished.")
super().closeEvent(event)