diff --git a/9_Firmware/9_3_GUI/v7/dashboard.py b/9_Firmware/9_3_GUI/v7/dashboard.py index c323739..728847a 100644 --- a/9_Firmware/9_3_GUI/v7/dashboard.py +++ b/9_Firmware/9_3_GUI/v7/dashboard.py @@ -37,7 +37,7 @@ from PyQt6.QtWidgets import ( QTableWidget, QTableWidgetItem, QHeaderView, QPlainTextEdit, QStatusBar, QMessageBox, ) -from PyQt6.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtCore import Qt, QLocale, QTimer, pyqtSignal, pyqtSlot, QObject from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg from matplotlib.figure import Figure @@ -72,6 +72,17 @@ logger = logging.getLogger(__name__) NUM_RANGE_BINS = 64 NUM_DOPPLER_BINS = 32 +# Force C locale (period as decimal separator) for all QDoubleSpinBox instances. +_C_LOCALE = QLocale(QLocale.Language.C) +_C_LOCALE.setNumberOptions(QLocale.NumberOption.RejectGroupSeparator) + + +def _make_dspin() -> QDoubleSpinBox: + """Create a QDoubleSpinBox with C locale (no comma decimals).""" + sb = QDoubleSpinBox() + sb.setLocale(_C_LOCALE) + return sb + # ============================================================================= # Range-Doppler Canvas (matplotlib) @@ -447,7 +458,7 @@ class RadarDashboard(QMainWindow): pb_layout.addWidget(self._pb_stop_btn) pb_layout.addWidget(QLabel("FPS:")) - self._pb_fps_spin = QDoubleSpinBox() + self._pb_fps_spin = _make_dspin() self._pb_fps_spin.setRange(0.1, 60.0) self._pb_fps_spin.setValue(10.0) self._pb_fps_spin.setSingleStep(1.0) @@ -534,25 +545,25 @@ class RadarDashboard(QMainWindow): pos_group = QGroupBox("Radar Position") pos_layout = QGridLayout(pos_group) - self._lat_spin = QDoubleSpinBox() + self._lat_spin = _make_dspin() self._lat_spin.setRange(-90, 90) self._lat_spin.setDecimals(6) self._lat_spin.setValue(self._radar_position.latitude) self._lat_spin.valueChanged.connect(self._on_position_changed) - self._lon_spin = QDoubleSpinBox() + self._lon_spin = _make_dspin() self._lon_spin.setRange(-180, 180) self._lon_spin.setDecimals(6) self._lon_spin.setValue(self._radar_position.longitude) self._lon_spin.valueChanged.connect(self._on_position_changed) - self._alt_spin = QDoubleSpinBox() + self._alt_spin = _make_dspin() self._alt_spin.setRange(0, 50000) self._alt_spin.setDecimals(1) self._alt_spin.setValue(0.0) self._alt_spin.setSuffix(" m") - self._heading_spin = QDoubleSpinBox() + self._heading_spin = _make_dspin() self._heading_spin.setRange(0, 360) self._heading_spin.setDecimals(1) self._heading_spin.setValue(0.0) @@ -575,7 +586,7 @@ class RadarDashboard(QMainWindow): cov_group = QGroupBox("Coverage") cov_layout = QGridLayout(cov_group) - self._coverage_spin = QDoubleSpinBox() + self._coverage_spin = _make_dspin() self._coverage_spin.setRange(1, 200) self._coverage_spin.setDecimals(1) self._coverage_spin.setValue(self._settings.coverage_radius / 1000) @@ -991,7 +1002,7 @@ class RadarDashboard(QMainWindow): for spine in self._agc_ax_sat.spines.values(): spine.set_color(DARK_BORDER) self._agc_sat_line, = self._agc_ax_sat.plot( - [], [], color=DARK_ERROR, linewidth=1.0) + [], [], color=DARK_ERROR, linewidth=1.0, label="Saturation") self._agc_sat_fill_artist = None self._agc_ax_sat.legend( loc="upper right", fontsize=8, @@ -1139,7 +1150,7 @@ class RadarDashboard(QMainWindow): row += 1 p_layout.addWidget(QLabel("DBSCAN eps:"), row, 0) - self._cluster_eps_spin = QDoubleSpinBox() + self._cluster_eps_spin = _make_dspin() self._cluster_eps_spin.setRange(1.0, 5000.0) self._cluster_eps_spin.setDecimals(1) self._cluster_eps_spin.setValue(self._processing_config.clustering_eps) diff --git a/9_Firmware/9_3_GUI/v7/map_widget.py b/9_Firmware/9_3_GUI/v7/map_widget.py index 08a6b04..fa0fcb1 100644 --- a/9_Firmware/9_3_GUI/v7/map_widget.py +++ b/9_Firmware/9_3_GUI/v7/map_widget.py @@ -17,7 +17,8 @@ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QFrame, QComboBox, QCheckBox, QPushButton, QLabel, ) -from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtCore import Qt, QUrl, pyqtSignal, pyqtSlot, QObject +from PyQt6.QtWebEngineCore import QWebEngineSettings from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebChannel import QWebChannel @@ -517,8 +518,20 @@ document.addEventListener('DOMContentLoaded', function() {{ # ---- load / helpers ---------------------------------------------------- def _load_map(self): - self._web_view.setHtml(self._get_map_html()) - logger.info("Leaflet map HTML loaded") + # Enable remote resource access so Leaflet CDN scripts/tiles can load. + settings = self._web_view.page().settings() + settings.setAttribute( + QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, + True, + ) + # Provide an HTTP base URL so the page has a proper origin; + # without this, setHtml() defaults to about:blank which blocks + # external resource loading in modern Chromium. + self._web_view.setHtml( + self._get_map_html(), + QUrl("http://localhost/radar_map"), + ) + logger.info("Leaflet map HTML loaded (with HTTP base URL)") def _on_map_ready(self): self._status_label.setText(f"Map ready - {len(self._targets)} targets") diff --git a/9_Firmware/9_3_GUI/v7/workers.py b/9_Firmware/9_3_GUI/v7/workers.py index b1e6d1d..281195d 100644 --- a/9_Firmware/9_3_GUI/v7/workers.py +++ b/9_Firmware/9_3_GUI/v7/workers.py @@ -292,6 +292,7 @@ class RawIQReplayWorker(QThread): def run(self): self._running = True self._frame_count = 0 + self._last_emitted_state: str | None = None logger.info("RawIQReplayWorker started") info = self._controller.info @@ -322,9 +323,13 @@ class RawIQReplayWorker(QThread): idx = self._controller.frame_index self.frameIndexChanged.emit(idx, total_frames) - # Emit playback state + # Emit playback state only on transitions (avoid race + # where a stale "playing" signal overwrites a pause) state = self._controller.state - self.playbackStateChanged.emit(state.name.lower()) + state_str = state.name.lower() + if state_str != self._last_emitted_state: + self._last_emitted_state = state_str + self.playbackStateChanged.emit(state_str) # Run host-side DSP if configured if self._host_processor is not None: