From 73796a47283a3951fad8f550068c9e8d99fd14af Mon Sep 17 00:00:00 2001 From: linuswck Date: Thu, 25 Jul 2024 15:56:31 +0800 Subject: [PATCH] gui: move unit to ctrl panel params' titles --- pykirdy/kirdy_qt.py | 122 ++++++++++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 34 deletions(-) diff --git a/pykirdy/kirdy_qt.py b/pykirdy/kirdy_qt.py index bc81b3f..9364c4b 100644 --- a/pykirdy/kirdy_qt.py +++ b/pykirdy/kirdy_qt.py @@ -5,6 +5,7 @@ from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, reg import pyqtgraph as pg pg.setConfigOptions(antialias=True) from pyqtgraph import mkPen +from pyqtgraph.functions import siEval, siParse, SI_PREFIX_EXPONENTS, FLOAT_REGEX from pglive.sources.live_axis_range import LiveAxisRange from pglive.sources.data_connector import DataConnector from pglive.kwargs import Axis, LeadingLine @@ -45,6 +46,26 @@ def get_argparser(): return parser +def siConvert(val, suffix, typ=float): + """ + Convert a value written in SI notation according to given Si Scale. + + Example:: + + siConvert(0.1, "mA") # returns 100 + """ + val, siprefix, suffix = siParse(str(val)+suffix, FLOAT_REGEX) + v = typ(val) + + n = -SI_PREFIX_EXPONENTS[siprefix] if siprefix != '' else 0 + if n > 0: + return v * 10**n + elif n < 0: + # this case makes it possible to use Decimal objects here + return v / 10**-n + else: + return v + class KirdyDataWatcher(QObject): """ This class provides various signals for Mainwindow to update various kinds of GUI objects @@ -341,18 +362,18 @@ class MainWindow(QtWidgets.QMainWindow): {'name': 'LF Mod Termination (50 Ohm)', 'type': 'list', 'limits': ['On', 'Off'], 'readonly': True} ]}, {'name': 'Output Config', 'expanded': True, 'type': 'group', 'children': [ - {'name': 'LD Current Set', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 1), - 'suffix': 'A', 'siPrefix': True, 'lock': False, 'target': 'laser', 'action': 'set_i'}, + {'name': 'LD Current Set', 'type': 'float', 'value': 0, 'step': 0.001, 'decimals': 6, 'limits': (0, 300), + 'unit': 'mA', 'lock': False, 'target': 'laser', 'action': 'set_i'}, {'name': 'LD Terminals Short', 'type': 'bool', 'value': False, 'lock': False, 'target': 'laser', 'action': 'set_ld_terms_short'}, {'name': 'Default Power On', 'type': 'bool', 'value': False, 'lock': False, 'target': 'laser', 'action': 'set_default_pwr_on'}, ]}, {'name': 'Photodiode Monitor Config', 'expanded': False, 'type': 'group', 'children': [ - {'name': 'LD Power Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 0.3), - 'suffix': 'W', 'siPrefix': True, 'lock': False, 'target': 'laser', 'action': 'set_ld_pwr_limit'}, - {'name': 'Responsitivity', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 3000), - 'suffix': 'A/W', 'siPrefix': True, 'lock': False, 'target': 'laser', 'action': 'set_pd_mon_responsitivity'}, - {'name': 'Dark Current', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 3000), - 'suffix': 'A', 'siPrefix': True, 'lock': False, 'target': 'laser', 'action': 'set_pd_mon_dark_current'}, + {'name': 'LD Power Limit', 'type': 'float', 'value': 0, 'step': 0.001, 'decimals': 6, 'limits': (0, float("inf")), + 'unit': 'mW', 'lock': False, 'target': 'laser', 'action': 'set_ld_pwr_limit'}, + {'name': 'Responsitivity', 'type': 'float', 'value': 0, 'step': 0.001, 'decimals': 6, 'limits': (0, float("inf")), + 'unit': 'mA/W', 'siPrefix': True, 'lock': False, 'target': 'laser', 'action': 'set_pd_mon_responsitivity'}, + {'name': 'Dark Current', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, float("inf")), + 'unit': 'uA', 'lock': False, 'target': 'laser', 'action': 'set_pd_mon_dark_current'}, ]}, ] @@ -368,49 +389,52 @@ class MainWindow(QtWidgets.QMainWindow): ]}, {'name': 'Output Config', 'expanded': True, 'type': 'group', 'children': [ {'name': 'Control Method', 'type': 'mutex', 'limits': ['Constant Current', 'Temperature PID'], - 'target_action_pair': [['thermostat', 'set_constant_current_control_mode'], ['thermostat', 'set_pid_control_mode']], 'children': [ - {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 0.001, 'limits': (-1, 1), 'triggerOnShow': True, - 'decimals': 6, 'suffix': 'A', 'siPrefix': True, 'lock': False, 'target': 'thermostat', 'action': 'set_tec_i_out'}, - {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.1, 'limits': (-273, 300), - 'format': '{value:.4f} °C', 'lock': False, 'target': 'thermostat', 'action': 'set_temperature_setpoint'}, + 'target_action_pair': [['thermostat', 'set_constant_current_control_mode'], ['thermostat', 'set_pid_control_mode']], 'children': [ + {'name': 'Set Current', 'type': 'float', 'value': 0, 'step': 1, 'limits': (-1000, 1000), 'triggerOnShow': True, 'decimals': 6, + 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_tec_i_out'}, + {'name': 'Set Temperature', 'type': 'float', 'value': 25, 'step': 0.0001, 'limits': (-273, 300), 'format': '{value:.4f}', + 'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temperature_setpoint'}, ]}, {'name': 'Limits', 'expanded': False, 'type': 'group', 'children': [ - {'name': 'Max Cooling Current', 'type': 'float', 'value': 0, 'step': 0.001, 'decimals': 6, 'limits': (0, 1), - 'suffix': 'A', 'siPrefix': True, 'lock': False, 'target': 'thermostat', 'action': 'set_tec_max_cooling_i'}, - {'name': 'Max Heating Current', 'type': 'float', 'value': 0, 'step': 0.001, 'decimals': 6, 'limits': (0, 1), - 'suffix': 'A', 'siPrefix': True, 'lock': False, 'target': 'thermostat', 'action': 'set_tec_max_heating_i'}, - {'name': 'Max Voltage Difference', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (0, 5), - 'suffix': 'V', 'siPrefix': True, 'lock': False, 'target': 'thermostat', 'action': 'set_tec_max_v'}, + {'name': 'Max Cooling Current', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 1000), + 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_tec_max_cooling_i'}, + {'name': 'Max Heating Current', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 1000), + 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_tec_max_heating_i'}, + {'name': 'Max Voltage Difference', 'type': 'float', 'value': 0, 'step': 0.1, 'limits': (0, 4), + 'unit': 'V', 'lock': False, 'target': 'thermostat', 'action': 'set_tec_max_v'}, ]}, {'name': 'Default Power On', 'type': 'bool', 'value': False, 'lock': False, 'target': 'thermostat', 'action': 'set_default_pwr_on'}, ]}, # TODO Temperature ADC Filter Settings {'name': 'Temperature Monitor Config', 'expanded': False, 'type': 'group', 'children': [ {'name': 'Upper Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300), - 'suffix': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_upper_limit'}, + 'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_upper_limit'}, {'name': 'Lower Limit', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300), - 'suffix': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_lower_limit'}, + 'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_temp_mon_lower_limit'}, ]}, {'name': 'Thermistor Settings','expanded': False, 'type': 'group', 'children': [ {'name': 'T₀', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (-273, 300), - 'suffix': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_t0'}, + 'unit': '°C', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_t0'}, {'name': 'R₀', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, - 'suffix': 'Ω', 'siPrefix': True, 'lock': False, 'target': 'thermostat', 'action': 'set_sh_r0'}, - {'name': 'B', 'type': 'float', 'value': 3950, 'step': 1, 'suffix': 'K', 'decimals': 4, 'lock': False, 'target': 'thermostat', 'action': 'set_sh_beta'}, + 'unit': 'kΩ', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_r0'}, + {'name': 'B', 'type': 'float', 'value': 3950, 'step': 1, 'decimals': 4, + 'unit': 'K', 'lock': False, 'target': 'thermostat', 'action': 'set_sh_beta'}, ]}, {'name': 'PID Config', 'expanded': False, 'type': 'group', 'children': [ - {'name': 'Kp', 'type': 'float', 'step': 0.1, 'suffix': '', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kp'}, - {'name': 'Ki', 'type': 'float', 'step': 0.1, 'suffix': 'Hz', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_ki'}, - {'name': 'Kd', 'type': 'float', 'step': 0.1, 'suffix': 's', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kd'}, + {'name': 'Kp', 'type': 'float', 'step': 0.1, 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kp'}, + {'name': 'Ki', 'type': 'float', 'step': 0.1, 'unit': 'Hz', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_ki'}, + {'name': 'Kd', 'type': 'float', 'step': 0.1, 'unit': 's', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_kd'}, {'name': "PID Output Clamping", 'expanded': True, 'type': 'group', 'children': [ - {'name': 'Minimum', 'type': 'float', 'step': 100, 'limits': (-1, 1), 'decimals': 6, 'suffix': 'A', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_min'}, - {'name': 'Maximum', 'type': 'float', 'step': 100, 'limits': (-1, 1), 'decimals': 6, 'suffix': 'A', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_max'}, + {'name': 'Minimum', 'type': 'float', 'step': 1, 'limits': (-1000, 1000), 'decimals': 6, + 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_min'}, + {'name': 'Maximum', 'type': 'float', 'step': 1, 'limits': (-1000, 1000), 'decimals': 6, + 'unit': 'mA', 'lock': False, 'target': 'thermostat', 'action': 'set_pid_output_max'}, ]}, {'name': 'PID Auto Tune', 'expanded': False, 'type': 'group', 'children': [ - {'name': 'Target Temperature', 'type': 'float', 'value': 20, 'step': 0.1, 'format': '{value:.4f} °C'}, - {'name': 'Test Current', 'type': 'float', 'value': 1000, 'decimals': 6, 'step': 100, 'limits': (-3000, 3000), 'suffix': 'mA'}, - {'name': 'Temperature Swing', 'type': 'float', 'value': 0.0, 'step': 0.1, 'prefix': '±', 'format': '{value:.4f} °C'}, - {'name': 'Lookback', 'type': 'float', 'value': 5.0, 'step': 0.1, 'format': '{value:.4f} s'}, + {'name': 'Target Temperature', 'type': 'float', 'value': 20.0, 'step': 0.1, 'unit': '°C', 'format': '{value:.4f}'}, + {'name': 'Test Current', 'type': 'float', 'value': 1000, 'decimals': 6, 'step': 100, 'limits': (-3000, 3000), 'unit': 'mA'}, + {'name': 'Temperature Swing', 'type': 'float', 'value': 0.0, 'step': 0.001, 'prefix': '±', 'unit': '°C', 'format': '{value:.4f}'}, + {'name': 'Lookback', 'type': 'float', 'value': 5.0, 'step': 0.1, 'unit': 's', 'format': '{value:.4f}'}, {'name': 'Run', 'type': 'action', 'tip': 'Run'}, ]}, ]}, @@ -449,9 +473,34 @@ class MainWindow(QtWidgets.QMainWindow): def _setValuewithLock(self, value): if not self.opts.get("lock", None): - self.setValue(value) + if self.opts.get("unit", None) is not None: + self.setValue(siConvert(value, self.opts.get("unit", None))) + else: + self.setValue(value) Parameter.setValuewithLock = _setValuewithLock + def _add_unit_to_title(param_tree): + def _traverse(param): + if param["type"] == "group" or param["type"] == "mutex": + for param in param["children"]: + _add_unit_to_title(param) + else: + if "unit" in param.keys(): + if not("title" in param.keys()): + param["title"] = param["name"] + param["title"] = param["title"] + " ({:})".format(param["unit"]) + + if isinstance(param_tree, list): + for param in param_tree: + _traverse(param) + else: + _traverse(param_tree) + + _add_unit_to_title(self.LASER_DIODE_STATUS) + _add_unit_to_title(self.LASER_DIODE_PARAMETERS) + _add_unit_to_title(self.THERMOSTAT_STATUS) + _add_unit_to_title(self.THERMOSTAT_PARAMETERS) + self.params = [ Parameter.create(name=f"Laser Diode Status", type='group', value=0, children=self.LASER_DIODE_STATUS), Parameter.create(name=f"Laser Diode Parameters", type='group', value=1, children=self.LASER_DIODE_PARAMETERS), @@ -908,6 +957,11 @@ class MainWindow(QtWidgets.QMainWindow): """ cmd translation from non-mutex type parameter""" if inner_param.opts.get("target", None) is not None: if inner_param.opts.get("action", None) is not None: + if inner_param.opts.get("unit", None) is not None: + # siParse is buggy on ° character + if not inner_param.opts["unit"] == "°C": + _, _, suffix = siParse(str(data)+inner_param.opts["unit"]) + data = siEval(str(data)+inner_param.opts["unit"], suffix=suffix) cmd = getattr(getattr(self.kirdy, inner_param.opts["target"]), inner_param.opts["action"]) param.child(*param.childPath(inner_param)).setOpts(lock=True) await cmd(data)