Refactor radar system
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user