""" 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