From e1565e2dfc65c6c8fdfce8c71d5ce3609596fb5e Mon Sep 17 00:00:00 2001 From: atse Date: Fri, 12 Jul 2024 17:19:51 +0800 Subject: [PATCH] Try make the i_set parameter adjustable pid "Auto" box --- pytec/pytec/gui/view/ctrl_panel.py | 103 +++++++++++++++++--------- pytec/pytec/gui/view/param_tree.json | 105 ++++++++++++--------------- pytec/tec_qt.py | 23 ++++-- 3 files changed, 129 insertions(+), 102 deletions(-) diff --git a/pytec/pytec/gui/view/ctrl_panel.py b/pytec/pytec/gui/view/ctrl_panel.py index 267fed6..b747d1b 100644 --- a/pytec/pytec/gui/view/ctrl_panel.py +++ b/pytec/pytec/gui/view/ctrl_panel.py @@ -1,45 +1,81 @@ from PyQt6.QtCore import pyqtSignal, QObject, QSignalBlocker, pyqtSlot +from PyQt6.QtWidgets import QCheckBox import pyqtgraph.parametertree.parameterTypes as pTypes from pyqtgraph.parametertree import ( Parameter, - registerParameterType, + registerParameterItemType, ) -class MutexParameter(pTypes.ListParameter): - """ - Mutually exclusive parameter where only one of its children is visible at a time, list selectable. +class SimpleAutoParameter(pTypes.SimpleParameter): - The ordering of the list items determines which children will be visible. - """ + sigAutoChanged = pyqtSignal(object, object) ## self, auto def __init__(self, **opts): super().__init__(**opts) + if "auto" not in self.opts: + self.opts["auto"] = False - self.sigValueChanged.connect(self.show_chosen_child) - self.sigValueChanged.emit(self, self.opts["value"]) + self.sigAutoChanged.connect(self._emitAutoChanged) - def _get_param_from_value(self, value): - if isinstance(self.opts["limits"], dict): - values_list = list(self.opts["limits"].values()) - else: - values_list = self.opts["limits"] + def setAuto(self, auto, blockSignal=None): + try: + if blockSignal is not None: + self.sigAutochanged.disconnect(blockSignal) - return self.children()[values_list.index(value)] + if self.opts["auto"] == auto: + return auto + self.opts["auto"] = auto + self.sigAutoChanged.emit(self, auto) + finally: + if blockSignal is not None: + self.sigAutoChanged.connect(blockSignal) - @pyqtSlot(object, object) - def show_chosen_child(self, value): - for param in self.children(): - param.hide() + return auto - child_to_show = self._get_param_from_value(value.value()) - child_to_show.show() - - if child_to_show.opts.get("triggerOnShow", None): - child_to_show.sigValueChanged.emit(child_to_show, child_to_show.value()) + def _emitAutoChanged(self, param, data): + self.emitStateChanged("auto", data) -registerParameterType("mutex", MutexParameter) +class NumericWithAutoParameterItem(pTypes.NumericParameterItem): + """ + Subclasses `NumericParameterItem` to add an auto checkbox option + + ========================== ============================================================= + **Registered Types:** + int_auto Displays a :class:`SpinBox ` in integer + mode, with a :class:`QCheckBox ` QCheckBox beside it. + float_auto Displays a :class:`SpinBox `, with a + :class:`QCheckBox ` QCheckBox beside it. + ========================== ============================================================= + """ + + def __init__(self, param, depth): + # Defer normal numeric handling to base class + t = param.opts["type"] + param.opts["type"] = t.removesuffix("_auto") + super().__init__(param, depth) + + # Set up the auto checkbox and insert it to the right of the SpinBox + auto_lbl_txt = param.opts.get("auto_label", "Auto") + auto_tip = param.opts.get("auto_tip") + self.auto_checkbox = QCheckBox(auto_lbl_txt) + self.auto_checkbox.setChecked(param.opts.get("auto")) + self.auto_checkbox.setToolTip(auto_tip) + self.layoutWidget.layout().insertWidget(2, self.auto_checkbox) + + # Connect it up + self.auto_checkbox.toggled.connect(self.param.setAuto) + param.sigAutoChanged.connect(self.auto_changed) + + def auto_changed(self, param, auto): + self.auto_checkbox.setChecked(auto) + + +registerParameterItemType("int_auto", NumericWithAutoParameterItem, SimpleAutoParameter) +registerParameterItemType( + "float_auto", NumericWithAutoParameterItem, SimpleAutoParameter +) def set_tree_label_tips(tree): @@ -125,6 +161,7 @@ class CtrlPanel(QObject): for settings in pid_settings: channel = settings["channel"] with QSignalBlocker(self.params[channel]): + self.params[channel].child("pid", "target").setValue(settings["target"]) self.params[channel].child("pid", "kp").setValue( settings["parameters"]["kp"] ) @@ -140,29 +177,25 @@ class CtrlPanel(QObject): self.params[channel].child( "pid", "pid_output_clamping", "output_max" ).setValue(settings["parameters"]["output_max"] * 1000) - self.params[channel].child( - "output", "control_method", "target" - ).setValue(settings["target"]) @pyqtSlot("QVariantList") def update_report(self, report_data): for settings in report_data: channel = settings["channel"] with QSignalBlocker(self.params[channel]): - self.params[channel].child("output", "control_method").setValue( - "temperature_pid" if settings["pid_engaged"] else "constant_current" + self.params[channel].child("output", "i_set").setValue( + settings["i_set"] * 1000 ) - self.params[channel].child( - "output", "control_method", "i_set" - ).setValue(settings["i_set"] * 1000) + self.params[channel].child("output", "i_set").setAuto( + settings["pid_engaged"] + ) + if settings["temperature"] is not None: self.params[channel].child("temperature").setValue( settings["temperature"] ) if settings["tec_i"] is not None: - self.params[channel].child("tec_i").setValue( - settings["tec_i"] - ) + self.params[channel].child("tec_i").setValue(settings["tec_i"]) @pyqtSlot("QVariantList") def update_thermistor(self, sh_data): diff --git a/pytec/pytec/gui/view/param_tree.json b/pytec/pytec/gui/view/param_tree.json index 3beb9d3..cd48413 100644 --- a/pytec/pytec/gui/view/param_tree.json +++ b/pytec/pytec/gui/view/param_tree.json @@ -25,66 +25,33 @@ "type":"group", "tip": "Settings of the output through to the load", "children":[ - { - "name":"control_method", - "title":"Control Method", - "type":"mutex", - "limits":{ - "Constant Current": "constant_current", - "Temperature PID": "temperature_pid" - }, - "activaters":[ - null, - [ - "pwm", - "$ch", - "pid" - ] + { + "name":"i_set", + "title":"Set Current (mA)", + "type":"float_auto", + "value":0, + "step":100, + "limits":[ + -2000, + 2000 ], - "tip":"Select control method of output", - "children":[ - { - "name":"i_set", - "title":"Set Current (mA)", - "type":"float", - "value":0, - "step":100, - "limits":[ - -2000, - 2000 - ], - "triggerOnShow":true, - "decimals":6, - "compactHeight":false, - "param":[ - "pwm", - "$ch", - "i_set" - ], - "tip": "Set current through output", - "lock":false - }, - { - "name": "target", - "title":"Set Temperature (°C)", - "type":"float", - "value":25, - "step":0.1, - "limits":[ - -273, - 300 - ], - "format":"{value:.4f}", - "compactHeight":false, - "param":[ - "pid", - "$ch", - "target" - ], - "tip": "Set target temperature", - "lock":false - } - ] + "decimals":6, + "compactHeight":false, + "param":[ + "pwm", + "$ch", + "i_set" + ], + "auto":false, + "auto_label":"PID", + "auto_tip":"Control by PID", + "auto_param":[ + "pwm", + "$ch", + "pid" + ], + "tip": "", + "lock":false }, { "name":"limits", @@ -244,6 +211,26 @@ "type":"group", "tip": "", "children":[ + { + "name":"target", + "title":"Setpoint (°C)", + "type":"float", + "value":25, + "step":0.1, + "limits":[ + -273, + 300 + ], + "format":"{value:.4f}", + "compactHeight":false, + "param":[ + "pid", + "ch", + "target" + ], + "tip": "", + "lock":false + }, { "name":"kp", "title":"Kp", diff --git a/pytec/tec_qt.py b/pytec/tec_qt.py index 6ccad8c..b379823 100644 --- a/pytec/tec_qt.py +++ b/pytec/tec_qt.py @@ -296,14 +296,21 @@ class MainWindow(QtWidgets.QMainWindow): ch = inner_param.opts["pid_autotune"][1] self.autotuners.set_params(auto_tuner_param, ch, data) - if inner_param.opts.get("activaters", None) is not None: - activater = inner_param.opts["activaters"][ - inner_param.reverse[0].index(data) # ListParameter.reverse = list of codename values - ] - if activater is not None: - if activater[1] == "$ch": - activater[1] = ch - await self.client.set_param(*activater) + if change == "auto": + is_auto = data + if is_auto: + if "auto_param" in inner_param.opts: + thermostat_param = inner_param.opts["auto_param"] + else: + thermostat_param = [*inner_param.opts["param"], inner_param.value() / 1000] + + if thermostat_param[1] == "$ch": + thermostat_param[1] = ch + + inner_param.setOpts(lock=True) + await self.client.set_param(*thermostat_param) + inner_param.setOpts(lock=False) + @asyncSlot() async def pid_auto_tune_request(self, ch=0):