diff options
Diffstat (limited to 'daq_system/ui/readout_panel.py')
| -rw-r--r-- | daq_system/ui/readout_panel.py | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/daq_system/ui/readout_panel.py b/daq_system/ui/readout_panel.py new file mode 100644 index 0000000..6086c30 --- /dev/null +++ b/daq_system/ui/readout_panel.py @@ -0,0 +1,128 @@ +""" +ui/readout_panel.py + +Right-side numeric readout panel. Shows current value for every channel +with a colored bar indicator and alarm highlighting. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QScrollArea, QFrame, QProgressBar +) +from PyQt6.QtCore import Qt, pyqtSlot +from PyQt6.QtGui import QColor + +from devices.device_registry import DeviceRegistry + + +class ChannelReadout(QFrame): + def __init__(self, device_name: str, channel_config, parent=None): + super().__init__(parent) + self.ch = channel_config + self.device_name = device_name + self.setObjectName("channelReadout") + self._alarm_active = False + self._build() + + def _build(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(8, 6, 8, 6) + layout.setSpacing(2) + + top = QHBoxLayout() + self.name_lbl = QLabel(f"{self.ch.name}") + self.name_lbl.setObjectName("readoutName") + top.addWidget(self.name_lbl) + top.addStretch() + self.value_lbl = QLabel("---") + self.value_lbl.setObjectName("readoutValue") + self.value_lbl.setStyleSheet(f"color: {self.ch.color};") + top.addWidget(self.value_lbl) + unit = QLabel(self.ch.unit) + unit.setObjectName("readoutUnit") + top.addWidget(unit) + layout.addLayout(top) + + # Mini bar + self.bar = QProgressBar() + self.bar.setRange(0, 1000) + self.bar.setValue(0) + self.bar.setTextVisible(False) + self.bar.setMaximumHeight(4) + self.bar.setObjectName("readoutBar") + self.bar.setStyleSheet( + f"QProgressBar::chunk {{ background: {self.ch.color}; border-radius: 2px; }}" + ) + layout.addWidget(self.bar) + + def update_value(self, value: float): + self.value_lbl.setText(f"{value:.3f}") + rng = self.ch.max_value - self.ch.min_value + if rng > 0: + norm = int(((value - self.ch.min_value) / rng) * 1000) + self.bar.setValue(max(0, min(1000, norm))) + + def set_alarm(self, active: bool): + if active != self._alarm_active: + self._alarm_active = active + self.setProperty("alarm", active) + self.style().unpolish(self) + self.style().polish(self) + + +class ReadoutPanel(QWidget): + def __init__(self, registry: DeviceRegistry): + super().__init__() + self.registry = registry + self._readouts: dict = {} # (device_id, channel_id) -> ChannelReadout + self._build_ui() + self.refresh() + + def _build_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + hdr = QLabel(" CHANNELS") + hdr.setObjectName("panelHeader") + 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(): + self._inner.removeWidget(w) + w.deleteLater() + self._readouts.clear() + + for dev in self.registry.all_instances(): + for ch in dev.info.channels: + if not ch.enabled: + continue + ro = ChannelReadout(dev.info.name, 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, device_id: str, channel_id: str, timestamp: float, value: float): + ro = self._readouts.get((device_id, channel_id)) + if ro: + ro.update_value(value) + ch = ro.ch + alarm = False + if ch.alarm_low is not None and value < ch.alarm_low: + alarm = True + if ch.alarm_high is not None and value > ch.alarm_high: + alarm = True + ro.set_alarm(alarm) |
