From 9d9b5b2694913982fe4cc404ca08d4bdbf601563 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 23 Aug 2023 13:05:18 +0200 Subject: [PATCH 01/13] frappy_psi.phytron: further improvements unfortunaely, sometimes communication errors happen. workaround: try several times reading the status Change-Id: I2788c6c9b4145246cdd51c31b246abffee60f93b Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32032 Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- frappy_psi/phytron.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/frappy_psi/phytron.py b/frappy_psi/phytron.py index fdd3039..1705a50 100644 --- a/frappy_psi/phytron.py +++ b/frappy_psi/phytron.py @@ -192,9 +192,17 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): self.hw_stop() def read_status(self): - sysstatus = self.communicate(f'{self.address:x}SE') - sysstatus = sysstatus[1:4] if self.axis == 'X' else sysstatus[5:8] - status = self.STATUS_MAP.get(sysstatus[1:]) or (ERROR, f'unknown error {sysstatus[1:]}') + for _ in range(3): + sysstatus = self.communicate(f'{self.address:x}SE') + try: + sysstatus = sysstatus[1:4] if self.axis == 'X' else sysstatus[5:8] + status = self.STATUS_MAP[sysstatus[1:]] + except Exception: # can not interprete the reply, probably communication error + self.log.warning('bad status reply %r', sysstatus) + continue + break + else: + status = (ERROR, f'unknown status after 3 tries {sysstatus!r}') self._running = sysstatus[0] != '1' if status[0] == ERROR: self._blocking_error = status[1] @@ -213,7 +221,7 @@ class Motor(HasOffset, HasStates, PersistentMixin, HasIO, Drivable): enc = self.read_encoder() else: enc = self.value - if not self._running: # at target + if not self._running: # at target (self._running is updated in self.read_status()) return False diff = abs(self.value - self._intermediate_target) if diff > self._prev_diff and diff > self.encoder_tolerance: From b9f046a66558380619a0790bd6c5d711fab0b014 Mon Sep 17 00:00:00 2001 From: sans Date: Wed, 6 Sep 2023 08:38:06 +0200 Subject: [PATCH 02/13] hvolt_short stick: make hcp writable --- cfg/sea/hvolt_short.stick.json | 2 +- cfg/stick/hvolt_short_cfg.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/sea/hvolt_short.stick.json b/cfg/sea/hvolt_short.stick.json index 48c403e..9a36236 100644 --- a/cfg/sea/hvolt_short.stick.json +++ b/cfg/sea/hvolt_short.stick.json @@ -1,5 +1,5 @@ {"hcp": {"base": "/hcp", "params": [ -{"path": "", "type": "float", "kids": 10}, +{"path": "", "type": "float", "readonly": false, "cmd": "hcp set", "kids": 10}, {"path": "send", "type": "text", "readonly": false, "cmd": "hcp send", "visibility": 3}, {"path": "status", "type": "text", "visibility": 3}, {"path": "set", "type": "float", "readonly": false, "cmd": "hcp set"}, diff --git a/cfg/stick/hvolt_short_cfg.py b/cfg/stick/hvolt_short_cfg.py index 8256e83..de1bc93 100644 --- a/cfg/stick/hvolt_short_cfg.py +++ b/cfg/stick/hvolt_short_cfg.py @@ -18,7 +18,7 @@ Mod('ts', ) Mod('hcp', - 'frappy_psi.sea.SeaReadable', '', + 'frappy_psi.sea.SeaWritable', '', io='sea_stick', sea_object='hcp', ) From 833a68db516d8d251334b3f7eb3ec95a1ad57ca1 Mon Sep 17 00:00:00 2001 From: sans Date: Wed, 6 Sep 2023 08:38:49 +0200 Subject: [PATCH 03/13] ma10: improve sea cfg --- cfg/main/ma10_cfg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cfg/main/ma10_cfg.py b/cfg/main/ma10_cfg.py index c77768a..844ab52 100644 --- a/cfg/main/ma10_cfg.py +++ b/cfg/main/ma10_cfg.py @@ -43,6 +43,7 @@ Mod('mf', 'frappy_psi.sea.SeaDrivable', '', io='sea_main', sea_object='mf', + rel_paths=['.', 'gen', 'ips'], ) Mod('lev', From 2e143963dfca84605c0c14557e6f375965cfa0b2 Mon Sep 17 00:00:00 2001 From: focus Date: Fri, 8 Sep 2023 10:45:18 +0200 Subject: [PATCH 04/13] stickmotor addon: add backlash -1 --- cfg/addons/stickmotor_cfg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cfg/addons/stickmotor_cfg.py b/cfg/addons/stickmotor_cfg.py index cbf7d0a..9586a8d 100644 --- a/cfg/addons/stickmotor_cfg.py +++ b/cfg/addons/stickmotor_cfg.py @@ -13,4 +13,5 @@ Mod('stickrot', 'stick rotation, typically not used as omega', io='stick_io', encoder_mode='CHECK', + backlash=-1, ) From 6c49abea745406d80197b0ebb2c5d94cad72ae39 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 16 Aug 2023 17:13:59 +0200 Subject: [PATCH 05/13] fix frappy/playground.py after change 31470 assumptions about dispatcher in playground.py are no longer valid. - let Dispatcher class in playground inherit from real dispatcher + improve log messages Change-Id: I2a9a9d532dabadc590543660c445c021dd2f2891 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31967 Tested-by: Jenkins Automated Tests Reviewed-by: Alexander Zaft Reviewed-by: Markus Zolliker --- frappy/playground.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/frappy/playground.py b/frappy/playground.py index 07061f6..c8b11f2 100644 --- a/frappy/playground.py +++ b/frappy/playground.py @@ -30,10 +30,10 @@ Remarks: import sys from logging import DEBUG, INFO, addLevelName import mlzlog -from frappy.errors import NoSuchModuleError from frappy.server import Server from frappy.config import load_config, Mod as ConfigMod from frappy.lib import generalConfig +from frappy.protocol import dispatcher USAGE = """create config on the fly: @@ -77,28 +77,25 @@ class MainLogger: self.log.handlers[0].setLevel(LOG_LEVELS['comlog']) -class Dispatcher: - def __init__(self, name, log, opts, srv): - self.log = log - self._modules = {} - self.equipment_id = opts.pop('equipment_id', name) +class Dispatcher(dispatcher.Dispatcher): + def __init__(self, name, log, options, srv): + super().__init__(name, log, options, srv) + self.log = srv.log # overwrite child logger def announce_update(self, modulename, pname, pobj): if pobj.readerror: value = repr(pobj.readerror) else: value = pobj.value - self.log.info('%s:%s %r', modulename, pname, value) + logobj = self._modules.get(modulename, self) + # self.log.info('%s:%s %r', modulename, pname, value) + logobj.log.info('%s %r', pname, value) def register_module(self, moduleobj, modulename, export=True): + self.log.info('registering %s', modulename) setattr(main, modulename, moduleobj) self._modules[modulename] = moduleobj - def get_module(self, modulename): - if modulename in self._modules: - return self._modules[modulename] - raise NoSuchModuleError(f'Module {modulename!r} does not exist on this SEC-Node!') - logger = MainLogger() From cb2c10655c043dc4714459b51d211e9b2ad93ae4 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 13 Sep 2023 17:22:58 +0200 Subject: [PATCH 06/13] improve shutdown time on shutdown, time.sleep(10) is blocking the reconnect thread. change the _shutdown attribute from bool to an Event, and use Event.wait instead of time.sleep Change-Id: Icea6a14ad73df0b3d26ef45806f4c05e6bf18492 --- frappy/client/__init__.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frappy/client/__init__.py b/frappy/client/__init__.py index 70f512b..7eb4160 100644 --- a/frappy/client/__init__.py +++ b/frappy/client/__init__.py @@ -261,7 +261,7 @@ class SecopClient(ProxyClient): """a general SECoP client""" reconnect_timeout = 10 _running = False - _shutdown = False + _shutdown = None _rxthread = None _txthread = None _connthread = None @@ -283,6 +283,7 @@ class SecopClient(ProxyClient): self.uri = uri self.nodename = uri self._lock = RLock() + self._shutdown = Event() def __del__(self): try: @@ -303,7 +304,7 @@ class SecopClient(ProxyClient): else: self._set_state(False, 'connecting') deadline = time.time() + try_period - while not self._shutdown: + while not self._shutdown.is_set(): try: self.io = AsynConn(self.uri) # timeout 1 sec self.io.writeline(IDENTREQUEST.encode('utf-8')) @@ -339,8 +340,8 @@ class SecopClient(ProxyClient): # stay online for now, if activated self._set_state(self.online and self.activate) raise - time.sleep(1) - if not self._shutdown: + self._shutdown.wait(1) + if not self._shutdown.is_set(): self.log.info('%s ready', self.nodename) def __txthread(self): @@ -436,7 +437,7 @@ class SecopClient(ProxyClient): self.log.error('rxthread ended with %r', e) self._rxthread = None self.disconnect(False) - if self._shutdown: + if self._shutdown.is_set(): return if self.activate: self.log.info('try to reconnect to %s', self.uri) @@ -454,7 +455,7 @@ class SecopClient(ProxyClient): self._connthread = mkthread(self._reconnect, connected_callback) def _reconnect(self, connected_callback=None): - while not self._shutdown: + while not self._shutdown.is_set(): try: self.connect() if connected_callback: @@ -474,15 +475,15 @@ class SecopClient(ProxyClient): self.log.info('continue trying to reconnect') # self.log.warning(formatExtendedTraceback()) self._set_state(False) - time.sleep(self.reconnect_timeout) + self._shutdown.wait(self.reconnect_timeout) else: - time.sleep(1) + self._shutdown.wait(1) self._connthread = None def disconnect(self, shutdown=True): self._running = False if shutdown: - self._shutdown = True + self._shutdown.set() self._set_state(False, 'shutdown') if self._connthread: if self._connthread == current_thread(): From 0bc4a63aa7951beef0252aac2dad7749685bc31b Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Thu, 14 Sep 2023 09:05:00 +0200 Subject: [PATCH 07/13] add parmod.Par the reasonly class frappy_psi.parmod.Par represents a parameter or a component of a tuple parameter Change-Id: I47208c9d7a6fc377cd56b82cc6a9e8cdb433fe8e --- frappy_psi/parmod.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/frappy_psi/parmod.py b/frappy_psi/parmod.py index 58c3b3f..990a472 100644 --- a/frappy_psi/parmod.py +++ b/frappy_psi/parmod.py @@ -23,12 +23,36 @@ """modules to access parameters""" from frappy.core import Drivable, IDLE, Attached, StringType, Property, \ - Parameter, FloatRange + Parameter, FloatRange, Readable from frappy.errors import ConfigError from frappy_psi.convergence import HasConvergence from frappy_psi.mixins import HasRamp +class Par(Readable): + value = Parameter(datatype=FloatRange(unit='$')) + read = Attached(description='. for read') + unit = Property('main unit', StringType()) + + def setProperty(self, key, value): + if key == 'read': + value, param = value.split('.') + setattr(self, f'{key}_param', param) + super().setProperty(key, value) + + def checkProperties(self): + self.applyMainUnit(self.unit) + if self.read == self.name : + raise ConfigError('illegal recursive read/write module') + super().checkProperties() + + def read_value(self): + return getattr(self.read, f'{self.read_param}') + + def read_status(self): + return IDLE, '' + + class Driv(Drivable): value = Parameter(datatype=FloatRange(unit='$')) target = Parameter(datatype=FloatRange(unit='$')) From 5e1c22ba28b099592af5b6763f9afc5ce2caa4a3 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Tue, 12 Sep 2023 08:18:17 +0200 Subject: [PATCH 08/13] further fixes after change 31470 - get_module is to be called when io is autocreated - register_module is missing in playground Change-Id: I28884575b71320667107c494473b0fc5d4363a50 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32123 Tested-by: Jenkins Automated Tests Reviewed-by: Alexander Zaft Reviewed-by: Markus Zolliker --- frappy/io.py | 1 + frappy/playground.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frappy/io.py b/frappy/io.py index 53de4bd..5a59d7c 100644 --- a/frappy/io.py +++ b/frappy/io.py @@ -63,6 +63,7 @@ class HasIO(Module): io = self.ioClass(ioname, srv.log.getChild(ioname), opts, srv) # pylint: disable=not-callable io.callingModule = [] srv.modules[ioname] = io + srv.dispatcher.register_module(io, ioname) self.ioDict[self.uri] = ioname self.io = ioname diff --git a/frappy/playground.py b/frappy/playground.py index c8b11f2..05647e7 100644 --- a/frappy/playground.py +++ b/frappy/playground.py @@ -93,8 +93,9 @@ class Dispatcher(dispatcher.Dispatcher): def register_module(self, moduleobj, modulename, export=True): self.log.info('registering %s', modulename) + super().register_module(moduleobj, modulename, export) setattr(main, modulename, moduleobj) - self._modules[modulename] = moduleobj + self.get_module(modulename) logger = MainLogger() From af34fef1e1f3b98eb0ef8fc2e4007448fde84a6a Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 13 Sep 2023 14:32:57 +0200 Subject: [PATCH 09/13] fix missing .poll attribute in simulation using super() in SimBase.__new__ fixes the problem Change-Id: I18d0ba6ac476c2edb0d973090bcb09508a983d6a Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32136 Reviewed-by: Alexander Zaft Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- frappy/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappy/simulation.py b/frappy/simulation.py index 6c7be02..6e3de00 100644 --- a/frappy/simulation.py +++ b/frappy/simulation.py @@ -54,7 +54,7 @@ class SimBase: attrs['write_' + k] = writer - return object.__new__(type(f'SimBase_{devname}', (cls,), attrs)) + return super().__new__(type(f'SimBase_{devname}', (cls,), attrs)) def initModule(self): super().initModule() From bf4b3e56836d92e4d0962fd9e29ece3a9fadea5f Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 13 Sep 2023 18:16:51 +0200 Subject: [PATCH 10/13] psi: improve sea interface - get return value from teh frappy-config script in order to detect failures - call config_check not more than once within 1 sec Change-Id: Ibe42e846521206463f2761d452aea7e558a36854 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32139 Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- frappy_psi/sea.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/frappy_psi/sea.py b/frappy_psi/sea.py index 7e17549..9906505 100644 --- a/frappy_psi/sea.py +++ b/frappy_psi/sea.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # ***************************************************************************** # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software @@ -185,8 +184,9 @@ class SeaClient(ProxyClient, Module): break else: raise CommunicationFailedError('reply %r should be "Login OK"' % reply) - self.request('frappy_config %s %s' % (self.service, self.config)) - + result = self.request('frappy_config %s %s' % (self.service, self.config)) + if result not in {'0', '1'}: + raise CommunicationFailedError(f'reply from frappy_config: {result}') # frappy_async_client switches to the json protocol (better for updates) self.asynio.writeline(b'frappy_async_client') self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) @@ -248,7 +248,16 @@ class SeaClient(ProxyClient, Module): raise TimeoutError('no response within 10s') def _rxthread(self, started_callback): + recheck = None while not self.shutdown: + if recheck and time.time() > recheck: + # try to collect device changes within 1 sec + recheck = None + result = self.request('check_config %s %s' % (self.service, self.config)) + if result == '1': + self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) + else: + self.DISPATCHER.shutdown() try: reply = self.asynio.readline() if reply is None: @@ -307,11 +316,7 @@ class SeaClient(ProxyClient, Module): if mplist is None: if path.startswith('/device'): if path == '/device/changetime': - result = self.request('check_config %s %s' % (self.service, self.config)) - if result == '1': - self.asynio.writeline(('get_all_param ' + ' '.join(self.objects)).encode()) - else: - self.DISPATCHER.shutdown() + recheck = time.time() + 1 elif path.startswith('/device/frappy_%s' % self.service) and value == '': self.DISPATCHER.shutdown() else: From 4c5109e5a3aebaaaad38dd37727af0fff9696b3a Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 18 Aug 2023 10:07:20 +0200 Subject: [PATCH 11/13] fix frappy_demo.lakeshore reading back the target does not work properly, because a) the readback value might be delayed b) there is no command to read back the target, SETP?1 is returning the working setpoint, which might be distinct in case of a ramp Change-Id: I0da2dbfc1a8ddbecbae6d0456ff64e008bc56336 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31983 Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- doc/source/tutorial_t_control.rst | 22 ++++++++++++++-------- frappy_demo/lakeshore.py | 11 ++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/doc/source/tutorial_t_control.rst b/doc/source/tutorial_t_control.rst index 420d294..786e7a4 100644 --- a/doc/source/tutorial_t_control.rst +++ b/doc/source/tutorial_t_control.rst @@ -200,7 +200,10 @@ implemented the readable stuff. We need to define some properties of the ``targe parameter and add a property ``loop`` indicating, which control loop and heater output we use. -In addition, we have to implement the methods ``write_target`` and ``read_target``: +In addition, we have to implement the method ``write_target``. Remark: we do not +implement ``read_target`` here, because the lakeshore does not offer to read back the +real target. The SETP command is returning the working setpoint, which may be distinct +from target during a ramp. .. code:: python @@ -216,11 +219,9 @@ In addition, we have to implement the methods ``write_target`` and ``read_target def write_target(self, target): # we always use a request / reply scheme - reply = self.communicate(f'SETP {self.loop},{target};SETP?{self.loop}') - return float(reply) + self.communicate(f'SETP {self.loop},{target};*OPC?') + return target - def read_target(self): - return float(self.communicate(f'SETP?{self.loop}')) In order to test this, we will need to change the entry module ``T`` in the configuration file: @@ -272,13 +273,12 @@ There are two things still missing: def write_target(self, target): # reactivate heater in case it was switched off self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}') - reply = self.communicate(f'SETP {self.loop},{target};SETP? {self.loop}') + self.communicate(f'SETP {self.loop},{target};*OPC?') self._driving = True # Setting the status attribute triggers an update message for the SECoP status # parameter. This has to be done before returning from this method! self.status = BUSY, 'target changed' - return float(reply) - + return target ... def read_status(self): @@ -403,4 +403,10 @@ Appendix 2: Extract from the LakeShore Manual Command (340) RANGE? *term* Command (336/350) RANGE? *term* Reply *term* + **Operation Complete Query** + ---------------------------------------------- + Command *OPC? + Reply 1 + Description in Frappy, we append this command to request in order + to generate a reply ====================== ======================= diff --git a/frappy_demo/lakeshore.py b/frappy_demo/lakeshore.py index c0442bc..a363712 100644 --- a/frappy_demo/lakeshore.py +++ b/frappy_demo/lakeshore.py @@ -69,24 +69,21 @@ class TemperatureSensor(HasIO, Readable): class TemperatureLoop(TemperatureSensor, Drivable): # lakeshore loop number to be used for this module loop = Property('lakeshore loop', IntRange(1, 2), default=1) - target = Parameter(datatype=FloatRange(min=0, max=1500, unit='K')) + target = Parameter(datatype=FloatRange(unit='K', min=0, max=1500)) heater_range = Property('heater power range', IntRange(0, 5)) # max. 3 on LakeShore 336 - tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly = False) + tolerance = Parameter('convergence criterion', FloatRange(0), default=0.1, readonly=False) _driving = False def write_target(self, target): # reactivate heater in case it was switched off # the command has to be changed in case of model 340 to f'RANGE {self.heater_range};RANGE?' self.communicate(f'RANGE {self.loop},{self.heater_range};RANGE?{self.loop}') - reply = self.communicate(f'SETP {self.loop},{target};SETP? {self.loop}') + self.communicate(f'SETP {self.loop},{target};*OPC?') self._driving = True # Setting the status attribute triggers an update message for the SECoP status # parameter. This has to be done before returning from this method! self.status = BUSY, 'target changed' - return float(reply) - - def read_target(self): - return float(self.communicate(f'SETP?{self.loop}')) + return target def read_status(self): code = int(self.communicate(f'RDGST?{self.channel}')) From 8019b359c43f4b065d9486438c1f29e06042f420 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 18 Aug 2023 09:29:32 +0200 Subject: [PATCH 12/13] change FloatRange arguments minval/maxval to min/max in the previous version FloatRange(max=100) was neither working properly nor complaining, because the maxval=None default was overriding the value for max. possible fixes: - raise an error when min/max used as argument (confusing for the programmer, as it is a property) - allow both versions minval/maxval and min/max (more code) - use min/max and a pylint directive here (the only thing to take care is not to use the min/max builtin in __init__) this change uses the last option for the fix Change-Id: Iff0e0c4d0d7b165003bdeffa67a93a1cd7f29eea Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/31982 Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- frappy/datatypes.py | 43 +++++++++++++++++++++--------------------- test/test_datatypes.py | 6 +++--- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/frappy/datatypes.py b/frappy/datatypes.py index c93de9f..5abf176 100644 --- a/frappy/datatypes.py +++ b/frappy/datatypes.py @@ -191,8 +191,8 @@ class HasUnit: class FloatRange(HasUnit, DataType): """(restricted) float type - :param minval: (property **min**) - :param maxval: (property **max**) + :param min: (property **min**) + :param max: (property **max**) :param kwds: any of the properties below """ min = Property('low limit', Stub('FloatRange'), extname='min', default=-sys.float_info.max) @@ -203,11 +203,11 @@ class FloatRange(HasUnit, DataType): relative_resolution = Property('relative resolution', Stub('FloatRange', 0), extname='relative_resolution', default=1.2e-7) - def __init__(self, minval=None, maxval=None, **kwds): + def __init__(self, min=None, max=None, **kwds): # pylint: disable=redefined-builtin super().__init__() - kwds['min'] = minval if minval is not None else -sys.float_info.max - kwds['max'] = maxval if maxval is not None else sys.float_info.max - self.set_properties(**kwds) + self.set_properties(min=min if min is not None else -sys.float_info.max, + max=max if max is not None else sys.float_info.max, + **kwds) def checkProperties(self): self.default = 0 if self.min <= 0 <= self.max else self.min @@ -247,9 +247,9 @@ class FloatRange(HasUnit, DataType): def __repr__(self): hints = self.get_info() if 'min' in hints: - hints['minval'] = hints.pop('min') + hints['min'] = hints.pop('min') if 'max' in hints: - hints['maxval'] = hints.pop('max') + hints['max'] = hints.pop('max') return 'FloatRange(%s)' % (', '.join('%s=%r' % (k, v) for k, v in hints.items())) def export_value(self, value): @@ -281,18 +281,18 @@ class FloatRange(HasUnit, DataType): class IntRange(DataType): """restricted int type - :param minval: (property **min**) - :param maxval: (property **max**) + :param min: (property **min**) + :param max: (property **max**) """ min = Property('minimum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='min', mandatory=True) max = Property('maximum value', Stub('IntRange', -UNLIMITED, UNLIMITED), extname='max', mandatory=True) # a unit on an int is now allowed in SECoP, but do we need them in Frappy? # unit = Property('physical unit', StringType(), extname='unit', default='') - def __init__(self, minval=None, maxval=None): + def __init__(self, min=None, max=None): # pylint: disable=redefined-builtin super().__init__() - self.set_properties(min=DEFAULT_MIN_INT if minval is None else minval, - max=DEFAULT_MAX_INT if maxval is None else maxval) + self.set_properties(min=DEFAULT_MIN_INT if min is None else min, + max=DEFAULT_MAX_INT if max is None else max) def checkProperties(self): self.default = 0 if self.min <= 0 <= self.max else self.min @@ -364,8 +364,8 @@ class IntRange(DataType): class ScaledInteger(HasUnit, DataType): """scaled integer (= fixed resolution float) type - :param minval: (property **min**) - :param maxval: (property **max**) + :param min: (property **min**) + :param max: (property **max**) :param kwds: any of the properties below note: limits are for the scaled float value @@ -380,7 +380,8 @@ class ScaledInteger(HasUnit, DataType): relative_resolution = Property('relative resolution', FloatRange(0), extname='relative_resolution', default=1.2e-7) - def __init__(self, scale, minval=None, maxval=None, absolute_resolution=None, **kwds): + # pylint: disable=redefined-builtin + def __init__(self, scale, min=None, max=None, absolute_resolution=None, **kwds): super().__init__() try: scale = float(scale) @@ -390,8 +391,8 @@ class ScaledInteger(HasUnit, DataType): absolute_resolution = scale self.set_properties( scale=scale, - min=DEFAULT_MIN_INT * scale if minval is None else float(minval), - max=DEFAULT_MAX_INT * scale if maxval is None else float(maxval), + min=DEFAULT_MIN_INT * scale if min is None else float(min), + max=DEFAULT_MAX_INT * scale if max is None else float(max), absolute_resolution=absolute_resolution, **kwds) @@ -1327,11 +1328,11 @@ DATATYPES = { 'bool': lambda **kwds: BoolType(), 'int': lambda min, max, **kwds: - IntRange(minval=min, maxval=max), + IntRange(min=min, max=max), 'scaled': lambda scale, min, max, **kwds: - ScaledInteger(scale=scale, minval=min*scale, maxval=max*scale, **floatargs(kwds)), + ScaledInteger(scale=scale, min=min*scale, max=max*scale, **floatargs(kwds)), 'double': lambda min=None, max=None, **kwds: - FloatRange(minval=min, maxval=max, **floatargs(kwds)), + FloatRange(min=min, max=max, **floatargs(kwds)), 'blob': lambda maxbytes, minbytes=0, **kwds: BLOBType(minbytes=minbytes, maxbytes=maxbytes), 'string': lambda minchars=0, maxchars=None, isUTF8=False, **kwds: diff --git a/test/test_datatypes.py b/test/test_datatypes.py index a4b2c1b..5c4c88b 100644 --- a/test/test_datatypes.py +++ b/test/test_datatypes.py @@ -160,9 +160,9 @@ def test_ScaledInteger(): with pytest.raises(ProgrammingError): ScaledInteger('xc', 'Yx') with pytest.raises(ProgrammingError): - ScaledInteger(scale=0, minval=1, maxval=2) + ScaledInteger(scale=0, min=1, max=2) with pytest.raises(ProgrammingError): - ScaledInteger(scale=-10, minval=1, maxval=2) + ScaledInteger(scale=-10, min=1, max=2) # check that unit can be changed dt.setProperty('unit', 'A') assert dt.export_datatype() == {'type': 'scaled', 'scale':0.01, 'min':-300, 'max':300, @@ -563,7 +563,7 @@ def test_get_datatype(): assert isinstance(get_datatype( {'type': 'scaled', 'scale':0.03, 'min':-99, 'max':111}), ScaledInteger) - dt = ScaledInteger(scale=0.03, minval=0, maxval=9.9) + dt = ScaledInteger(scale=0.03, min=0, max=9.9) assert dt.export_datatype() == {'type': 'scaled', 'max':330, 'min':0, 'scale':0.03} assert get_datatype(dt.export_datatype()).export_datatype() == dt.export_datatype() From 45dd87060b09932a3498566240eb5a635b40b21e Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 13 Sep 2023 17:22:58 +0200 Subject: [PATCH 13/13] improve client shutdown time in SecopClient.disconnect joinng the reconnect thread may take up to 10 s, because of the time.sleep(10) call in the reconnect thread. change the _shutdown attribute from bool to an Event, and use Event.wait instead of time.sleep Change-Id: Icea6a14ad73df0b3d26ef45806f4c05e6bf18492 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/32137 Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- frappy/client/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappy/client/__init__.py b/frappy/client/__init__.py index 7eb4160..0d4ddfc 100644 --- a/frappy/client/__init__.py +++ b/frappy/client/__init__.py @@ -261,7 +261,6 @@ class SecopClient(ProxyClient): """a general SECoP client""" reconnect_timeout = 10 _running = False - _shutdown = None _rxthread = None _txthread = None _connthread = None