From 7f07f4a3dd4350e265d1786da06769774e53b979 Mon Sep 17 00:00:00 2001 From: gac-x01da Date: Tue, 11 Mar 2025 09:41:40 +0100 Subject: [PATCH] Added devices and updated config --- debye_bec/device_configs/x01da_database.yaml | 20 +- .../device_configs/x01da_test_config.yaml | 170 +++++++++++++ debye_bec/devices/amplifiers.py | 186 ++++++++++++++ debye_bec/devices/es0filter.py | 89 +++++++ debye_bec/devices/gas_mix_setup.py | 236 ++++++++++++++++++ debye_bec/devices/hv_supplies.py | 165 ++++++++++++ debye_bec/devices/pilatus_curtain.py | 81 ++++++ 7 files changed, 937 insertions(+), 10 deletions(-) create mode 100644 debye_bec/devices/amplifiers.py create mode 100644 debye_bec/devices/es0filter.py create mode 100644 debye_bec/devices/gas_mix_setup.py create mode 100644 debye_bec/devices/hv_supplies.py create mode 100644 debye_bec/devices/pilatus_curtain.py diff --git a/debye_bec/device_configs/x01da_database.yaml b/debye_bec/device_configs/x01da_database.yaml index 99413c2..e50414a 100644 --- a/debye_bec/device_configs/x01da_database.yaml +++ b/debye_bec/device_configs/x01da_database.yaml @@ -360,15 +360,15 @@ fm_ztcp: onFailure: retry enabled: true softwareTrigger: false -fm_xstripe: - readoutPriority: baseline - description: Focusing Morror X Stripe - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: X01DA-OP-FM:XSTRIPE - onFailure: retry - enabled: true - softwareTrigger: false +# fm_xstripe: +# readoutPriority: baseline +# description: Focusing Morror X Stripe +# deviceClass: ophyd.EpicsMotor +# deviceConfig: +# prefix: X01DA-OP-FM:XSTRIPE +# onFailure: retry +# enabled: true +# softwareTrigger: false ## Optics Slits 1 -- Physical positioners @@ -782,7 +782,7 @@ es1arc_roty: description: End Station segmented arc Y-rotation deviceClass: ophyd.EpicsMotor deviceConfig: - prefix: X01DA-ES1-MAN2:ROTY + prefix: X01DA-ES1-ARC:ROTY onFailure: retry enabled: true softwareTrigger: false diff --git a/debye_bec/device_configs/x01da_test_config.yaml b/debye_bec/device_configs/x01da_test_config.yaml index ce7048c..ebb0a9d 100644 --- a/debye_bec/device_configs/x01da_test_config.yaml +++ b/debye_bec/device_configs/x01da_test_config.yaml @@ -17,6 +17,8 @@ dummy_pv: onFailure: retry enabled: true softwareTrigger: false + +## NIDAQ nidaq: readoutPriority: async description: NIDAQ backend for data reading for debye scans @@ -27,3 +29,171 @@ nidaq: enabled: true softwareTrigger: false +# ES0 Filter +# es0filter: +# readoutPriority: async +# description: ES0 filter station +# deviceClass: debye_bec.devices.es0filter.ES0Filter +# deviceConfig: +# prefix: "X01DA-ES0-FI:" +# onFailure: retry +# enabled: true +# softwareTrigger: false + +# Current amplifiers +# amplifiers: +# readoutPriority: async +# description: ES current amplifiers +# deviceClass: debye_bec.devices.amplifiers.Amplifiers +# deviceConfig: +# prefix: "X01DA-ES:AMP5004" +# onFailure: retry +# enabled: true +# softwareTrigger: false + +# HV power supplies +# hv_supplies: +# readoutPriority: async +# description: HV power supplies +# deviceClass: debye_bec.devices.hv_supplies.HVSupplies +# deviceConfig: +# prefix: "X01DA-" +# onFailure: retry +# enabled: true +# softwareTrigger: false + +# Gas Mix Setup +# gas_mix_setup: +# readoutPriority: async +# description: Gas Mix Setup for Ionization Chambers +# deviceClass: debye_bec.devices.gas_mix_setup.GasMixSetup +# deviceConfig: +# prefix: "X01DA-ES-GMES:" +# onFailure: retry +# enabled: true +# softwareTrigger: false + +# Pilatus Curtain +# pilatus_curtain: +# readoutPriority: async +# description: Pilatus Curtain +# deviceClass: debye_bec.devices.pilatus_curtain.PilatusCurtain +# deviceConfig: +# prefix: "X01DA-ES2-DET3:TRY-" +# onFailure: retry +# enabled: true +# softwareTrigger: false + + +################################ +## ES Hutch Sensors and Light ## +################################ + +es_temperature1: + readoutPriority: monitored + description: ES temperature sensor 1 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-PC-I2C:_CH1:TEMP" + onFailure: retry + enabled: true + softwareTrigger: false + +es_humidity1: + readoutPriority: monitored + description: ES humidity sensor 1 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-PC-I2C:_CH1:HUMIREL" + onFailure: retry + enabled: true + softwareTrigger: false + +es_pressure1: + readoutPriority: monitored + description: ES ambient pressure sensor 1 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-PC-I2C:_CH1:PRES" + onFailure: retry + enabled: true + softwareTrigger: false + +es_temperature2: + readoutPriority: monitored + description: ES temperature sensor 2 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-PC-I2C:_CH2:TEMP" + onFailure: retry + enabled: true + softwareTrigger: false + +es_humidity2: + readoutPriority: monitored + description: ES humidity sensor 2 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-PC-I2C:_CH2:HUMIREL" + onFailure: retry + enabled: true + softwareTrigger: false + +es_pressure2: + readoutPriority: monitored + description: ES ambient pressure sensor 2 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-PC-I2C:_CH2:PRES" + onFailure: retry + enabled: true + softwareTrigger: false + +es_light_toggle: + readoutPriority: monitored + description: ES light toggle + deviceClass: ophyd.EpicsSignal + deviceConfig: + read_pv: "X01DA-EH-LIGHT:TOGGLE" + onFailure: retry + enabled: true + softwareTrigger: false + +################# +## SDD sensors ## +################# + +sdd1_temperature: + readoutPriority: monitored + description: SDD1 temperature sensor + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-ES1-DET1:Temperature" + onFailure: retry + enabled: true + softwareTrigger: false + +sdd1_humidity: + readoutPriority: monitored + description: SDD1 humidity sensor + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-ES1-DET1:Humidity" + onFailure: retry + enabled: true + softwareTrigger: false + +##################### +## Alignment Laser ## +##################### + +es1_alignment_laser: + readoutPriority: monitored + description: ES1 alignment laser + deviceClass: ophyd.EpicsSignal + deviceConfig: + read_pv: "X01DA-ES1-LAS:Relay" + onFailure: retry + enabled: true + softwareTrigger: false + \ No newline at end of file diff --git a/debye_bec/devices/amplifiers.py b/debye_bec/devices/amplifiers.py new file mode 100644 index 0000000..4786bf6 --- /dev/null +++ b/debye_bec/devices/amplifiers.py @@ -0,0 +1,186 @@ +""" ES Current amplifiers""" + +import enum +from typing import Literal + +from ophyd import Component as Cpt +from ophyd import Device, Kind, EpicsSignalWithRBV +from ophyd_devices.utils import bec_utils + +class Enable(int, enum.Enum): + """Enum class for the enable signal of the channel""" + + OFF = 0 + STARTUP = 1 + ON = 2 + +class Gain(int, enum.Enum): + """Enum class for the gain of the channel""" + + G1E6 = 0 + G1E7 = 1 + G5E7 = 2 + G1E8 = 3 + G1E9 = 4 + +class Filter(int, enum.Enum): + """Enum class for the filter of the channel""" + + F1US = 0 + F3US = 1 + F10US = 2 + F30US = 3 + F100US = 4 + F300US = 5 + F1MS = 6 + F3MS = 7 + +class Amplifiers(Device): + """Class for the ES current amplifiers""" + + USER_ACCESS = ['set_channel'] + + ic0_enable = Cpt( + EpicsSignalWithRBV, suffix=".cOnOff1", kind="config", doc='Enable ch1 -> IC0' + ) + ic0_gain = Cpt( + EpicsSignalWithRBV, suffix=":cGain1_ENUM", kind="config", doc='Gain of ch1 -> IC0' + ) + ic0_filter = Cpt( + EpicsSignalWithRBV, suffix=":cFilter1_ENUM", kind="config", doc='Filter of ch1 -> IC0' + ) + + ic1_enable = Cpt( + EpicsSignalWithRBV, suffix=".cOnOff2", kind="config", doc='Enable ch2 -> IC1' + ) + ic1_gain = Cpt( + EpicsSignalWithRBV, suffix=":cGain2_ENUM", kind="config", doc='Gain of ch2 -> IC1' + ) + ic1_filter = Cpt( + EpicsSignalWithRBV, suffix=":cFilter2_ENUM", kind="config", doc='Filter of ch2 -> IC1' + ) + + ic2_enable = Cpt( + EpicsSignalWithRBV, suffix=".cOnOff3", kind="config", doc='Enable ch3 -> IC2' + ) + ic2_gain = Cpt( + EpicsSignalWithRBV, suffix=":cGain3_ENUM", kind="config", doc='Gain of ch3 -> IC2' + ) + ic2_filter = Cpt( + EpicsSignalWithRBV, suffix=":cFilter3_ENUM", kind="config", doc='Filter of ch3 -> IC2' + ) + + pips_enable = Cpt( + EpicsSignalWithRBV, suffix=".cOnOff4", kind="config", doc='Enable ch4 -> PIPS' + ) + pips_gain = Cpt( + EpicsSignalWithRBV, suffix=":cGain4_ENUM", kind="config", doc='Gain of ch4 -> PIPS' + ) + pips_filter = Cpt( + EpicsSignalWithRBV, suffix=":cFilter4_ENUM", kind="config", doc='Filter of ch4 -> PIPS' + ) + + def __init__( + self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs + ): + """Initialize the Current Amplifiers. + + Args: + prefix (str): EPICS prefix for the device + name (str): Name of the device + kind (Kind): Kind of the device + device_manager (DeviceManager): Device manager instance + parent (Device): Parent device + kwargs: Additional keyword arguments + """ + super().__init__(prefix, name=name, kind=kind, parent=parent, **kwargs) + self.device_manager = device_manager + self.service_cfg = None + + self.timeout_for_pvwait = 2.5 + self.readback.name = self.name + # Wait for connection on all components, ensure IOC is connected + self.wait_for_connection(all_signals=True, timeout=5) + + if device_manager: + self.device_manager = device_manager + else: + self.device_manager = bec_utils.DMMock() + + self.connector = self.device_manager.connector + + def set_channel( + self, + detector: Literal['ic0', 'ic1', 'ic2', 'pips'], + gain: Literal['1e6', '1e7', '5e7', '1e8', '1e9'], + filter: Literal['1us', '3us', '10us', '30us', '100us', '300us', '1ms', '3ms'] + ) -> None: + """Configure the gain setting of the specified channel + + Args: + detector (Literal['ic0', 'ic1', 'ic2', 'pips']) : Detector + gain (Literal['1e6', '1e7', '5e7', '1e8', '1e9']) : Desired gain + filter (Literal['1us', '3us', '10us', '30us', '100us', '300us', '1ms', '3ms']) : Desired filter + """ + + ch_enable = None + ch_gain = None + ch_filter = None + match detector: + case 'ic0': + ch_enable = self.ic0_enable + ch_gain = self.ic0_gain + ch_filter = self.ic0_filter + case 'ic1': + ch_enable = self.ic1_enable + ch_gain = self.ic1_gain + ch_filter = self.ic1_filter + case 'ic2': + ch_enable = self.ic2_enable + ch_gain = self.ic2_gain + ch_filter = self.ic2_filter + case 'pips': + ch_enable = self.pips_enable + ch_gain = self.pips_gain + ch_filter = self.pips_filter + + ch_enable.put(Enable.ON) + # Wait until channel is switched on + if not self.wait_for_signals( + signal_conditions=[(ch_enable.get, Enable.ON)], + timeout=self.timeout_for_pvwait, + check_stopped=True, + ): + raise TimeoutError( + f"Enabling channel run into timeout after {self.timeout_for_pvwait} seconds" + ) + + match gain: + case '1e6': + ch_gain.put(Gain.G1E6) + case '1e7': + ch_gain.put(Gain.G1E7) + case '5e7': + ch_gain.put(Gain.G5E7) + case '1e8': + ch_gain.put(Gain.G1E8) + case '1e9': + ch_gain.put(Gain.G1E9) + + match filter: + case '1us': + ch_filter.put(Filter.F1US) + case '3us': + ch_filter.put(Filter.F3US) + case '10us': + ch_filter.put(Filter.F10US) + case '30us': + ch_filter.put(Filter.F30US) + case '100us': + ch_filter.put(Filter.F100US) + case '300us': + ch_filter.put(Filter.F300US) + case '1ms': + ch_filter.put(Filter.F1MS) + case '3ms': + ch_filter.put(Filter.F3MS) diff --git a/debye_bec/devices/es0filter.py b/debye_bec/devices/es0filter.py new file mode 100644 index 0000000..52818d4 --- /dev/null +++ b/debye_bec/devices/es0filter.py @@ -0,0 +1,89 @@ +""" ES0 Filter Station""" + +from ophyd import Component as Cpt +from ophyd import Device, Kind, EpicsSignalWithRBV + +from ophyd_devices.utils import bec_utils + +class ES0Filter(Device): + """Class for the ES0 filter station""" + + USER_ACCESS = ['set_filters'] + + filter_output = Cpt( + EpicsSignalWithRBV, + suffix="BIO", + kind="config", + doc='Packed value of filter positions' + ) + + def __init__( + self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs + ): + """Initialize the ES0 Filter Station. + + Args: + prefix (str): EPICS prefix for the device + name (str): Name of the device + kind (Kind): Kind of the device + device_manager (DeviceManager): Device manager instance + parent (Device): Parent device + kwargs: Additional keyword arguments + """ + super().__init__(prefix, name=name, kind=kind, parent=parent, **kwargs) + self.device_manager = device_manager + self.service_cfg = None + + self.timeout_for_pvwait = 2.5 + self.readback.name = self.name + # Wait for connection on all components, ensure IOC is connected + self.wait_for_connection(all_signals=True, timeout=5) + + if device_manager: + self.device_manager = device_manager + else: + self.device_manager = bec_utils.DMMock() + + self.connector = self.device_manager.connector + + def set_filters(self, filters: list) -> None: + """Configure the filters according to the list + + Args: + filters (list) : List of strings representing the filters, e.g. ['Mo400', 'Al20'] + """ + + output = 0 + for filter in filters: + match filter: + case 'Mo400': + output = output & (1 << 1) + case 'Mo300': + output = output & (1 << 2) + case 'Mo200': + output = output & (1 << 3) + case 'Zn500': + output = output & (1 << 4) + case 'Zn250': + output = output & (1 << 5) + case 'Zn125': + output = output & (1 << 6) + case 'Zn50': + output = output & (1 << 7) + case 'Zn25': + output = output & (1 << 8) + case 'Al500': + output = output & (1 << 9) + case 'Al320': + output = output & (1 << 10) + case 'Al200': + output = output & (1 << 11) + case 'Al100': + output = output & (1 << 12) + case 'Al50': + output = output & (1 << 13) + case 'Al20': + output = output & (1 << 14) + case 'Al10': + output = output & (1 << 15) + self.filter_output.put(output) diff --git a/debye_bec/devices/gas_mix_setup.py b/debye_bec/devices/gas_mix_setup.py new file mode 100644 index 0000000..7c7996c --- /dev/null +++ b/debye_bec/devices/gas_mix_setup.py @@ -0,0 +1,236 @@ +""" ES Gas Mix Setup""" + +import time +from typing import Literal + +from ophyd import Component as Cpt +from ophyd import Device, Kind, EpicsSignalWithRBV, EpicsSignal, EpicsSignalRO +from ophyd_devices.utils import bec_utils + +class GasMixSetup(Device): + """Class for the ES HGas Mix Setup""" + + USER_ACCESS = ['fill_ic'] + + # IC0 + ic0_gas1_req = Cpt( + EpicsSignalWithRBV, suffix="IC0Gas1Req", kind="config", doc='IC0 Gas 1 requirement' + ) + ic0_conc1_req = Cpt( + EpicsSignalWithRBV, suffix="IC0Conc1Req", kind="config", doc='IC0 Concentration 1 requirement' + ) + ic0_gas2_req = Cpt( + EpicsSignalWithRBV, suffix="IC0Gas2Req", kind="config", doc='IC0 Gas 2 requirement' + ) + ic0_conc2_req = Cpt( + EpicsSignalWithRBV, suffix="IC0Conc2Req", kind="config", doc='IC0 Concentration 2 requirement' + ) + ic0_press_req = Cpt( + EpicsSignalWithRBV, suffix="IC0PressReq", kind="config", doc='IC0 Pressure requirement' + ) + ic0_fill = Cpt( + EpicsSignal, suffix="IC0Fill", kind="config", doc='Fill IC0' + ) + ic0_status = Cpt( + EpicsSignalRO, suffix="IC0Status", kind="config", doc='Status of IC0' + ) + ic0_gas1 = Cpt( + EpicsSignalRO, suffix="IC0Gas1", kind="config", doc='IC0 Gas 1' + ) + ic0_conc1 = Cpt( + EpicsSignalRO, suffix="IC0Conc1", kind="config", doc='IC0 Concentration 1' + ) + ic0_gas2 = Cpt( + EpicsSignalRO, suffix="IC0Gas2", kind="config", doc='IC0 Gas 2' + ) + ic0_conc2 = Cpt( + EpicsSignalRO, suffix="IC0Conc2", kind="config", doc='IC0 Concentration 2' + ) + ic0_press = Cpt( + EpicsSignalRO, suffix="IC0PressTransm", kind="config", doc='Current IC0 Pressure' + ) + + # IC1 + ic1_gas1_req = Cpt( + EpicsSignalWithRBV, suffix="IC1Gas1Req", kind="config", doc='IC1 Gas 1 requirement' + ) + ic1_conc1_req = Cpt( + EpicsSignalWithRBV, suffix="IC1Conc1Req", kind="config", doc='IC1 Concentration 1 requirement' + ) + ic1_gas2_req = Cpt( + EpicsSignalWithRBV, suffix="IC1Gas2Req", kind="config", doc='IC1 Gas 2 requirement' + ) + ic1_conc2_req = Cpt( + EpicsSignalWithRBV, suffix="IC1Conc2Req", kind="config", doc='IC1 Concentration 2 requirement' + ) + ic1_press_req = Cpt( + EpicsSignalWithRBV, suffix="IC1PressReq", kind="config", doc='IC1 Pressure requirement' + ) + ic1_fill = Cpt( + EpicsSignal, suffix="IC1Fill", kind="config", doc='Fill IC1' + ) + ic1_status = Cpt( + EpicsSignalRO, suffix="IC1Status", kind="config", doc='Status of IC1' + ) + ic1_gas1 = Cpt( + EpicsSignalRO, suffix="IC1Gas1", kind="config", doc='IC1 Gas 1' + ) + ic1_conc1 = Cpt( + EpicsSignalRO, suffix="IC1Conc1", kind="config", doc='IC1 Concentration 1' + ) + ic1_gas2 = Cpt( + EpicsSignalRO, suffix="IC1Gas2", kind="config", doc='IC1 Gas 2' + ) + ic1_conc2 = Cpt( + EpicsSignalRO, suffix="IC1Conc2", kind="config", doc='IC1 Concentration 2' + ) + ic1_press = Cpt( + EpicsSignalRO, suffix="IC1PressTransm", kind="config", doc='Current IC1 Pressure' + ) + + # IC2 + ic2_gas1_req = Cpt( + EpicsSignalWithRBV, suffix="IC2Gas1Req", kind="config", doc='IC2 Gas 1 requirement' + ) + ic2_conc1_req = Cpt( + EpicsSignalWithRBV, suffix="IC2Conc1Req", kind="config", doc='IC2 Concentration 1 requirement' + ) + ic2_gas2_req = Cpt( + EpicsSignalWithRBV, suffix="IC2Gas2Req", kind="config", doc='IC2 Gas 2 requirement' + ) + ic2_conc2_req = Cpt( + EpicsSignalWithRBV, suffix="IC2Conc2Req", kind="config", doc='IC2 Concentration 2 requirement' + ) + ic2_press_req = Cpt( + EpicsSignalWithRBV, suffix="IC2PressReq", kind="config", doc='IC2 Pressure requirement' + ) + ic2_fill = Cpt( + EpicsSignal, suffix="IC2Fill", kind="config", doc='Fill IC2' + ) + ic2_status = Cpt( + EpicsSignalRO, suffix="IC2Status", kind="config", doc='Status of IC2' + ) + ic2_gas1 = Cpt( + EpicsSignalRO, suffix="IC2Gas1", kind="config", doc='IC2 Gas 1' + ) + ic2_conc1 = Cpt( + EpicsSignalRO, suffix="IC2Conc1", kind="config", doc='IC2 Concentration 1' + ) + ic2_gas2 = Cpt( + EpicsSignalRO, suffix="IC2Gas2", kind="config", doc='IC2 Gas 2' + ) + ic2_conc2 = Cpt( + EpicsSignalRO, suffix="IC2Conc2", kind="config", doc='IC2 Concentration 2' + ) + ic2_press = Cpt( + EpicsSignalRO, suffix="IC2PressTransm", kind="config", doc='Current IC2 Pressure' + ) + + + def __init__( + self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs + ): + """Initialize the Gas Mix Setup. + + Args: + prefix (str): EPICS prefix for the device + name (str): Name of the device + kind (Kind): Kind of the device + device_manager (DeviceManager): Device manager instance + parent (Device): Parent device + kwargs: Additional keyword arguments + """ + super().__init__(prefix, name=name, kind=kind, parent=parent, **kwargs) + self.device_manager = device_manager + self.service_cfg = None + + self.timeout_for_pvwait = 360 + self.readback.name = self.name + # Wait for connection on all components, ensure IOC is connected + self.wait_for_connection(all_signals=True, timeout=5) + + if device_manager: + self.device_manager = device_manager + else: + self.device_manager = bec_utils.DMMock() + + self.connector = self.device_manager.connector + + def fill_ic( + self, + detector: Literal['ic0', 'ic1', 'ic2'], + gas1: Literal['He', 'LN2', 'Ar', 'Kr'], + conc1: float, + gas2: Literal['He', 'LN2', 'Ar', 'Kr'], + conc2: float, + pressure: float, + ) -> None: + """Fill an ionization chamber with the specified gas mixture. + + Args: + detector (Literal['ic0', 'ic1', 'ic2']) : Detector + gas1 (Literal['He', 'LN2', 'Ar', 'Kr']) : Gas 1 requirement, + conc1 (float) : Concentration 1 requirement in %, + gas2 (Literal['He', 'LN2', 'Ar', 'Kr']) : Gas 2 requirement, + conc2 (float) : Concentration 2 requirement in %, + pressure (float) : Required pressure in bar abs, + """ + + if 100 < conc1 < 0: + raise ValueError('Concentration 1 out of range [0 .. 100 %]') + if 100 < conc2 < 0: + raise ValueError('Concentration 2 out of range [0 .. 100 %]') + if 3 < pressure < 0: + raise ValueError('Pressure out of range [0 .. 3 bar abs]') + + ch_gas1_req = None + ch_conc1_req = None + ch_gas2_req = None + ch_conc2_req = None + ch_press_req = None + ch_fill = None + ch_status = None + match detector: + case 'ic0': + ch_gas1_req = self.ic0_gas1_req + ch_conc1_req = self.ic0_conc1_req + ch_gas2_req = self.ic0_gas2_req + ch_conc2_req = self.ic0_conc2_req + ch_press_req = self.ic0_press_req + ch_fill = self.ic0_fill + ch_status = self.ic0_status + case 'ic1': + ch_gas1_req = self.ic1_gas1_req + ch_conc1_req = self.ic1_conc1_req + ch_gas2_req = self.ic1_gas2_req + ch_conc2_req = self.ic1_conc2_req + ch_press_req = self.ic1_press_req + ch_fill = self.ic1_fill + ch_status = self.ic1_status + case 'ic2': + ch_gas1_req = self.ic2_gas1_req + ch_conc1_req = self.ic2_conc1_req + ch_gas2_req = self.ic2_gas2_req + ch_conc2_req = self.ic2_conc2_req + ch_press_req = self.ic2_press_req + ch_fill = self.ic2_fill + ch_status = self.ic2_status + + ch_gas1_req.put(gas1) + ch_conc1_req.put(conc1) + ch_gas2_req.put(gas2) + ch_conc2_req.put(conc2) + ch_press_req.put(pressure) + time.sleep(0.5) + ch_fill.put(1) + time.sleep(1) + + # Wait until ionization chamber is filled successfully + if not self.wait_for_signals( + signal_conditions=[(ch_status.get, 1)], + timeout=self.timeout_for_pvwait, + check_stopped=True, + ): + raise TimeoutError( + f"Ionization chamber still not filled after {self.timeout_for_pvwait} seconds, check caqtdm panel" + ) diff --git a/debye_bec/devices/hv_supplies.py b/debye_bec/devices/hv_supplies.py new file mode 100644 index 0000000..8799bb2 --- /dev/null +++ b/debye_bec/devices/hv_supplies.py @@ -0,0 +1,165 @@ +""" ES HV power supplies""" + +from typing import Literal + +from ophyd import Component as Cpt +from ophyd import Device, Kind, EpicsSignalWithRBV, EpicsSignal, EpicsSignalRO +from ophyd_devices.utils import bec_utils + +class Amplifiers(Device): + """Class for the ES HV power supplies""" + + USER_ACCESS = ['set_ic'] + + # IC0 + ic0_ext_ena = Cpt( + EpicsSignalRO, suffix="ES1-IC0:HV-Ext-Ena", kind="config", doc='External enable signal of HV IC0' + ) + ic0_ena = Cpt( + EpicsSignalWithRBV, suffix="ES1-IC0:HV-Ena", kind="config", doc='Enable signal of HV IC0' + ) + ic0_hv_v = Cpt( + EpicsSignal, suffix="ES1-IC0:HV1-VSet", kind="config", doc='HV voltage of IC0' + ) + ic0_hv_i = Cpt( + EpicsSignal, suffix="ES1-IC0:HV1-V-RB", kind="config", doc='HV current of IC0' + ) + ic0_grid_v = Cpt( + EpicsSignal, suffix="ES1-IC0:HV2-VSet", kind="config", doc='Grid voltage of IC0' + ) + ic0_grid_i = Cpt( + EpicsSignal, suffix="ES1-IC0:HV2-V-RB", kind="config", doc='Grid current of IC0' + ) + + # IC1 + ic1_ext_ena = Cpt( + EpicsSignalRO, suffix="ES2-IC12:HV-Ext-Ena", kind="config", doc='External enable signal of HV IC1/IC2' + ) + ic1_ena = Cpt( + EpicsSignalWithRBV, suffix="ES2-IC12:HV-Ena", kind="config", doc='Enable signal of HV IC1/IC2' + ) + ic1_hv_v = Cpt( + EpicsSignal, suffix="ES2-IC1:HV1-VSet", kind="config", doc='HV voltage of IC1' + ) + ic1_hv_i = Cpt( + EpicsSignal, suffix="ES2-IC1:HV1-V-RB", kind="config", doc='HV current of IC1' + ) + ic1_grid_v = Cpt( + EpicsSignal, suffix="ES2-IC1:HV2-VSet", kind="config", doc='Grid voltage of IC1' + ) + ic1_grid_i = Cpt( + EpicsSignal, suffix="ES2-IC1:HV2-V-RB", kind="config", doc='Grid current of IC1' + ) + + # IC2 + ic2_ext_ena = Cpt( + EpicsSignalRO, suffix="ES2-IC12:HV-Ext-Ena", kind="config", doc='External enable signal of HV IC1/IC2' + ) + ic2_ena = Cpt( + EpicsSignalWithRBV, suffix="ES2-IC12:HV-Ena", kind="config", doc='Enable signal of HV IC1/IC2' + ) + ic2_hv_v = Cpt( + EpicsSignal, suffix="ES2-IC2:HV1-VSet", kind="config", doc='HV voltage of IC2' + ) + ic2_hv_i = Cpt( + EpicsSignal, suffix="ES2-IC2:HV1-V-RB", kind="config", doc='HV current of IC2' + ) + ic2_grid_v = Cpt( + EpicsSignal, suffix="ES2-IC2:HV2-VSet", kind="config", doc='Grid voltage of IC2' + ) + ic2_grid_i = Cpt( + EpicsSignal, suffix="ES2-IC2:HV2-V-RB", kind="config", doc='Grid current of IC2' + ) + + def __init__( + self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs + ): + """Initialize the Current Amplifiers. + + Args: + prefix (str): EPICS prefix for the device + name (str): Name of the device + kind (Kind): Kind of the device + device_manager (DeviceManager): Device manager instance + parent (Device): Parent device + kwargs: Additional keyword arguments + """ + super().__init__(prefix, name=name, kind=kind, parent=parent, **kwargs) + self.device_manager = device_manager + self.service_cfg = None + + self.timeout_for_pvwait = 2.5 + self.readback.name = self.name + # Wait for connection on all components, ensure IOC is connected + self.wait_for_connection(all_signals=True, timeout=5) + + if device_manager: + self.device_manager = device_manager + else: + self.device_manager = bec_utils.DMMock() + + self.connector = self.device_manager.connector + + def set_voltage( + self, + detector: Literal['ic0', 'ic1', 'ic2'], + hv: float, + grid: float + ) -> None: + """Configure the voltage settings of the specified detector, this will + enable the high voltage (if external enable is active)! + + Args: + detector (Literal['ic0', 'ic1', 'ic2']) : Detector + hv (float) : Desired voltage for the 'HV' terminal + grid (float) : Desired voltage for the 'Grid' terminal + """ + + if 3000 < hv < 0: + raise ValueError('specified HV not within range [0 .. 3000]') + if 3000 < grid < 0: + raise ValueError('specified Grid not within range [0 .. 3000]') + if grid > hv: + raise ValueError('Grid must not be higher than HV!') + + ch_ena = None + ch_hv_v = None + ch_hv_i = None + ch_grid_v = None + ch_grid_i = None + match detector: + case 'ic0': + ch_ena = self.ic0_ena + ch_hv_v = self.ic0_hv_v + ch_hv_i = self.ic0_hv_i + ch_grid_v = self.ic0_grid_v + ch_grid_i = self.ic0_grid_i + case 'ic1': + ch_ena = self.ic1_ena + ch_hv_v = self.ic1_hv_v + ch_hv_i = self.ic1_hv_i + ch_grid_v = self.ic1_grid_v + ch_grid_i = self.ic1_grid_i + case 'ic2': + ch_ena = self.ic2_ena + ch_hv_v = self.ic2_hv_v + ch_hv_i = self.ic2_hv_i + ch_grid_v = self.ic2_grid_v + ch_grid_i = self.ic2_grid_i + + ch_ena.put(1) + # Wait until channel is switched on + if not self.wait_for_signals( + signal_conditions=[(ch_ena.get, 1)], + timeout=self.timeout_for_pvwait, + check_stopped=True, + ): + raise TimeoutError( + f"Enabling channel run into timeout after {self.timeout_for_pvwait} seconds" + ) + + # Set current fixed to 3 mA (max) + ch_hv_i.put(3) + ch_hv_v.put(hv) + ch_grid_i.put(3) + ch_grid_v.put(grid) diff --git a/debye_bec/devices/pilatus_curtain.py b/debye_bec/devices/pilatus_curtain.py new file mode 100644 index 0000000..35bcfc9 --- /dev/null +++ b/debye_bec/devices/pilatus_curtain.py @@ -0,0 +1,81 @@ +""" ES2 Pilatus Curtain""" + +import time + +from ophyd import Component as Cpt +from ophyd import Device, Kind, EpicsSignal, EpicsSignalRO +from ophyd_devices.utils import bec_utils + +class GasMixSetup(Device): + """Class for the ES2 Pilatus Curtain""" + + USER_ACCESS = ['open', 'close'] + + open_cover = Cpt( + EpicsSignal, suffix="OpenCover", kind="config", doc='Open Cover' + ) + close_cover = Cpt( + EpicsSignal, suffix="CloseCover", kind="config", doc='Close Cover' + ) + cover_is_closed = Cpt( + EpicsSignalRO, suffix="CoverIsClosed", kind="config", doc='Cover is closed' + ) + cover_is_open = Cpt( + EpicsSignalRO, suffix="CoverIsOpen", kind="config", doc='Cover is open' + ) + cover_is_moving = Cpt( + EpicsSignalRO, suffix="CoverIsMoving", kind="config", doc='Cover is moving' + ) + cover_error = Cpt( + EpicsSignalRO, suffix="CoverError", kind="config", doc='Cover error' + ) + + + def __init__( + self, prefix="", *, name: str, kind: Kind = None, device_manager=None, parent=None, **kwargs + ): + """Initialize the Pilatus Curtain. + + Args: + prefix (str): EPICS prefix for the device + name (str): Name of the device + kind (Kind): Kind of the device + device_manager (DeviceManager): Device manager instance + parent (Device): Parent device + kwargs: Additional keyword arguments + """ + super().__init__(prefix, name=name, kind=kind, parent=parent, **kwargs) + self.device_manager = device_manager + self.service_cfg = None + + self.timeout_for_pvwait = 30 + self.readback.name = self.name + # Wait for connection on all components, ensure IOC is connected + self.wait_for_connection(all_signals=True, timeout=5) + + if device_manager: + self.device_manager = device_manager + else: + self.device_manager = bec_utils.DMMock() + + self.connector = self.device_manager.connector + + def open(self) -> None: + """Open the cover""" + + self.open_cover.put(1) + + while not self.cover_is_open.get(): + time.sleep(0.1) + if self.cover_error.get(): + raise TimeoutError('Curtain did not open successfully and is now in an error state') + + def close(self) -> None: + """Close the cover""" + + self.close_cover.put(1) + + while not self.cover_is_closed.get(): + time.sleep(0.1) + if self.cover_error.get(): + raise TimeoutError('Curtain did not close successfully and is now in an error state')