From c59e3e7ac4d1f755a5b2da063ca5a3fa1a771c43 Mon Sep 17 00:00:00 2001 From: atse <atse@m-labs.hk> Date: Wed, 31 Jul 2024 16:02:12 +0800 Subject: [PATCH] ctrl_panel: Remove need for "mA" hack Remove all instances of mA scaling scattered all around the code and specify it in the parameter tree with a single source of truth. Done by adding the option "pinSiPrefix" for all Parameters of type `int` or `float`, and using it for current Parameters with unit "mA". --- pytec/pytec/gui/view/ctrl_panel.py | 11 ++-- pytec/pytec/gui/view/param_tree.json | 56 +++++++++------- pytec/pytec/gui/view/pin_si_prefix.py | 95 +++++++++++++++++++++++++++ pytec/tec_qt.py | 3 - 4 files changed, 135 insertions(+), 30 deletions(-) create mode 100644 pytec/pytec/gui/view/pin_si_prefix.py diff --git a/pytec/pytec/gui/view/ctrl_panel.py b/pytec/pytec/gui/view/ctrl_panel.py index b68c4fd..c907591 100644 --- a/pytec/pytec/gui/view/ctrl_panel.py +++ b/pytec/pytec/gui/view/ctrl_panel.py @@ -4,6 +4,7 @@ from pyqtgraph.parametertree import ( Parameter, registerParameterType, ) +import pytec.gui.view.pin_si_prefix class MutexParameter(pTypes.ListParameter): @@ -136,10 +137,10 @@ class CtrlPanel(QObject): ) self.params[channel].child( "pid", "pid_output_clamping", "output_min" - ).setValue(settings["parameters"]["output_min"] * 1000) + ).setValue(settings["parameters"]["output_min"]) self.params[channel].child( "pid", "pid_output_clamping", "output_max" - ).setValue(settings["parameters"]["output_max"] * 1000) + ).setValue(settings["parameters"]["output_max"]) self.params[channel].child( "output", "control_method", "target" ).setValue(settings["target"]) @@ -154,7 +155,7 @@ class CtrlPanel(QObject): ) self.params[channel].child( "output", "control_method", "i_set" - ).setValue(settings["i_set"] * 1000) + ).setValue(settings["i_set"]) if settings["temperature"] is not None: self.params[channel].child("temperature").setValue( settings["temperature"] @@ -188,10 +189,10 @@ class CtrlPanel(QObject): pwm_params["max_v"]["value"] ) self.params[channel].child("output", "limits", "max_i_pos").setValue( - pwm_params["max_i_pos"]["value"] * 1000 + pwm_params["max_i_pos"]["value"] ) self.params[channel].child("output", "limits", "max_i_neg").setValue( - pwm_params["max_i_neg"]["value"] * 1000 + pwm_params["max_i_neg"]["value"] ) for limit in "max_i_pos", "max_i_neg", "max_v": diff --git a/pytec/pytec/gui/view/param_tree.json b/pytec/pytec/gui/view/param_tree.json index 1ebc70d..d737f9c 100644 --- a/pytec/pytec/gui/view/param_tree.json +++ b/pytec/pytec/gui/view/param_tree.json @@ -48,14 +48,16 @@ "title": "Set Current", "type": "float", "value": 0, - "step": 100, + "step": 0.1, "limits": [ - -2000, - 2000 + -2, + 2 ], "triggerOnShow": true, "decimals": 6, - "suffix": "mA", + "pinSiPrefix": "m", + "suffix": "A", + "siPrefix": true, "param": [ "pwm", "ch", @@ -97,13 +99,15 @@ "title": "Max Cooling Current", "type": "float", "value": 0, - "step": 100, + "step": 0.1, "decimals": 6, "limits": [ 0, - 2000 + 2 ], - "suffix": "mA", + "siPrefix": true, + "pinSiPrefix": "m", + "suffix": "A", "param": [ "pwm", "ch", @@ -117,13 +121,15 @@ "title": "Max Heating Current", "type": "float", "value": 0, - "step": 100, + "step": 0.1, "decimals": 6, + "siPrefix": true, + "pinSiPrefix": "m", + "suffix": "A", "limits": [ 0, - 2000 + 2 ], - "suffix": "mA", "param": [ "pwm", "ch", @@ -296,13 +302,15 @@ "name": "output_min", "title": "Minimum", "type": "float", - "step": 100, + "step": 0.1, "limits": [ - -2000, - 2000 + -2, + 2 ], "decimals": 6, - "suffix": "mA", + "siPrefix": true, + "pinSiPrefix": "m", + "suffix": "A", "param": [ "pid", "ch", @@ -315,13 +323,15 @@ "name": "output_max", "title": "Maximum", "type": "float", - "step": 100, + "step": 0.1, "limits": [ - -2000, - 2000 + -2, + 2 ], "decimals": 6, - "suffix": "mA", + "siPrefix": true, + "pinSiPrefix": "m", + "suffix": "A", "param": [ "pid", "ch", @@ -358,12 +368,14 @@ "type": "float", "value": 0, "decimals": 6, - "step": 100, + "step": 0.1, "limits": [ - -2000, - 2000 + -2, + 2 ], - "suffix": "mA", + "siPrefix": true, + "pinSiPrefix": "m", + "suffix": "A", "pid_autotune": [ "test_current", "ch" diff --git a/pytec/pytec/gui/view/pin_si_prefix.py b/pytec/pytec/gui/view/pin_si_prefix.py new file mode 100644 index 0000000..1b0ad3d --- /dev/null +++ b/pytec/pytec/gui/view/pin_si_prefix.py @@ -0,0 +1,95 @@ +from pyqtgraph import SpinBox +import pyqtgraph.functions as fn +from pyqtgraph.parametertree import registerParameterItemType +from pyqtgraph.parametertree.parameterTypes import SimpleParameter, NumericParameterItem + + +class PinSIPrefixSpinBox(SpinBox): + """ + Extension of PyQtGraph's SpinBox widget. + Adds: + + * The "pinSiPrefix" option, where the siPrefix could be fixed to a + particular scale instead of as determined by its value. + """ + + def setOpts(self, **opts): + if "pinSiPrefix" in opts: + self.opts["pinSiPrefix"] = opts.pop("pinSiPrefix") + + super().setOpts(**opts) + + def formatText(self, prev=None): + """ + In addition to pyqtgraph.SpinBox's formatting, incorporate the + 'pinSiPrefix' mechanism, where SI prefixes could be fixed. + """ + # Code modified from the PyQtGraph source + + # get the number of decimal places to print + decimals = self.opts['decimals'] + suffix = self.opts['suffix'] + prefix = self.opts['prefix'] + pin_si_prefix = self.opts.get("pinSiPrefix") + + # format the string + val = self.value() + if self.opts['siPrefix'] is True: + # SI prefix was requested, so scale the value accordingly + if pin_si_prefix is not None and pin_si_prefix in fn.SI_PREFIX_EXPONENTS: + # fixed scale + s = 10**-fn.SI_PREFIX_EXPONENTS[pin_si_prefix] + p = pin_si_prefix + elif self.val == 0 and prev is not None: + # special case: if it's zero use the previous prefix + (s, p) = fn.siScale(prev) + else: + (s, p) = fn.siScale(val) + parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': p, 'scaledValue': s*val, 'prefix':prefix} + + else: + # no SI prefix /suffix requested; scale is 1 + parts = {'value': val, 'suffix': suffix, 'decimals': decimals, 'siPrefix': '', 'scaledValue': val, 'prefix':prefix} + + parts['prefixGap'] = '' if parts['prefix'] == '' else ' ' + parts['suffixGap'] = '' if (parts['suffix'] == '' and parts['siPrefix'] == '') else ' ' + + return self.opts['format'].format(**parts) + + +class PinSIPrefixNumericParameterItem(NumericParameterItem): + """ + Subclasses PyQtGraph's `NumericParameterItem` and uses + PinSIPrefixSpinBox for editing. + """ + + def makeWidget(self): + opts = self.param.opts + t = opts['type'] + defs = { + 'value': 0, 'min': None, 'max': None, + 'step': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '', 'decimals': 3, + 'pinSiPrefix': None, + } + if t == 'int': + defs['int'] = True + defs['minStep'] = 1.0 + for k in defs: + if k in opts: + defs[k] = opts[k] + if 'limits' in opts: + defs['min'], defs['max'] = opts['limits'] + w = PinSIPrefixSpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + return w + + +registerParameterItemType( + "float", PinSIPrefixNumericParameterItem, SimpleParameter, override=True +) +registerParameterItemType( + "int", PinSIPrefixNumericParameterItem, SimpleParameter, override=True +) diff --git a/pytec/tec_qt.py b/pytec/tec_qt.py index 6047e34..0ec1a18 100644 --- a/pytec/tec_qt.py +++ b/pytec/tec_qt.py @@ -267,9 +267,6 @@ class MainWindow(QtWidgets.QMainWindow): for inner_param, change, data in changes: if change == "value": if inner_param.opts.get("param", None) is not None: - if inner_param.opts.get("suffix", None) == "mA": - data /= 1000 # Given in mA - thermostat_param = inner_param.opts["param"] if thermostat_param[1] == "ch": thermostat_param[1] = ch