From 05edf98dfeb9ba0b6b319fad392cdc0ce2af9274 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Thu, 22 Sep 2022 10:25:50 +0200 Subject: [PATCH] secop_psi.entangle.AnalogInput: fix main value when the unit of parameter 'value' is taken from tango, the '$' units of other parameters are already replaced by the configured value and are not updated. this change fixes this. not yet tested on entangle, but a test with similar code works Change-Id: I87ad112b0965b39bb204d6c3d1fc1de6d4e14f60 Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/29357 Tested-by: Jenkins Automated Tests Reviewed-by: Enrico Faulhaber Reviewed-by: Markus Zolliker --- secop/modules.py | 8 ++++++-- secop_mlz/entangle.py | 9 +++++++++ test/test_modules.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/secop/modules.py b/secop/modules.py index 5e3ea67..77e8bbc 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -461,8 +461,7 @@ class Module(HasAccessibles): if mainvalue: mainunit = mainvalue.datatype.unit if mainunit: - for pname, pobj in self.parameters.items(): - pobj.datatype.set_main_unit(mainunit) + self.applyMainUnit(mainunit) # 6) check complete configuration of * properties if not errors: @@ -485,6 +484,11 @@ class Module(HasAccessibles): def __getitem__(self, item): return self.accessibles.__getitem__(item) + def applyMainUnit(self, mainunit): + """replace $ in units of parameters by mainunit""" + for pobj in self.parameters.values(): + pobj.datatype.set_main_unit(mainunit) + def announceUpdate(self, pname, value=None, err=None, timestamp=None): """announce a changed value or readerror""" diff --git a/secop_mlz/entangle.py b/secop_mlz/entangle.py index df20938..528c9bc 100644 --- a/secop_mlz/entangle.py +++ b/secop_mlz/entangle.py @@ -376,6 +376,12 @@ class AnalogInput(PyTangoDevice, Readable): """ The AnalogInput handles all devices only delivering an analogue value. """ + __main_unit = None + + def applyMainUnit(self, mainunit): + # called from __init__ method + # replacement of '$' by main unit must be done later + self.__main_unit = mainunit def startModule(self, start_events): super().startModule(start_events) @@ -386,8 +392,11 @@ class AnalogInput(PyTangoDevice, Readable): # update if attrInfo.unit != 'No unit': self.accessibles['value'].datatype.setProperty('unit', attrInfo.unit) + self.__main_unit = attrInfo.unit except Exception as e: self.log.error(e) + if self.__main_unit: + super().applyMainUnit(self.__main_unit) def read_value(self): return self._dev.value diff --git a/test/test_modules.py b/test/test_modules.py index 007a1ed..cbe43e5 100644 --- a/test/test_modules.py +++ b/test/test_modules.py @@ -659,3 +659,39 @@ def test_problematic_value_range(): obj = Mod4('obj', logger, { 'value.min': 0, 'value.max': 10, 'target.min': 0, 'target.max': 10, 'description': ''}, srv) + + +@pytest.mark.parametrize('config, dynamicunit, finalunit, someunit', [ + ({}, 'K', 'K', 'K'), + ({'value.unit': 'K'}, 'C', 'C', 'C'), + ({'value.unit': 'K'}, '', 'K', 'K'), + ({'value.unit': 'K', 'someparam.unit': 'A'}, 'C', 'C', 'A'), +]) +def test_deferred_main_unit(config, dynamicunit, finalunit, someunit): + # this pattern is used in secop_mlz.entangle.AnalogInput + class Mod(Drivable): + ramp = Parameter('', datatype=FloatRange(unit='$/min')) + someparam = Parameter('', datatype=FloatRange(unit='$')) + __main_unit = None + + def applyMainUnit(self, mainunit): + # called from __init__ method + # replacement of '$' by main unit must be done later + self.__main_unit = mainunit + + def startModule(self, start_events): + super().startModule(start_events) + if dynamicunit: + self.accessibles['value'].datatype.setProperty('unit', dynamicunit) + self.__main_unit = dynamicunit + if self.__main_unit: + super().applyMainUnit(self.__main_unit) + + srv = ServerStub({}) + m = Mod('m', logger, {'description': '', **config}, srv) + m.startModule(None) + assert m.parameters['value'].datatype.unit == finalunit + assert m.parameters['target'].datatype.unit == finalunit + assert m.parameters['ramp'].datatype.unit == finalunit + '/min' + # when someparam.unit is configured, this differs from finalunit + assert m.parameters['someparam'].datatype.unit == someunit