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