add conversation recap document

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 20:13:07 +02:00
co-authored by Claude Sonnet 4.6
parent 5efef47f1a
commit a920802cce
+113
View File
@@ -0,0 +1,113 @@
# 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 |