4.9 KiB
4.9 KiB
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 onlydrawImage— main thread never blocks - Trails: per-satellite
deque(maxlen=86400)(24 h at 1 Hz); global Show Trails checkbox + depth spinbox (0–24 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 000ms 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.floorfor first tick (≤ lo),math.ceilfor last tick (≥ hi), with 5 % padding- Settings persistence: y-axis mode, satellite selection, point count saved to
~/.nmea_client_plots.jsonkeyed by plot index 0–7 - 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:
QPainteronQImageis thread-safe;Queue(maxsize=1)ensures only the latest data is rendered, stale frames discarded _GNSS_QCOLORSdict pre-built in main thread; render thread reads it as read-only- In-place table cell updates:
_table_keyslist tracks row identity;setTextonly when structure unchanged;setRowCount/setSortingEnabledavoided 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 |