summaryrefslogtreecommitdiff
path: root/daq_system/ui/readout_panel.py
blob: 6086c30e325eeefd7a5c7a3c8267fef5b536c883 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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)