From a5a421269176033053e7b3133c1ca97692784c01 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 27 Jun 2025 16:09:53 +0200 Subject: [PATCH 1/6] DIL5: changed interface on ITC and LS372 --- cfg/dil5_cfg.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/cfg/dil5_cfg.py b/cfg/dil5_cfg.py index 37af8e18..fab68b9e 100644 --- a/cfg/dil5_cfg.py +++ b/cfg/dil5_cfg.py @@ -1,14 +1,12 @@ -# by ID (independent of plug location) +# by id (independent of plug location, but may not neccessarly be unique) +# to verify just do: +# ls /dev/serial/by-id turbo_uri = '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A601PCGF-if00-port0' press_uri = '/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AH07445U-if00-port0' itc_uri = '/dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller_D-if00-port0' lsc_uri = '192.168.1.2:7777' -# by plug location: -#turbo_uri='/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.1:1.0-port0' -#press_uri = '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.2:1.0-port0' -#itc_uri = '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0-port0' -# over USB (does not work anymore) -#lsc_uri='serial:///dev/ttyACM1?baudrate=57600+parity=odd+bytesize=7+stopbits=1', +logo_ip = '192.168.0.3' +# by plug location would also be possible (/dev/serial/by-path) Node('dil5_logo.psi.ch', @@ -20,7 +18,7 @@ Node('dil5_logo.psi.ch', Mod('logo', 'frappy_psi.logo.IO', '', - ip_address = "192.168.0.3", + ip_address = logo_ip, tcap_client = 0x3000, tsap_server = 0x2000 ) @@ -45,7 +43,7 @@ Mod('V4', 'frappy_psi.logo.DigitalActuator', 'compressor to dump', io = 'logo', - # feedback_addr ="V1024.5", # not verified + # feedback seems not to work output_addr ="V1064.7", target_addr ="V404.1", ) From 83f40f0c3345ac565b064cf3e3868a15a0275870 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Mon, 30 Jun 2025 18:09:07 +0200 Subject: [PATCH 2/6] fs: make 'sensor broken' message active - for this use frappy_psi.PRtransmitter instead of ionopimax.CurrentInput - add disabled_checks parameter --- cfg/fs_cfg.py | 10 ++++++---- frappy_psi/furnace.py | 45 +++++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/cfg/fs_cfg.py b/cfg/fs_cfg.py index 96af89d1..1b2fd3da 100644 --- a/cfg/fs_cfg.py +++ b/cfg/fs_cfg.py @@ -7,6 +7,7 @@ Mod('T', 'frappy_psi.furnace.PI2', 'controlled Temperature on sample (2nd loop)', value = Param(unit='degC'), + meaning = ['temperature', 30], input_module = 'T_sam', output_module = 'T_reg', relais = 'relais', @@ -38,7 +39,7 @@ Mod('T_reg', # ) Mod('T_htr', - 'frappy_psi.ionopimax.CurrentInput', + 'frappy_psi.furnace.PRtransmitter', 'heater temperature', addr = 'ai4', valuerange = (0, 1372), @@ -47,7 +48,7 @@ Mod('T_htr', Mod('T_sam', - 'frappy_psi.ionopimax.CurrentInput', + 'frappy_psi.furnace.PRtransmitter', 'sample temperature', addr = 'ai3', valuerange = (0, 1372), @@ -56,7 +57,7 @@ Mod('T_sam', ) Mod('T_extra', - 'frappy_psi.ionopimax.CurrentInput', + 'frappy_psi.furnace.PRtransmitter', 'extra temperature', addr = 'ai2', valuerange = (0, 1372), @@ -113,10 +114,11 @@ Mod('interlocks', control = 'T', wall_limit = 60, vacuum_limit = 0.001, + disabled_checks = 'T_extra', ) Mod('p', - 'frappy_psi.ionopimax.LogVoltageInput', + 'frappy_psi.furnace.PKRgauge', 'pressure reading', addr = 'av1', rawrange = (1.82, 8.6), diff --git a/frappy_psi/furnace.py b/frappy_psi/furnace.py index 1473843c..f7c04d08 100644 --- a/frappy_psi/furnace.py +++ b/frappy_psi/furnace.py @@ -20,7 +20,7 @@ """interlocks for furnace""" from frappy.core import Module, Writable, Attached, Parameter, FloatRange, Readable,\ - BoolType, ERROR, IDLE, Command + BoolType, ERROR, IDLE, Command, StringType from frappy.errors import ImpossibleError from frappy.ctrlby import WrapControlledBy from frappy_psi.picontrol import PImixin @@ -53,19 +53,20 @@ class Interlocks(Writable): default = 530, readonly = False) extra_T_limit = Parameter('maximum extra temperature', FloatRange(0, unit='degC'), default = 530, readonly = False) + disabled_checks = Parameter('checks to disable', StringType(), + value = '', readonly = False) _off_reason = None # reason triggering interlock _conditions = '' # summary of reasons why locked now + _sensor_checks = () - def initModule(self): - super().initModule() - self._sensor_checks = [ - (self.wall_T, 'wall_limit'), - (self.main_T, 'main_T_limit'), - (self.extra_T, 'extra_T_limit'), - (self.htr_T, 'htr_T_limit'), - (self.vacuum, 'vacuum_limit'), - ] + SENSOR_MAP = { + 'wall_T': 'wall_limit', + 'main_T': 'main_T_limit', + 'extra_T': 'extra_T_limit', + 'htr_T': 'htr_T_limit', + 'vacuum': 'vacuum_limit', + } def write_target(self, value): if value: @@ -93,13 +94,21 @@ class Interlocks(Writable): self.log.warning('switch off relais %r %r', self.relais.value, self.relais.target) self.relais.write_target(False) + def write_disabled_checks(self, value): + disabled = set(value.split()) + self._sensor_checks = [] + for att, limitname in self.SENSOR_MAP.items(): + obj = getattr(self, att) + if obj and obj.name not in disabled: + self.log.info('info %r %r %r', att, obj.name, disabled) + self._sensor_checks.append((obj, limitname)) + def read_status(self): conditions = [] if self.flowswitch and self.flowswitch.value == 0: conditions.append('no cooling water') + for sensor, limitname in self._sensor_checks: - if sensor is None: - continue if sensor.value > getattr(self, limitname): conditions.append(f'above {sensor.name} limit') if sensor.status[0] >= ERROR: @@ -155,3 +164,15 @@ class PI2(PI): if not self.control_active: self.output_module.write_target(target) super().write_target(target) + + +class PRtransmitter(CurrentInput): + rawrange = (0.004, 0.02) + extendedrange = (0.0036, 0.021) + + +class PKRgauge(LogVoltageInput): + rawrange = (1.82, 8.6) + valuerange = (5e-9, 1000) + extendedrange = (0.5, 9.5) + value = Parameter(unit='mbar') From 926dcd09e272927511a28992c6a0ef43d770726e Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 2 Jul 2025 11:20:58 +0200 Subject: [PATCH 3/6] frappy_psi.sea: use secnode.name to determine the service this fix is only needed in case uri is not given --- frappy_psi/sea.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappy_psi/sea.py b/frappy_psi/sea.py index 1234d235..fa17547f 100644 --- a/frappy_psi/sea.py +++ b/frappy_psi/sea.py @@ -126,8 +126,7 @@ class SeaClient(ProxyClient, Module): _last_connect = 0 def __init__(self, name, log, opts, srv): - nodename = srv.node_cfg.get('name') or srv.node_cfg.get('equipment_id') - instance = nodename.rsplit('_', 1)[0] + instance = srv.secnode.name.rsplit('_', 1)[0] if 'uri' not in opts: self._instance = instance port = get_sea_port(instance) From c7496fa21f0ccc361033972a1ede78052a9985aa Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 2 Jul 2025 11:26:05 +0200 Subject: [PATCH 4/6] SEA dil*.stick: add off=0 to the list of heater ranges --- cfg/sea/dil2.stick.json | 2 +- cfg/sea/dil3.stick.json | 2 +- cfg/sea/dil4.stick.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cfg/sea/dil2.stick.json b/cfg/sea/dil2.stick.json index 5a10580a..343973f2 100644 --- a/cfg/sea/dil2.stick.json +++ b/cfg/sea/dil2.stick.json @@ -18,7 +18,7 @@ {"path": "heaterselect", "type": "enum", "enum": {"sample": 0, "mix": 1, "mix(temporarely)": 2}, "readonly": false, "cmd": "ts heaterselect"}, {"path": "control", "type": "enum", "enum": {"off": 0, "sample": 6, "mix": 5, "samplehtr": 8}, "readonly": false, "cmd": "ts control", "description": "click off to reload list"}, {"path": "heatermode", "type": "enum", "enum": {"disabled": -1, "off": 0, "on": 1}, "readonly": false, "cmd": "ts heatermode"}, -{"path": "heaterrange", "type": "enum", "enum": {"2uW": 1, "20uW": 2, "200uW": 3, "2mW": 4, "20mW": 5}, "readonly": false, "cmd": "ts heaterrange"}, +{"path": "heaterrange", "type": "enum", "enum": {"off": 0, "2uW": 1, "20uW": 2, "200uW": 3, "2mW": 4, "20mW": 5}, "readonly": false, "cmd": "ts heaterrange"}, {"path": "autoheater", "type": "bool", "readonly": false, "cmd": "ts autoheater", "description": "automatic heater range", "kids": 12}, {"path": "autoheater/wlp0", "type": "float", "readonly": false, "cmd": "ts autoheater/wlp0", "description": "weak link base temperature (used for auto heater)"}, {"path": "autoheater/wlp1", "type": "float", "readonly": false, "cmd": "ts autoheater/wlp1", "description": "weak link temperature at 1 uW (used for auto heater)"}, diff --git a/cfg/sea/dil3.stick.json b/cfg/sea/dil3.stick.json index 5a10580a..343973f2 100644 --- a/cfg/sea/dil3.stick.json +++ b/cfg/sea/dil3.stick.json @@ -18,7 +18,7 @@ {"path": "heaterselect", "type": "enum", "enum": {"sample": 0, "mix": 1, "mix(temporarely)": 2}, "readonly": false, "cmd": "ts heaterselect"}, {"path": "control", "type": "enum", "enum": {"off": 0, "sample": 6, "mix": 5, "samplehtr": 8}, "readonly": false, "cmd": "ts control", "description": "click off to reload list"}, {"path": "heatermode", "type": "enum", "enum": {"disabled": -1, "off": 0, "on": 1}, "readonly": false, "cmd": "ts heatermode"}, -{"path": "heaterrange", "type": "enum", "enum": {"2uW": 1, "20uW": 2, "200uW": 3, "2mW": 4, "20mW": 5}, "readonly": false, "cmd": "ts heaterrange"}, +{"path": "heaterrange", "type": "enum", "enum": {"off": 0, "2uW": 1, "20uW": 2, "200uW": 3, "2mW": 4, "20mW": 5}, "readonly": false, "cmd": "ts heaterrange"}, {"path": "autoheater", "type": "bool", "readonly": false, "cmd": "ts autoheater", "description": "automatic heater range", "kids": 12}, {"path": "autoheater/wlp0", "type": "float", "readonly": false, "cmd": "ts autoheater/wlp0", "description": "weak link base temperature (used for auto heater)"}, {"path": "autoheater/wlp1", "type": "float", "readonly": false, "cmd": "ts autoheater/wlp1", "description": "weak link temperature at 1 uW (used for auto heater)"}, diff --git a/cfg/sea/dil4.stick.json b/cfg/sea/dil4.stick.json index 5a10580a..343973f2 100644 --- a/cfg/sea/dil4.stick.json +++ b/cfg/sea/dil4.stick.json @@ -18,7 +18,7 @@ {"path": "heaterselect", "type": "enum", "enum": {"sample": 0, "mix": 1, "mix(temporarely)": 2}, "readonly": false, "cmd": "ts heaterselect"}, {"path": "control", "type": "enum", "enum": {"off": 0, "sample": 6, "mix": 5, "samplehtr": 8}, "readonly": false, "cmd": "ts control", "description": "click off to reload list"}, {"path": "heatermode", "type": "enum", "enum": {"disabled": -1, "off": 0, "on": 1}, "readonly": false, "cmd": "ts heatermode"}, -{"path": "heaterrange", "type": "enum", "enum": {"2uW": 1, "20uW": 2, "200uW": 3, "2mW": 4, "20mW": 5}, "readonly": false, "cmd": "ts heaterrange"}, +{"path": "heaterrange", "type": "enum", "enum": {"off": 0, "2uW": 1, "20uW": 2, "200uW": 3, "2mW": 4, "20mW": 5}, "readonly": false, "cmd": "ts heaterrange"}, {"path": "autoheater", "type": "bool", "readonly": false, "cmd": "ts autoheater", "description": "automatic heater range", "kids": 12}, {"path": "autoheater/wlp0", "type": "float", "readonly": false, "cmd": "ts autoheater/wlp0", "description": "weak link base temperature (used for auto heater)"}, {"path": "autoheater/wlp1", "type": "float", "readonly": false, "cmd": "ts autoheater/wlp1", "description": "weak link temperature at 1 uW (used for auto heater)"}, From abf5f21e16274609ec7f886a6930cac3969e9ffa Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 2 Jul 2025 11:27:55 +0200 Subject: [PATCH 5/6] SEA ah2700.addon: fix name 'bufperiod' --- cfg/sea/ah2700.addon.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/sea/ah2700.addon.json b/cfg/sea/ah2700.addon.json index 1069ea85..ee75afba 100644 --- a/cfg/sea/ah2700.addon.json +++ b/cfg/sea/ah2700.addon.json @@ -12,4 +12,4 @@ {"path": "node", "type": "text", "readonly": false, "cmd": "capslope node"}, {"path": "unit", "type": "float", "readonly": false, "cmd": "capslope unit", "description": "unit=60: mainunits/minutes, unit=1: mainunits/sec"}, {"path": "ref", "type": "float", "readonly": false, "cmd": "capslope ref"}, -{"path": "buffersize", "type": "float", "readonly": false, "cmd": "capslope buffersize"}]}} +{"path": "bufperiod", "type": "float", "readonly": false, "cmd": "capslope bufperiod"}]}} From 73c620797c1bddf104dfdb476b7456a33254a340 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Wed, 2 Jul 2025 15:41:41 +0200 Subject: [PATCH 6/6] frappy.ctrlby: improvements Change-Id: I7ea2d0398fa3b32002dbaa066e3923fef72535fa --- frappy/ctrlby.py | 97 ++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/frappy/ctrlby.py b/frappy/ctrlby.py index a115e76e..73a66fbf 100644 --- a/frappy/ctrlby.py +++ b/frappy/ctrlby.py @@ -24,29 +24,20 @@ from frappy.core import Parameter, Attached class WrapControlledBy: - """mixin for modules with controlled_by + """mixin to add controlled_by to writable modules - Two use cases: + Create a wrapper class inheriting from this mixin + to add controlled_by to a Writable module - 1) on the implementation of a hardware module it is already known that - HasControlledBy is wanted. In this case, functionality to apply the - target to the hardware has to be implemented in method 'set_target' + Usage: - class MyWritable(HasControlledBy, ...): + class Enhanced(WrapControlledBy, BaseWritable): + pass - def set_target(self, target): - "apply target to HW" - # no supercall needed! + # typically nothing else has to be implemented - # do not override write_target ! - - 2) a hardware module is already available, and we extend it with the - controlled_by stuff - - class Enhanced(HasControlledBy, BaseWritable): - set_target = BaseWritable.write_target - - # nothing else is needed. + from a module with output (inheriting from HasOutput), the + method update_target must be called for internal updates """ controlled_by = Parameter('source of target value', EnumType(members={'self': 0}), default=0) @@ -87,12 +78,12 @@ class WrapControlledBy: self.write_controlled_by('self') def set_off(self): - """to be overriden if the off state should be different than the default + """to be overridden if the off state should be different from the default on a FloatRange() the default value is 0 """ self.self_controlled() - self.set_target_cby(self.parameters['target'].datatype.default) + self.internal_set_target(self.parameters['target'].datatype.default) def update_target(self, module, value): """update internal target value @@ -108,28 +99,50 @@ class WrapControlledBy: if deactivate_control: deactivate_control(module) self.controlled_by = module - target = self.set_target_cby(value) + target = self.internal_set_target(value) self.target = value if target is None else target def write_target(self, target): self.self_controlled() - return self.set_target_cby(target) + return self.internal_set_target(target) - def set_target_cby(self, target): + def internal_set_target(self, target): + # we need this additional indirection: + # super().write_target must refer to an inherited base class + # which is after WrapControlledBy in the method resolution order return super().write_target(target) class HasControlledBy(WrapControlledBy): + """mixin for controlled_by functionality + + Create a wrapper class inheriting from this mixin + to add controlled_by to a Writable module + + Usage: + + class Enhanced(HasControlledBy, BaseWritable): + def set_target(self, value): + # implement here hardware access for setting target + + # do not override write_target! + + from a module with output (inheriting from HasOutput), the + method update_target must be called for internal updates + """ + def set_target(self, value): """to be overridden for setting target of HW""" raise NotImplementedError - def set_target_cby(self, value): - """to be overridden in case this mixin is not added on top""" + def internal_set_target(self, value): + # we need this additional indirection: + # self.write_target must refer to a base class which + # is before HasControlledBy in the method resolution order return self.set_target(value) -class WrapOutputModule: +class HasOutputModule: """mixin for modules having an output module this module will call the update_target method of an output module @@ -145,7 +158,14 @@ class WrapOutputModule: self.output_module.register_input(self.name, self.deactivate_control) def write_control_active(self, value): - """override with supercall if needed""" + """override with supercall if needed + + control_active is readonly by default, as specified in the SECoP standard. + This method is meant to be called internally. + + However, it is possible to override control_active with readonly=False + and this is quite useful IMHO in some situations + """ out = self.output_module if out: if value: @@ -157,13 +177,16 @@ class WrapOutputModule: out.set_off() # this sets out.controlled_by to 0 (=self) def set_control_active(self, active): - """to be overridden for switching hw control""" + """to be overridden for switching hw control + + TODO: remove this legacy method (replaced by write_control_active) + """ self.control_active = active def activate_control(self): """method to switch control_active on - to be called from the write_target method, with the target as argument + TODO: remove this legacy method (replaced by write_control_active) """ self.write_control_active(True) @@ -178,16 +201,8 @@ class WrapOutputModule: def write_target(self, target): self.write_control_active(True) - return self.set_target_out(target) - - def set_target_out(self, target): - return super().write_target(target) - - -class HasOutputModule(WrapOutputModule): - def set_target(self, target): - """to be overridden except for WrapOutputModule""" - raise NotImplementedError - - def set_target_out(self, target): return self.set_target(target) + + def set_target(self, target): + """to be overridden""" + raise NotImplementedError