sky tab: add per-receiver info bar with pDOP, visible and used satellite counts
One card per receiver showing receiver ID, pDOP, visible satellites and used satellites. Cards are added/removed dynamically with receivers. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+55
-3
@@ -9,7 +9,7 @@ for _p in [_root, _gui]:
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget,
|
||||
QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView,
|
||||
QSplitter, QGroupBox,
|
||||
QSplitter, QGroupBox, QFrame,
|
||||
)
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QColor, QBrush
|
||||
@@ -26,6 +26,9 @@ class SkyTab(QWidget):
|
||||
self._model = model
|
||||
self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData
|
||||
self._table_keys: list = [] # current row order as (rid, gnss_id, sv_id)
|
||||
self._pdop: dict = {} # rid -> float
|
||||
self._info_cards: dict = {} # rid -> QFrame
|
||||
self._info_labels: dict = {} # rid -> (lbl_pdop, lbl_visible, lbl_used)
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(4, 4, 4, 4)
|
||||
@@ -44,6 +47,16 @@ class SkyTab(QWidget):
|
||||
src_bl.addWidget(self._src_list)
|
||||
outer.addWidget(src_box)
|
||||
|
||||
# ── Info bar (one card per receiver) ──────────────────────────────────
|
||||
info_frame = QFrame()
|
||||
info_frame.setFrameShape(QFrame.StyledPanel)
|
||||
info_frame.setMaximumHeight(42)
|
||||
self._info_layout = QHBoxLayout(info_frame)
|
||||
self._info_layout.setContentsMargins(6, 2, 6, 2)
|
||||
self._info_layout.setSpacing(12)
|
||||
self._info_layout.addStretch()
|
||||
outer.addWidget(info_frame)
|
||||
|
||||
# ── Sky plot + satellite table ─────────────────────────────────────────
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
@@ -74,6 +87,7 @@ class SkyTab(QWidget):
|
||||
model.receiver_added.connect(self._on_receiver_added)
|
||||
model.receiver_removed.connect(self._on_receiver_removed)
|
||||
model.satellite_update.connect(self._on_sat_update)
|
||||
model.pdop_update.connect(self._on_pdop_update)
|
||||
|
||||
# ── Source list management ────────────────────────────────────────────────
|
||||
|
||||
@@ -84,6 +98,22 @@ class SkyTab(QWidget):
|
||||
item.setData(Qt.UserRole, rid)
|
||||
self._src_list.addItem(item)
|
||||
|
||||
# Info card for this receiver
|
||||
card = QFrame()
|
||||
card.setFrameShape(QFrame.Box)
|
||||
cl = QHBoxLayout(card)
|
||||
cl.setContentsMargins(8, 1, 8, 1)
|
||||
cl.setSpacing(10)
|
||||
name_lbl = QLabel(f"<b>{rid}</b>")
|
||||
lbl_pdop = QLabel("pDOP: —")
|
||||
lbl_visible = QLabel("Visible: —")
|
||||
lbl_used = QLabel("Used: —")
|
||||
for w in (name_lbl, lbl_pdop, lbl_visible, lbl_used):
|
||||
cl.addWidget(w)
|
||||
self._info_cards[rid] = card
|
||||
self._info_labels[rid] = (lbl_pdop, lbl_visible, lbl_used)
|
||||
self._info_layout.insertWidget(self._info_layout.count() - 1, card)
|
||||
|
||||
def _on_receiver_removed(self, rid: str):
|
||||
for i in range(self._src_list.count()):
|
||||
if self._src_list.item(i).data(Qt.UserRole) == rid:
|
||||
@@ -92,6 +122,12 @@ class SkyTab(QWidget):
|
||||
keys = [k for k in self._store if k[0] == rid]
|
||||
for k in keys:
|
||||
del self._store[k]
|
||||
self._pdop.pop(rid, None)
|
||||
card = self._info_cards.pop(rid, None)
|
||||
if card:
|
||||
self._info_layout.removeWidget(card)
|
||||
card.deleteLater()
|
||||
self._info_labels.pop(rid, None)
|
||||
self._refresh_immediate()
|
||||
|
||||
def _selected_rids(self) -> set:
|
||||
@@ -107,22 +143,38 @@ class SkyTab(QWidget):
|
||||
return [(k[0], self._store[k])
|
||||
for k in self._store if k[0] in selected]
|
||||
|
||||
# ── Info bar ──────────────────────────────────────────────────────────────
|
||||
|
||||
def _update_info(self):
|
||||
for rid, (lbl_pdop, lbl_visible, lbl_used) in self._info_labels.items():
|
||||
sats = [self._store[k] for k in self._store if k[0] == rid]
|
||||
n_visible = len(sats)
|
||||
n_used = sum(1 for s in sats if s.used_in_fix)
|
||||
pdop = self._pdop.get(rid)
|
||||
lbl_pdop.setText( f"pDOP: {pdop:.1f}" if pdop is not None else "pDOP: —")
|
||||
lbl_visible.setText(f"Visible: {n_visible}")
|
||||
lbl_used.setText( f"Used: {n_used}")
|
||||
|
||||
# ── Data update ───────────────────────────────────────────────────────────
|
||||
|
||||
def _on_sat_update(self, rid: str, sats: list):
|
||||
for sat in sats:
|
||||
self._store[(rid, sat.gnss_id, sat.sv_id)] = sat
|
||||
# Sky widget: always submit latest (render is off-thread, trivially cheap)
|
||||
self._sky.update_data(self._visible_sats())
|
||||
# Table: schedule a rebuild if one isn't already pending
|
||||
self._update_info()
|
||||
if not self._table_timer.isActive():
|
||||
self._table_timer.start()
|
||||
|
||||
def _on_pdop_update(self, rid: str, pdop: float, _ts: float):
|
||||
self._pdop[rid] = pdop
|
||||
self._update_info()
|
||||
|
||||
def _refresh_immediate(self):
|
||||
"""Called on source-checkbox toggle or receiver removal — rebuild now."""
|
||||
self._table_timer.stop()
|
||||
visible = self._visible_sats()
|
||||
self._sky.update_data(visible)
|
||||
self._update_info()
|
||||
self._rebuild_table(visible)
|
||||
|
||||
def _rebuild_table_from_store(self):
|
||||
|
||||
Reference in New Issue
Block a user