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
|
"""
devices/temperature.py
Temperature sensor module — thermocouple / RTD / thermistor inputs.
"""
import math
import random
import time
from typing import Any, Dict
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QFormLayout, QGroupBox,
QComboBox, QDoubleSpinBox, QLabel, QCheckBox
)
from devices.base_device import BaseDevice, ChannelConfig, DeviceInfo, DeviceStatus
TEMP_COLORS = ["#ff6b35", "#ffcc00", "#c77dff", "#ff4d6d"]
class TemperatureDevice(BaseDevice):
DEVICE_TYPE = "temperature"
ICON = "🌡"
def __init__(self, device_id: str = "temp_0", num_channels: int = 4,
simulate: bool = True, sensor_type: str = "thermocouple"):
channels = [
ChannelConfig(
channel_id=f"tc{i}", name=f"TC {i}",
unit="°C", min_value=-200.0, max_value=1200.0,
alarm_low=0.0, alarm_high=100.0,
color=TEMP_COLORS[i % len(TEMP_COLORS)]
)
for i in range(num_channels)
]
info = DeviceInfo(
device_id=device_id, name="Temperature",
device_type=self.DEVICE_TYPE,
description=f"{sensor_type.title()} temperature input",
icon=self.ICON, channels=channels
)
super().__init__(info)
self.simulate = simulate
self.sensor_type = sensor_type
self._start = time.time()
# Simulate slow thermal drift
self._targets = [20.0 + i * 5 for i in range(num_channels)]
self._currents = [20.0 + i * 5 for i in range(num_channels)]
def connect(self) -> bool:
self._start = time.time()
self.status = DeviceStatus.SIMULATED if self.simulate else DeviceStatus.ERROR
return self.simulate
def disconnect(self) -> None:
self.status = DeviceStatus.DISCONNECTED
def read_channels(self) -> Dict[str, float]:
result = {}
for i, ch in enumerate(self.info.channels):
if not ch.enabled:
continue
# Slow drift toward target with noise
diff = self._targets[i] - self._currents[i]
self._currents[i] += diff * 0.05 + random.gauss(0, 0.02)
# Occasionally shift target
if random.random() < 0.01:
self._targets[i] += random.gauss(0, 2.0)
self._targets[i] = max(10.0, min(200.0, self._targets[i]))
result[ch.channel_id] = round(self._currents[i], 2)
return result
def write_channel(self, channel_id: str, value: Any) -> bool:
return False # Read-only
def get_config_widget(self) -> QWidget:
w = QWidget()
layout = QVBoxLayout(w)
grp = QGroupBox("Sensor Configuration")
form = QFormLayout(grp)
sensor_cb = QComboBox()
sensor_cb.addItems(["thermocouple", "rtd", "thermistor", "ic_sensor"])
sensor_cb.setCurrentText(self.sensor_type)
form.addRow("Sensor Type:", sensor_cb)
tc_type = QComboBox()
tc_type.addItems(["K", "J", "T", "E", "N", "R", "S", "B"])
form.addRow("TC Type:", tc_type)
unit_cb = QComboBox()
unit_cb.addItems(["°C", "°F", "K"])
form.addRow("Units:", unit_cb)
layout.addWidget(grp)
# Alarm config per channel
alarm_grp = QGroupBox("Alarm Setpoints")
alarm_layout = QVBoxLayout(alarm_grp)
for ch in self.info.channels:
row_layout = QFormLayout()
lo = QDoubleSpinBox()
lo.setRange(-200, 1200)
lo.setValue(ch.alarm_low or 0.0)
lo.setSuffix(" °C")
hi = QDoubleSpinBox()
hi.setRange(-200, 1200)
hi.setValue(ch.alarm_high or 100.0)
hi.setSuffix(" °C")
row_layout.addRow(f"{ch.name} Low:", lo)
row_layout.addRow(f"{ch.name} High:", hi)
alarm_layout.addLayout(row_layout)
layout.addWidget(alarm_grp)
layout.addStretch()
return w
|