diff options
Diffstat (limited to 'ui/readout_panel.py')
| -rw-r--r-- | ui/readout_panel.py | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/ui/readout_panel.py b/ui/readout_panel.py new file mode 100644 index 0000000..ef04e18 --- /dev/null +++ b/ui/readout_panel.py @@ -0,0 +1,122 @@ +""" +ui/readout_panel.py — Right-side numeric readout panel. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QScrollArea, QFrame, QProgressBar, +) +from PyQt6.QtCore import Qt, pyqtSlot +from devices.device_registry import DeviceRegistry + + +class ChannelReadout(QFrame): + def __init__(self, ch_config): + super().__init__() + self.ch = ch_config + self.setObjectName("channelReadout") + self._alarm = False + self._build() + + def _build(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(8, 6, 8, 6) + layout.setSpacing(2) + + top = QHBoxLayout() + self._name = QLabel(self.ch.name) + self._name.setObjectName("readoutName") + top.addWidget(self._name, 1) + + self._val = QLabel("— — —") + self._val.setObjectName("readoutValue") + self._val.setStyleSheet(f"color:{self.ch.color};") + top.addWidget(self._val) + + unit = QLabel(f" {self.ch.unit}") + unit.setObjectName("readoutUnit") + top.addWidget(unit) + layout.addLayout(top) + + self._bar = QProgressBar() + self._bar.setObjectName("readoutBar") + self._bar.setRange(0, 1000) + self._bar.setValue(500) + self._bar.setTextVisible(False) + self._bar.setMaximumHeight(3) + self._bar.setStyleSheet( + f"QProgressBar::chunk {{ background:{self.ch.color}; border-radius:1px; }}" + ) + layout.addWidget(self._bar) + + def update_value(self, v: float): + self._val.setText(f"{v:>10.4f}".strip()) + rng = self.ch.max_value - self.ch.min_value + norm = int(((v - self.ch.min_value) / rng) * 1000) if rng else 500 + self._bar.setValue(max(0, min(1000, norm))) + + alarm = ( + (self.ch.alarm_low is not None and v < self.ch.alarm_low) or + (self.ch.alarm_high is not None and v > self.ch.alarm_high) + ) + if alarm != self._alarm: + self._alarm = alarm + self.setProperty("alarm", alarm) + self.style().unpolish(self) + self.style().polish(self) + + +class ReadoutPanel(QWidget): + def __init__(self, registry: DeviceRegistry): + super().__init__() + self.registry = registry + self._readouts = {} # (dev_id, ch_id) -> ChannelReadout + self._build() + self.refresh() + + def _build(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + hdr = QLabel(" CHANNELS") + hdr.setObjectName("panelHeader") + hdr.setMinimumHeight(28) + layout.addWidget(hdr) + + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + self._container = QWidget() + self._inner = QVBoxLayout(self._container) + self._inner.setContentsMargins(6, 6, 6, 6) + self._inner.setSpacing(4) + self._inner.addStretch() + + scroll.setWidget(self._container) + layout.addWidget(scroll) + + def refresh(self): + for w in self._readouts.values(): + w.deleteLater() + self._readouts.clear() + # Clear layout + while self._inner.count() > 1: + item = self._inner.takeAt(0) + if item and item.widget(): + item.widget().deleteLater() + + for dev in self.registry.all_instances(): + for ch in dev.info.channels: + if not ch.enabled: + continue + ro = ChannelReadout(ch) + self._inner.insertWidget(self._inner.count() - 1, ro) + self._readouts[(dev.info.device_id, ch.channel_id)] = ro + + @pyqtSlot(str, str, float, float) + def on_new_data(self, dev_id: str, ch_id: str, _ts: float, value: float): + ro = self._readouts.get((dev_id, ch_id)) + if ro: + ro.update_value(value) |
