diff --git a/debye_bec/bec_widgets/widgets/temperature_controller/temperature_controller.py b/debye_bec/bec_widgets/widgets/temperature_controller/temperature_controller.py index 27a837b..23a0f2c 100644 --- a/debye_bec/bec_widgets/widgets/temperature_controller/temperature_controller.py +++ b/debye_bec/bec_widgets/widgets/temperature_controller/temperature_controller.py @@ -18,25 +18,28 @@ from bec_widgets.widgets.plots.waveform.waveform import Waveform # pylint: disable=E0611 from qtpy.QtCore import QTimer -from qtpy.QtWidgets import QApplication, QHBoxLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QApplication, QHBoxLayout, QLabel, QVBoxLayout, QWidget from ....devices.univ_temp_cont.univ_temp_cont_enums import ( POWER_MAP, RAMP_RATE_MAP, + THERMOCOUPLE_MAP, + UnivTempContAutoTune, UnivTempContHeaterTcFault, UnivTempContOpMode, UnivTempContPower, UnivTempContRampRateUnit, UnivTempContSensorBreak, ) -from .qt_widgets import ComboBox, Group, InputNumberField, NumberIndicator, TextIndicator +from .qt_widgets import Button, ComboBox, Group, InputNumberField, NumberIndicator, TextIndicator logger = bec_logger.logger DEVICE_NAME = "univ_temp_cont" DEFAULT_WINDOW_S = 600 -REFRESH_MS = 200 +MAX_WINDOW_S = 36000 +REFRESH_MS = 500 PLOT_SIGNALS = ["temperature", "setpoint", "output_power"] READ_CONFIG_SIGNAL_MS = 1000 @@ -108,14 +111,54 @@ class TemperatureController(BECWidget, QWidget): ], ) + self.pid_pb = InputNumberField("pb", "PB", decimals=0, single_step=1, ll=1, hl=9999) + self.pid_ti = InputNumberField( + "ti", "TI", decimals=0, single_step=1, ll=0, hl=9999, unit="s" + ) + self.pid_td = InputNumberField( + "td", "TD", decimals=0, single_step=1, ll=0, hl=9999, unit="s" + ) + + self.autotune = Button("Autotune", "Start", enabled=True, color="success") + self.autotune_status = TextIndicator("Autotune Status") + + pid_loop_group = Group( + "PID loop", [self.pid_pb, self.pid_ti, self.pid_td, self.autotune, self.autotune_status] + ) + + self.tc_info = QLabel("Controller will restart when changing value") + self.thermocouple = ComboBox("thermocouple", "Thermocouple", THERMOCOUPLE_MAP.keys()) + + configuration_group = Group("Configuration", [self.tc_info, self.thermocouple]) + control_layout.addWidget(status_group) control_layout.addWidget(control_group) + control_layout.addWidget(pid_loop_group) + control_layout.addWidget(configuration_group) control_layout.addStretch() root_layout.addLayout(control_layout) + waveform_layout = QVBoxLayout() + self.waveform = Waveform(parent=self, client=self.client, popups=True) - root_layout.addWidget(self.waveform, stretch=1) + + waveform_layout.addWidget(self.waveform, stretch=1) + + self.user_window_s = InputNumberField( + "", + "Plot range", + "s", + init=DEFAULT_WINDOW_S, + decimals=0, + single_step=60, + ll=1, + hl=MAX_WINDOW_S, + ) + + waveform_layout.addWidget(self.user_window_s) + + root_layout.addLayout(waveform_layout, stretch=1) self.waveform.set(title="", x_label="Time") self.waveform.enable_toolbar = False @@ -146,6 +189,11 @@ class TemperatureController(BECWidget, QWidget): self.target_setpoint.editing_finished_connect(self._on_value_changed) self.ramp_rate.editing_finished_connect(self._on_value_changed) self.ramp_rate_unit.activated_connect(self._on_value_changed) + self.pid_pb.editing_finished_connect(self._on_value_changed) + self.pid_ti.editing_finished_connect(self._on_value_changed) + self.pid_td.editing_finished_connect(self._on_value_changed) + self.autotune.clicked_connect(self._start_stop_autotune) + self.thermocouple.activated_connect(self._on_value_changed) self.plot_init = True @@ -158,6 +206,12 @@ class TemperatureController(BECWidget, QWidget): signal = getattr(self.dev.univ_temp_cont, kwargs["identifier"]) signal.put(value) + @SafeSlot() + def _start_stop_autotune(self, *_): + signal = getattr(self.dev.univ_temp_cont, "autotune") + val = signal.read()["autotune"]["value"] + signal.put(not val) + @SafeSlot(dict, dict) def _on_readback(self, msg: dict, *_): signals = msg.get("signals", {}) @@ -229,25 +283,57 @@ class TemperatureController(BECWidget, QWidget): self.ramp_rate.set_number(value) def _config_signal_read(self): + # Ramp rate unit value = self.dev.univ_temp_cont.ramp_rate_unit.read() - if value is None: - raise TemperatureControllerError("Reading ramp rate unit returned None!") index = value[DEVICE_NAME + "_ramp_rate_unit"]["value"] self.ramp_rate_unit.setCurrentIndex(index) self.ramp_rate.setUnit( list(RAMP_RATE_MAP.keys())[list(RAMP_RATE_MAP.values()).index(index)] ) + # PB + value = self.dev.univ_temp_cont.pb.read()[DEVICE_NAME + "_pb"]["value"] + self.pid_pb.set_number(value) + # TI + value = self.dev.univ_temp_cont.ti.read()[DEVICE_NAME + "_ti"]["value"] + self.pid_ti.set_number(value) + # TD + value = self.dev.univ_temp_cont.td.read()[DEVICE_NAME + "_td"]["value"] + self.pid_td.set_number(value) + # Autotune + value = self.dev.univ_temp_cont.autotune.read() + index = value[DEVICE_NAME + "_autotune"]["value"] + match index: + case UnivTempContAutoTune.NOTUNE: + self.autotune.setText("Start") + self.autotune.setColor("success") + self.autotune_status.setText("Not active") + self.autotune_status.setColor("default") + case UnivTempContAutoTune.TUNE: + self.autotune.setText("Stop") + self.autotune.setColor("emergency") + self.autotune_status.setText("Active") + self.autotune_status.setColor("warning") + case _: + raise TemperatureControllerError(f"Not implemented autotune value {index}") + # Thermocouple + value = self.dev.univ_temp_cont.thermocouple.read() + index = value[DEVICE_NAME + "_thermocouple"]["value"] + self.thermocouple.setCurrentIndex(index) def _redraw(self): now = time.time() - cutoff = now - self._window_s + cutoff = now - MAX_WINDOW_S + view_cutoff = now - min(self.user_window_s.value(), MAX_WINDOW_S) + for key, buf in self._buffers.items(): while buf and buf[0][0] < cutoff: buf.popleft() if not buf: continue - t, v = zip(*buf) - self._curves[key].set_data(np.asarray(t), np.asarray(v)) + + t, v = map(np.asarray, zip(*buf)) + i = np.searchsorted(t, view_cutoff, side="left") + self._curves[key].set_data(t[i:], v[i:]) if len(self._buffers[PLOT_SIGNALS[0]]) > 0 and self.plot_init: self.plot_init = False self.waveform.plot_item.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True) diff --git a/debye_bec/devices/univ_temp_cont/univ_temp_cont_enums.py b/debye_bec/devices/univ_temp_cont/univ_temp_cont_enums.py index ab1c7c9..97fc191 100644 --- a/debye_bec/devices/univ_temp_cont/univ_temp_cont_enums.py +++ b/debye_bec/devices/univ_temp_cont/univ_temp_cont_enums.py @@ -54,6 +54,12 @@ class UnivTempContThermocouple(int, Enum): Type_K = 1 +THERMOCOUPLE_MAP = { + "Type J": UnivTempContThermocouple.TYPE_J, + "Type K": UnivTempContThermocouple.Type_K, +} + + class UnivTempContPower(int, Enum): """Class for power mode"""