Files
NmeaClient/conversation_recap.md
T
2026-05-24 20:13:07 +02:00

4.9 KiB
Raw Blame History

GNSS Monitor GUI — Conversation Recap

Date: 2026-05-24

Goal

Build a PyQt5 frontend (gui/) on top of the existing nmea_client pipeline (NetworkBackend / Transceiver / NmeaPacketizer / UbxPacketizer) with three tabs: Connection, Sky, Plot.


Files Created

File Purpose
gui/main.py QApplication, MainWindow, QTabWidget with 3 tabs
gui/data_model.py ReceiverManager (QObject), SatelliteData, ConnectionConfig, GNSS constants, LRU persistence
gui/listeners.py NavSatQtListener (UBX NAV-SAT → SatelliteData), GsaQtListener (NMEA GSA → pDOP)
gui/backends.py SerialBackend (ABackend over pyserial)
gui/connection_tab.py ConnectionTab + ConnectionRow, TCP/serial config, LRU history combo
gui/sky_widget.py SkyPlotWidget — off-thread QPainter renderer via SkyRenderThread
gui/sky_tab.py SkyTab — sky plot + satellite table + source selector + info bar + trails
gui/plot_widget.py SinglePlotWidget — custom QPainter canvas, 6 y-axis modes, settings persistence
gui/plot_tab.py PlotTab — 8 SinglePlotWidgets in a 2-column scrollable grid

Connection Tab

  • Each row: ID field, TCP/Serial switcher (QStackedWidget), host/port or serial/baud fields, status dot, Connect/Disconnect, Remove
  • LRU history combo bottom-left, Add button next to it — user picks from history then hits Add
  • Host field accepts both IP addresses and hostnames
  • LRU persisted to ~/.nmea_client_gui.json (max 20 entries)

Sky Tab

  • Sky plot: polar projection, concentric rings at 0°/30°/60°, cardinal labels, satellites sized by C/N₀, filled = used in fix, dashed outline = not used
  • Off-thread rendering: SkyRenderThread (QThread + Queue(maxsize=1)) renders to QImage; paintEvent does only drawImage — main thread never blocks
  • Trails: per-satellite deque(maxlen=86400) (24 h at 1 Hz); global Show Trails checkbox + depth spinbox (024 h); subsampled to ≤120 points in render thread; alpha fades 15→200 oldest→newest
  • Satellite table: columns GNSS / SV / Elev° / Az° / C/N₀ / Fix / Receiver / Msg; sorted by GNSS then SV; in-place cell updates preserve scroll position; full rebuild only when satellite set changes
  • Msg column: fixed 36 px width; shows for _MSG_HIGHLIGHT_MS = 10 000 ms after a message is received; clears and evicts satellite from store/sky when timer fires (not seen for 10 s)
  • Info bar: one card per receiver showing pDOP / Visible / Used counts
  • Source selector: per-receiver checkboxes to include/exclude from plot and table

Plot Tab

  • 8 independent plots, 2-column scrollable layout
  • Y-axis modes: C/N₀, Azimuth, Elevation (satellite-specific); pDOP, Visible Sats, Used Sats (global/per-receiver)
  • Off-thread rendering: same QThread+QImage pattern as sky widget
  • Custom QPainter canvas: grid, axis ticks, curves, legend — no pyqtgraph dependency
  • _nice_ticks(lo, hi): math.floor for first tick (≤ lo), math.ceil for last tick (≥ hi), with 5 % padding
  • Settings persistence: y-axis mode, satellite selection, point count saved to ~/.nmea_client_plots.json keyed by plot index 07
  • Right-click context menu: Save as PNG, Copy to clipboard
  • All GNSS sources default to checked (enabled)

Key Architectural Decisions

  • Qt cross-thread safety: listener callbacks emit signals from backend threads; Qt queues delivery to main thread automatically — no explicit locking needed
  • Off-thread rendering pattern: QPainter on QImage is thread-safe; Queue(maxsize=1) ensures only the latest data is rendered, stale frames discarded
  • _GNSS_QCOLORS dict pre-built in main thread; render thread reads it as read-only
  • In-place table cell updates: _table_keys list tracks row identity; setText only when structure unchanged; setRowCount/setSortingEnabled avoided to preserve scroll position

Notable Fixes & Iterations

Issue Fix
Sky plot slow, table scrolling laggy QTimer throttle (500 ms) + in-place cell updates + viewport().setUpdatesEnabled(False/True)
Y-axis clipping data at top math.floor for first tick; loop extended to cover ceil of max
Plot satellite combo always empty Source checkboxes defaulted to Qt.Checked
Msg column showing two sub-columns QHeaderView.Fixed + explicit setColumnWidth
Row background highlight rejected Replaced with dedicated Msg column + symbol
Host field rejecting hostnames QLineEdit with setPlaceholderText instead of IP-only validator

GNSS Systems Supported

ID Name Short Color
0 GPS G #2196F3 blue
1 SBAS S #9E9E9E gray
2 Galileo E #4CAF50 green
3 BeiDou C #FF9800 orange
4 IMES I #607D8B blue-gray
5 QZSS J #9C27B0 purple
6 GLONASS R #F44336 red
7 NavIC N #F4A0A0 rose