add conversation recap document
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (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 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 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**: `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 |
|
||||
Reference in New Issue
Block a user