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

114 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |