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:
+57
-5
@@ -9,7 +9,7 @@ for _p in [_root, _gui]:
|
|||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget,
|
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QListWidget,
|
||||||
QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView,
|
QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView,
|
||||||
QSplitter, QGroupBox,
|
QSplitter, QGroupBox, QFrame,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt5.QtCore import Qt, QTimer
|
||||||
from PyQt5.QtGui import QColor, QBrush
|
from PyQt5.QtGui import QColor, QBrush
|
||||||
@@ -24,8 +24,11 @@ class SkyTab(QWidget):
|
|||||||
def __init__(self, model: ReceiverManager):
|
def __init__(self, model: ReceiverManager):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._model = model
|
self._model = model
|
||||||
self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData
|
self._store: dict = {} # (rid, gnss_id, sv_id) -> SatelliteData
|
||||||
self._table_keys: list = [] # current row order as (rid, gnss_id, sv_id)
|
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 = QVBoxLayout(self)
|
||||||
outer.setContentsMargins(4, 4, 4, 4)
|
outer.setContentsMargins(4, 4, 4, 4)
|
||||||
@@ -44,6 +47,16 @@ class SkyTab(QWidget):
|
|||||||
src_bl.addWidget(self._src_list)
|
src_bl.addWidget(self._src_list)
|
||||||
outer.addWidget(src_box)
|
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 ─────────────────────────────────────────
|
# ── Sky plot + satellite table ─────────────────────────────────────────
|
||||||
splitter = QSplitter(Qt.Horizontal)
|
splitter = QSplitter(Qt.Horizontal)
|
||||||
|
|
||||||
@@ -74,6 +87,7 @@ class SkyTab(QWidget):
|
|||||||
model.receiver_added.connect(self._on_receiver_added)
|
model.receiver_added.connect(self._on_receiver_added)
|
||||||
model.receiver_removed.connect(self._on_receiver_removed)
|
model.receiver_removed.connect(self._on_receiver_removed)
|
||||||
model.satellite_update.connect(self._on_sat_update)
|
model.satellite_update.connect(self._on_sat_update)
|
||||||
|
model.pdop_update.connect(self._on_pdop_update)
|
||||||
|
|
||||||
# ── Source list management ────────────────────────────────────────────────
|
# ── Source list management ────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -84,6 +98,22 @@ class SkyTab(QWidget):
|
|||||||
item.setData(Qt.UserRole, rid)
|
item.setData(Qt.UserRole, rid)
|
||||||
self._src_list.addItem(item)
|
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):
|
def _on_receiver_removed(self, rid: str):
|
||||||
for i in range(self._src_list.count()):
|
for i in range(self._src_list.count()):
|
||||||
if self._src_list.item(i).data(Qt.UserRole) == rid:
|
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]
|
keys = [k for k in self._store if k[0] == rid]
|
||||||
for k in keys:
|
for k in keys:
|
||||||
del self._store[k]
|
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()
|
self._refresh_immediate()
|
||||||
|
|
||||||
def _selected_rids(self) -> set:
|
def _selected_rids(self) -> set:
|
||||||
@@ -107,22 +143,38 @@ class SkyTab(QWidget):
|
|||||||
return [(k[0], self._store[k])
|
return [(k[0], self._store[k])
|
||||||
for k in self._store if k[0] in selected]
|
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 ───────────────────────────────────────────────────────────
|
# ── Data update ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _on_sat_update(self, rid: str, sats: list):
|
def _on_sat_update(self, rid: str, sats: list):
|
||||||
for sat in sats:
|
for sat in sats:
|
||||||
self._store[(rid, sat.gnss_id, sat.sv_id)] = sat
|
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())
|
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():
|
if not self._table_timer.isActive():
|
||||||
self._table_timer.start()
|
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):
|
def _refresh_immediate(self):
|
||||||
"""Called on source-checkbox toggle or receiver removal — rebuild now."""
|
"""Called on source-checkbox toggle or receiver removal — rebuild now."""
|
||||||
self._table_timer.stop()
|
self._table_timer.stop()
|
||||||
visible = self._visible_sats()
|
visible = self._visible_sats()
|
||||||
self._sky.update_data(visible)
|
self._sky.update_data(visible)
|
||||||
|
self._update_info()
|
||||||
self._rebuild_table(visible)
|
self._rebuild_table(visible)
|
||||||
|
|
||||||
def _rebuild_table_from_store(self):
|
def _rebuild_table_from_store(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user