diff --git a/debye_bec/device_configs/x01da_experimental_hutch.yaml b/debye_bec/device_configs/x01da_experimental_hutch.yaml index f14d1aa..08753f2 100644 --- a/debye_bec/device_configs/x01da_experimental_hutch.yaml +++ b/debye_bec/device_configs/x01da_experimental_hutch.yaml @@ -386,4 +386,64 @@ es_light_toggle: read_pv: "X01DA-EH-LIGHT:TOGGLE" onFailure: retry enabled: true + softwareTrigger: false + +es_gas_sensor_o2: + readoutPriority: baseline + description: ES Gas Sensor O2 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-KIMESSA2:EH-O2" + onFailure: retry + enabled: true + softwareTrigger: false + +es_gas_sensor_h2s: + readoutPriority: baseline + description: ES Gas Sensor H2S + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-KIMESSA2:EH-H2S" + onFailure: retry + enabled: true + softwareTrigger: false + +es_gas_sensor_no2: + readoutPriority: baseline + description: ES Gas Sensor NO2 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-KIMESSA2:EH-NO2" + onFailure: retry + enabled: true + softwareTrigger: false + +es_gas_sensor_co: + readoutPriority: baseline + description: ES Gas Sensor CO + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-KIMESSA2:EH-CO" + onFailure: retry + enabled: true + softwareTrigger: false + +es_gas_sensor_h2: + readoutPriority: baseline + description: ES Gas Sensor H2 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-KIMESSA2:EH-H2" + onFailure: retry + enabled: true + softwareTrigger: false + +es_gas_sensor_nh3: + readoutPriority: baseline + description: ES Gas Sensor NH3 + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-KIMESSA2:EH-NH3" + onFailure: retry + enabled: true softwareTrigger: false \ No newline at end of file diff --git a/debye_bec/device_configs/x01da_hutch_cameras.yaml b/debye_bec/device_configs/x01da_hutch_cameras.yaml new file mode 100644 index 0000000..63efbc5 --- /dev/null +++ b/debye_bec/device_configs/x01da_hutch_cameras.yaml @@ -0,0 +1,34 @@ + +################################### +## Hutch Cameras ## +################################### + +hutch_cam_1: + readoutPriority: baseline + description: Hutch Camera 1 + deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam + deviceConfig: + prefix: "pcp085420" + onFailure: retry + enabled: true + softwareTrigger: false + +hutch_cam_2: + readoutPriority: baseline + description: Hutch Camera 2 + deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam + deviceConfig: + prefix: "pcp085436" + onFailure: retry + enabled: true + softwareTrigger: false + +hutch_cam_3: + readoutPriority: baseline + description: Hutch Camera 3 + deviceClass: debye_bec.devices.cameras.hutch_cam.HutchCam + deviceConfig: + prefix: "pcp085435" + onFailure: retry + enabled: true + softwareTrigger: false \ No newline at end of file diff --git a/debye_bec/device_configs/x01da_standard_config.yaml b/debye_bec/device_configs/x01da_standard_config.yaml index 5a0a701..3490ae0 100644 --- a/debye_bec/device_configs/x01da_standard_config.yaml +++ b/debye_bec/device_configs/x01da_standard_config.yaml @@ -51,7 +51,7 @@ optics_config: ## Experimental Hutch ## ################################### -## NIDAQ +# ## NIDAQ nidaq: readoutPriority: monitored description: NIDAQ backend for data reading for debye scans @@ -67,8 +67,13 @@ xas_config: - !include ./x01da_xas.yaml ## XRD (Pilatus, pinhole, beamstop) -xrd_config: - - !include ./x01da_xrd.yaml +#xrd_config: +# - !include ./x01da_xrd.yaml + +# Commented out because too slow +## Hutch cameras +# hutch_cams: +# - !include ./x01da_hutch_cameras.yaml ## Remaining experimental hutch es_config: diff --git a/debye_bec/device_configs/x01da_xas.yaml b/debye_bec/device_configs/x01da_xas.yaml index 42e2876..4fb696d 100644 --- a/debye_bec/device_configs/x01da_xas.yaml +++ b/debye_bec/device_configs/x01da_xas.yaml @@ -3,35 +3,45 @@ ## Ionization Chambers ## ################################### -# ic0: -# readoutPriority: baseline -# description: Ionization chamber 0 -# deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0 -# deviceConfig: -# prefix: "X01DA-" -# onFailure: retry -# enabled: true -# softwareTrigger: false +ic0: + readoutPriority: baseline + description: Ionization chamber 0 + deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber0 + deviceConfig: + prefix: "X01DA-" + onFailure: retry + enabled: true + softwareTrigger: false -# ic1: -# readoutPriority: baseline -# description: Ionization chamber 1 -# deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1 -# deviceConfig: -# prefix: "X01DA-" -# onFailure: retry -# enabled: true -# softwareTrigger: false +ic1: + readoutPriority: baseline + description: Ionization chamber 1 + deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber1 + deviceConfig: + prefix: "X01DA-" + onFailure: retry + enabled: true + softwareTrigger: false -# ic2: -# readoutPriority: baseline -# description: Ionization chamber 2 -# deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2 -# deviceConfig: -# prefix: "X01DA-" -# onFailure: retry -# enabled: true -# softwareTrigger: false +ic2: + readoutPriority: baseline + description: Ionization chamber 2 + deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.IonizationChamber2 + deviceConfig: + prefix: "X01DA-" + onFailure: retry + enabled: true + softwareTrigger: false + +pips: + readoutPriority: baseline + description: Pips diode + deviceClass: debye_bec.devices.ionization_chambers.ionization_chamber.Pips + deviceConfig: + prefix: "X01DA-" + onFailure: retry + enabled: true + softwareTrigger: false ################################### ## Reference Foil Changer ## diff --git a/debye_bec/device_configs/x01da_xrd.yaml b/debye_bec/device_configs/x01da_xrd.yaml index 22ffdba..961cece 100644 --- a/debye_bec/device_configs/x01da_xrd.yaml +++ b/debye_bec/device_configs/x01da_xrd.yaml @@ -86,7 +86,7 @@ pilatus_curtain: softwareTrigger: false pilatus: - readoutPriority: async + readoutPriority: baseline description: Pilatus deviceClass: debye_bec.devices.pilatus.pilatus.Pilatus deviceTags: @@ -97,12 +97,12 @@ pilatus: enabled: true softwareTrigger: true -# sampl_pil: -# readoutPriority: baseline -# description: Sample to pilatus distance -# deviceClass: ophyd.EpicsSignalRO -# deviceConfig: -# read_pv: "X01DA-SAMPL-PIL" -# onFailure: retry -# enabled: true -# softwareTrigger: false +pilatus_smpl: + readoutPriority: baseline + description: Sample to pilatus distance + deviceClass: ophyd.EpicsSignalRO + deviceConfig: + read_pv: "X01DA-ES2-DET:SMPLDIST" + onFailure: retry + enabled: true + softwareTrigger: false \ No newline at end of file diff --git a/debye_bec/devices/cameras/basler_cam.py b/debye_bec/devices/cameras/basler_cam.py index fbf0477..5c5f7ce 100644 --- a/debye_bec/devices/cameras/basler_cam.py +++ b/debye_bec/devices/cameras/basler_cam.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from ophyd import ADBase +from ophyd import ADBase, EpicsSignalRO from ophyd import ADComponent as ADCpt from ophyd import Component as Cpt from ophyd_devices import PreviewSignal @@ -20,6 +20,16 @@ if TYPE_CHECKING: # pragma: no cover class BaslerCamBase(ADBase): """BaslerCam Base class.""" + cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True) + + _default_configuration_attrs = [ + 'cam1.acquire_time', + 'cam1.detector_state', + 'cam_detector_state_string', + 'cam1.gain', + 'cam1.model', + ] + cam1 = ADCpt(AravisDetectorCam, "cam1:") image1 = ADCpt(ImagePlugin_V35, "image1:") diff --git a/debye_bec/devices/cameras/hutch_cam.py b/debye_bec/devices/cameras/hutch_cam.py new file mode 100644 index 0000000..633b8dc --- /dev/null +++ b/debye_bec/devices/cameras/hutch_cam.py @@ -0,0 +1,79 @@ +"""EH Hutch Cameras""" + +from __future__ import annotations + +import cv2 +import threading +from typing import TYPE_CHECKING + +from bec_lib.logger import bec_logger +from bec_lib.file_utils import get_full_path +from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase +from ophyd_devices import DeviceStatus + +if TYPE_CHECKING: # pragma: no cover + from bec_lib.devicemanager import ScanInfo + from bec_lib.messages import ScanStatusMessage + +logger = bec_logger.logger + +CAM_USERNAME = "camera_user" +CAM_PASSWORD = "camera_user1" +CAM_PORT = 554 + +class HutchCam(PSIDeviceBase): + """Class for the Hutch Cameras""" + + # image = Cpt(Signal, name='image', kind='config') + + def __init__(self, *, name: str, prefix: str = "", scan_info: ScanInfo | None = None, **kwargs): + super().__init__(name=name, scan_info=scan_info, **kwargs) + + self.hostname = prefix + self.status = None + + # pylint: disable=E1101 + def on_connected(self) -> None: + """ + Called after the device is connected and its signals are connected. + Default values for signals should be set here. + """ + rtsp_url = f"rtsp://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch:{CAM_PORT}/rtpstream/config1" + cap = cv2.VideoCapture(f"{rtsp_url}?tcp") + if not cap.isOpened(): + logger.error(self, "Connection Failed", "Could not connect to the camera stream.") + return + cap.release() + + def on_stage(self) -> DeviceStatus: + """Called while staging the device.""" + + scan_msg: ScanStatusMessage = self.scan_info.msg + file_path = get_full_path(scan_msg, name='hutch_cam_' + self.hostname).removesuffix('h5') + + self.status = DeviceStatus(self) + + thread = threading.Thread(target=self._save_picture, args=(file_path, self.status), daemon=True) + thread.start() + + return self.status + + def _save_picture(self, file_path, status): + try: + logger.info(f'Capture from camera {self.hostname}') + rtsp_url = f"rtsp://{CAM_USERNAME}:{CAM_PASSWORD}@{self.hostname}.psi.ch:{CAM_PORT}/rtpstream/config1" + cap = cv2.VideoCapture(f"{rtsp_url}?tcp") + if not cap.isOpened(): + logger.error("Connection Failed", "Could not connect to the camera stream.") + return + logger.info(f'Connection to camera {self.hostname} established') + ret, frame = cap.readAsync() + cap.release() + if not ret: + logger.error("Capture Failed", "Failed to capture image from camera.") + return + cv2.imwrite(file_path + 'png', frame) + status.set_finished() + logger.info(f'Capture from camera {self.hostname} done') + except Exception as e: + status.set_exception(e) diff --git a/debye_bec/devices/cameras/prosilica_cam.py b/debye_bec/devices/cameras/prosilica_cam.py index 69846dd..92f842d 100644 --- a/debye_bec/devices/cameras/prosilica_cam.py +++ b/debye_bec/devices/cameras/prosilica_cam.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from ophyd import ADBase +from ophyd import ADBase, EpicsSignalRO from ophyd import ADComponent as ADCpt from ophyd import Component as Cpt from ophyd_devices import PreviewSignal @@ -20,6 +20,16 @@ if TYPE_CHECKING: # pragma: no cover class ProsilicaCamBase(ADBase): """Base class for Prosilica cameras.""" + cam_detector_state_string = Cpt(EpicsSignalRO, suffix="cam1:DetectorState_RBV", string=True) + + _default_configuration_attrs = [ + 'cam1.acquire_time', + 'cam1.detector_state', + 'cam_detector_state_string', + 'cam1.gain', + 'cam1.model', + ] + cam1 = ADCpt(ProsilicaDetectorCam, "cam1:") image1 = ADCpt(ImagePlugin_V35, "image1:") diff --git a/debye_bec/devices/ionization_chambers/ionization_chamber.py b/debye_bec/devices/ionization_chambers/ionization_chamber.py index 52a6a78..25b81d8 100644 --- a/debye_bec/devices/ionization_chambers/ionization_chamber.py +++ b/debye_bec/devices/ionization_chambers/ionization_chamber.py @@ -33,22 +33,24 @@ class EpicsSignalSplit(EpicsSignal): class GasMixSetupControl(Device): """GasMixSetup Control for Inonization Chamber 0""" - gas1_req = Cpt(EpicsSignalWithRBV, suffix="Gas1Req", kind="config", doc="Gas 1 requirement") + gas1_req = Cpt(EpicsSignalWithRBV, suffix="Gas1Req", kind="omitted", doc="Gas 1 requirement") conc1_req = Cpt( - EpicsSignalWithRBV, suffix="Conc1Req", kind="config", doc="Concentration 1 requirement" + EpicsSignalWithRBV, suffix="Conc1Req", kind="omitted", doc="Concentration 1 requirement" ) - gas2_req = Cpt(EpicsSignalWithRBV, suffix="Gas2Req", kind="config", doc="Gas 2 requirement") + gas2_req = Cpt(EpicsSignalWithRBV, suffix="Gas2Req", kind="omitted", doc="Gas 2 requirement") conc2_req = Cpt( - EpicsSignalWithRBV, suffix="Conc2Req", kind="config", doc="Concentration 2 requirement" + EpicsSignalWithRBV, suffix="Conc2Req", kind="omitted", doc="Concentration 2 requirement" ) press_req = Cpt( - EpicsSignalWithRBV, suffix="PressReq", kind="config", doc="Pressure requirement" + EpicsSignalWithRBV, suffix="PressReq", kind="omitted", doc="Pressure requirement" ) fill = Cpt(EpicsSignal, suffix="Fill", kind="config", doc="Fill the chamber") status = Cpt(EpicsSignalRO, suffix="Status", kind="config", doc="Status") gas1 = Cpt(EpicsSignalRO, suffix="Gas1", kind="config", doc="Gas 1") + gas1_string = Cpt(EpicsSignalRO, suffix="Gas1", kind="config", doc="Gas 1", string=True) conc1 = Cpt(EpicsSignalRO, suffix="Conc1", kind="config", doc="Concentration 1") gas2 = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2") + gas2_string = Cpt(EpicsSignalRO, suffix="Gas2", kind="config", doc="Gas 2", string=True) conc2 = Cpt(EpicsSignalRO, suffix="Conc2", kind="config", doc="Concentration 2") press = Cpt(EpicsSignalRO, suffix="PressTransm", kind="config", doc="Current Pressure") @@ -84,10 +86,25 @@ class IonizationChamber0(PSIDeviceBase): (f"ES:AMP5004:cFilter{num}_ENUM"), {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"}, ), + "cOnOff_string": ( + EpicsSignal, + (f"ES:AMP5004.cOnOff{num}"), + {"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True}, + ), + "cGain_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cGain{num}_ENUM"), + {"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True}, + ), + "cFilter_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cFilter{num}_ENUM"), + {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True}, + ), } amp = Dcpt(amp_signals) gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}") - gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status") + gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status") hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES1-IC{num-1}:") hv_en_signals = { "ext_ena": ( @@ -275,10 +292,25 @@ class IonizationChamber1(IonizationChamber0): (f"ES:AMP5004:cFilter{num}_ENUM"), {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"}, ), + "cOnOff_string": ( + EpicsSignal, + (f"ES:AMP5004.cOnOff{num}"), + {"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True}, + ), + "cGain_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cGain{num}_ENUM"), + {"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True}, + ), + "cFilter_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cFilter{num}_ENUM"), + {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True}, + ), } amp = Dcpt(amp_signals) gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}") - gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status") + gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status") hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:") hv_en_signals = { "ext_ena": ( @@ -311,10 +343,25 @@ class IonizationChamber2(IonizationChamber0): (f"ES:AMP5004:cFilter{num}_ENUM"), {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"}, ), + "cOnOff_string": ( + EpicsSignal, + (f"ES:AMP5004.cOnOff{num}"), + {"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True}, + ), + "cGain_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cGain{num}_ENUM"), + {"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True}, + ), + "cFilter_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cFilter{num}_ENUM"), + {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True}, + ), } amp = Dcpt(amp_signals) gmes = Cpt(GasMixSetupControl, suffix=f"ES-GMES:IC{num-1}") - gmes_status = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status") + gmes_status_msg = Cpt(EpicsSignalRO, suffix="ES-GMES:StatusMsg0", kind="config", doc="Status") hv = Cpt(HighVoltageSuppliesControl, suffix=f"ES2-IC{num-1}:") hv_en_signals = { "ext_ena": ( @@ -325,3 +372,63 @@ class IonizationChamber2(IonizationChamber0): "ena": (EpicsSignal, "ES2-IC12:HV-Ena", {"kind": "config", "doc": "Enable signal of HV"}), } hv_en = Dcpt(hv_en_signals) + +class Pips(IonizationChamber0): + """Pips, prefix should be 'X01DA-'.""" + + USER_ACCESS = ["set_gain", "set_filter"] + + num = 4 + amp_signals = { + "cOnOff": ( + EpicsSignal, + (f"ES:AMP5004.cOnOff{num}"), + {"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}"}, + ), + "cGain_ENUM": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cGain{num}_ENUM"), + {"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}"}, + ), + "cFilter_ENUM": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cFilter{num}_ENUM"), + {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}"}, + ), + "cOnOff_string": ( + EpicsSignal, + (f"ES:AMP5004.cOnOff{num}"), + {"kind": "config", "doc": f"Enable ch{num} -> IC{num-1}", "string": True}, + ), + "cGain_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cGain{num}_ENUM"), + {"kind": "config", "doc": f"Gain of ch{num} -> IC{num-1}", "string": True}, + ), + "cFilter_ENUM_string": ( + EpicsSignalWithRBV, + (f"ES:AMP5004:cFilter{num}_ENUM"), + {"kind": "config", "doc": f"Filter of ch{num} -> IC{num-1}", "string": True}, + ), + } + amp = Dcpt(amp_signals) + gmes = None + gmes_status_msg = None + hv = None + hv_en_signals = None + hv_en = None + + @typechecked + def set_hv(self, *_) -> None: + """Not available for the PIPS""" + return None + + @typechecked + def set_grid(self, *_) -> None: + """Not available for the PIPS""" + return None + + @typechecked + def fill(self, *_) -> None: + """Not available for the PIPS""" + return None diff --git a/debye_bec/devices/nidaq/nidaq.py b/debye_bec/devices/nidaq/nidaq.py index 8d6dcd9..3cd2f58 100644 --- a/debye_bec/devices/nidaq/nidaq.py +++ b/debye_bec/devices/nidaq/nidaq.py @@ -146,6 +146,76 @@ class NidaqControl(Device): doc="EPICS counter input 7", auto_monitor=True, ) + ci8 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI8", + kind=Kind.normal, + doc="EPICS counter input 8", + auto_monitor=True, + ) + ci9 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI9", + kind=Kind.normal, + doc="EPICS counter input 9", + auto_monitor=True, + ) + ci10 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI10", + kind=Kind.normal, + doc="EPICS counter input 0", + auto_monitor=True, + ) + ci11 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI11", + kind=Kind.normal, + doc="EPICS counter input 1", + auto_monitor=True, + ) + ci12 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI12", + kind=Kind.normal, + doc="EPICS counter input 2", + auto_monitor=True, + ) + ci13 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI13", + kind=Kind.normal, + doc="EPICS counter input 3", + auto_monitor=True, + ) + ci14 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI14", + kind=Kind.normal, + doc="EPICS counter input 4", + auto_monitor=True, + ) + ci15 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI15", + kind=Kind.normal, + doc="EPICS counter input 5", + auto_monitor=True, + ) + ci16 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI16", + kind=Kind.normal, + doc="EPICS counter input 6", + auto_monitor=True, + ) + ci17 = Cpt( + EpicsSignalRO, + suffix="NIDAQ-CI17", + kind=Kind.normal, + doc="EPICS counter input 7", + auto_monitor=True, + ) di0 = Cpt( EpicsSignalRO, @@ -275,6 +345,36 @@ class NidaqControl(Device): ci7_mean = Cpt( SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7, MEAN" ) + ci8_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8, MEAN" + ) + ci9_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9, MEAN" + ) + ci10_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10, MEAN" + ) + ci11_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11, MEAN" + ) + ci12_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12, MEAN" + ) + ci13_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13, MEAN" + ) + ci14_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14, MEAN" + ) + ci15_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15, MEAN" + ) + ci16_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16, MEAN" + ) + ci17_mean = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17, MEAN" + ) ci0_std_dev = Cpt( SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 0. STD" @@ -300,12 +400,44 @@ class NidaqControl(Device): ci7_std_dev = Cpt( SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 7. STD" ) + ci8_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 8. STD" + ) + ci9_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 9. STD" + ) + ci10_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 10. STD" + ) + ci11_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 11. STD" + ) + ci12_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 12. STD" + ) + ci13_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 13. STD" + ) + ci14_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 14. STD" + ) + ci15_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 15. STD" + ) + ci16_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 16. STD" + ) + ci17_std_dev = Cpt( + SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream counter input 17. STD" + ) xas_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XAS timestamp") xrd_timestamp = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD timestamp") - + xrd_angle = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD angle") xrd_energy = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD energy") + xrd_ai0_mean = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 mean") + xrd_ai0_std_dev = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream XRD ai0 std dev") di0_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 0, MAX") di1_max = Cpt(SetableSignal, value=0, kind=Kind.normal, doc="NIDAQ stream digital input 1, MAX") @@ -320,16 +452,21 @@ class NidaqControl(Device): ### Control PVs ### enable_compression = Cpt(EpicsSignal, suffix="NIDAQ-EnableRLE", kind=Kind.config) + enable_dead_time_correction = Cpt(EpicsSignal, suffix="NIDAQ-EnableDTC", kind=Kind.config) kickoff_call = Cpt(EpicsSignal, suffix="NIDAQ-Kickoff", kind=Kind.config) stage_call = Cpt(EpicsSignal, suffix="NIDAQ-Stage", kind=Kind.config) state = Cpt(EpicsSignal, suffix="NIDAQ-FSMState", kind=Kind.config, auto_monitor=True) server_status = Cpt(EpicsSignalRO, suffix="NIDAQ-ServerStatus", kind=Kind.config) compression_ratio = Cpt(EpicsSignalRO, suffix="NIDAQ-CompressionRatio", kind=Kind.config) scan_type = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config) + scan_type_string = Cpt(EpicsSignal, suffix="NIDAQ-ScanType", kind=Kind.config, string=True) sampling_rate = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config) + sampling_rate_string = Cpt(EpicsSignal, suffix="NIDAQ-SamplingRateRequested", kind=Kind.config, string=True) scan_duration = Cpt(EpicsSignal, suffix="NIDAQ-SamplingDuration", kind=Kind.config) readout_range = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config) + readout_range_string = Cpt(EpicsSignal, suffix="NIDAQ-ReadoutRange", kind=Kind.config, string=True) encoder_factor = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config) + encoder_factor_string = Cpt(EpicsSignal, suffix="NIDAQ-EncoderFactor", kind=Kind.config, string=True) stop_call = Cpt(EpicsSignal, suffix="NIDAQ-Stop", kind=Kind.config) power = Cpt(EpicsSignal, suffix="NIDAQ-Power", kind=Kind.config) heartbeat = Cpt(EpicsSignal, suffix="NIDAQ-Heartbeat", kind=Kind.config, auto_monitor=True) @@ -357,7 +494,7 @@ class Nidaq(PSIDeviceBase, NidaqControl): super().__init__(name=name, prefix=prefix, scan_info=scan_info, **kwargs) self.scan_info: ScanInfo self.timeout_wait_for_signal = 5 # put 5s firsts - self._timeout_wait_for_pv = 3 # 3s timeout for pv calls + self._timeout_wait_for_pv = 5 # 5s timeout for pv calls. editted due to timeout issues persisting self.valid_scan_names = [ "xas_simple_scan", "xas_simple_scan_with_xrd", @@ -556,7 +693,11 @@ class Nidaq(PSIDeviceBase, NidaqControl): # Stage call to IOC status = CompareStatus(self.state, NidaqState.STAGE) self.cancel_on_stop(status) - self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv) + # TODO 11.11.25/HS64 + # Switched from set to put in the hope to get rid of the rare event where nidaq is stopped at the start of a scan + # Problems consistently persisting, testing changing back to set, unconvinced this is the actual cause 14.11.25/AHC + # self.stage_call.set(1).wait(timeout=self._timeout_wait_for_pv) + self.stage_call.put(1) status.wait(timeout=self.timeout_wait_for_signal) if self.scan_info.msg.scan_name != "nidaq_continuous_scan": status = self.on_kickoff() diff --git a/debye_bec/devices/pilatus/pilatus.py b/debye_bec/devices/pilatus/pilatus.py index 3db1c4c..2a7668f 100644 --- a/debye_bec/devices/pilatus/pilatus.py +++ b/debye_bec/devices/pilatus/pilatus.py @@ -6,13 +6,13 @@ import enum import threading import time import traceback -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Tuple import numpy as np from bec_lib.file_utils import get_full_path from bec_lib.logger import bec_logger from ophyd import Component as Cpt -from ophyd import EpicsSignal, Kind +from ophyd import EpicsSignal, EpicsSignalRO, Kind from ophyd.areadetector.cam import ADBase, PilatusDetectorCam from ophyd.areadetector.plugins import HDF5Plugin_V22 as HDF5Plugin from ophyd.areadetector.plugins import ImagePlugin_V22 as ImagePlugin @@ -145,6 +145,19 @@ class Pilatus(PSIDeviceBase, ADBase): # USER_ACCESS = ["start_live_mode", "stop_live_mode"] + cam_gain_menu_string = Cpt(EpicsSignalRO, suffix='cam1:GainMenu', string=True) + + _default_configuration_attrs = [ + 'cam.threshold_energy', + 'cam.threshold_auto_apply', + 'cam.gain_menu', + 'cam_gain_menu_string', + 'cam.pixel_cut_off', + 'cam.acquire_time', + 'cam.num_exposures', + 'cam.model', + ] + cam = Cpt(PilatusDetectorCam, "cam1:") hdf = Cpt(HDF5Plugin, "HDF1:") image1 = Cpt(ImagePlugin, "image1:") @@ -203,22 +216,11 @@ class Pilatus(PSIDeviceBase, ADBase): PreviewSignal, name="preview", ndim=2, - num_rotation_90=0, # Check this + num_rotation_90=3, doc="Preview signal for the Pilatus Detector", ) file_event = Cpt(FileEventSignal, name="file_event") - @property - def baseline_signals(self): - """Define baseline signals""" - return [ - self.cam.acquire_time, - self.cam.num_exposures, - self.cam.threshold_energy, - self.cam.gain_menu, - self.cam.pixel_cut_off, - ] - def __init__( self, *, @@ -366,68 +368,51 @@ class Pilatus(PSIDeviceBase, ADBase): status = status_acquire & status_writing & status_cam_server return status - def _calculate_trigger(self, scan_msg: ScanStatusMessage): + def _calculate_trigger(self, scan_msg: ScanStatusMessage) -> Tuple[float, float]: self._update_scan_parameter() total_osc = 0 + calc_duration = 0 total_trig_lo = 0 total_trig_hi = 0 - calc_duration = 0 - n_trig_lo = 1 - n_trig_hi = 1 - init_lo = 1 - init_hi = 1 - lo_done = 0 - hi_done = 0 - if not self.scan_parameter.break_enable_low: - lo_done = 1 - if not self.scan_parameter.break_enable_high: - hi_done = 1 - start_time = time.time() - while True: - # TODO, we should not use infinite loops, for now let's add the escape Timeout of 20s, but should eventually be reviewed. - if time.time() - start_time > 20: - raise RuntimeError( - f"Calculating the number of triggers for scan {scan_msg.scan_name} took more than 20 seconds, aborting." - ) + # Switching high/low is intended as angle is inverse to energy and settings in BEC are always in energy + loc_break_enable_low = self.scan_parameter.break_enable_high + loc_break_time_low = self.scan_parameter.break_time_high + loc_cycle_low = self.scan_parameter.cycle_high + loc_break_enable_high = self.scan_parameter.break_enable_low + loc_break_time_high = self.scan_parameter.break_time_low + loc_cycle_high = self.scan_parameter.cycle_low + + if not loc_break_enable_low: + loc_break_time_low = 0 + loc_cycle_low = 1 + if not loc_break_enable_high: + loc_break_time_high = 0 + loc_cycle_high = 1 + + total_osc = self.scan_parameter.scan_duration / ( + self.scan_parameter.scan_time + + loc_break_time_low / (2 * loc_cycle_low) + + loc_break_time_high / (2 * loc_cycle_high) + ) + total_osc = np.ceil(total_osc) + total_osc = total_osc + total_osc % 2 # round up to the next even number + + if loc_break_enable_low: + total_trig_lo = np.floor(total_osc / (2 * loc_cycle_low)) + if loc_break_enable_high: + total_trig_hi = np.floor(total_osc / (2 * loc_cycle_high)) + calc_duration = total_osc * self.scan_parameter.scan_time + total_trig_lo * loc_break_time_low + total_trig_hi * loc_break_time_high + + if calc_duration < self.scan_parameter.scan_duration: + # Due to inaccuracy in formula, this can happen, we then need to manually add two oscillations and recalculate the triggers total_osc = total_osc + 2 - calc_duration = calc_duration + 2 * self.scan_parameter.scan_time + if loc_break_enable_low: + total_trig_lo = np.floor(total_osc / (2 * loc_cycle_low)) + if loc_break_enable_high: + total_trig_hi = np.floor(total_osc / (2 * loc_cycle_high)) + calc_duration = total_osc * self.scan_parameter.scan_time + total_trig_lo * loc_break_time_low + total_trig_hi * loc_break_time_high - if self.scan_parameter.break_enable_low and n_trig_lo >= self.scan_parameter.cycle_low: - n_trig_lo = 1 - calc_duration = calc_duration + self.scan_parameter.break_time_low - if init_lo: - lo_done = 1 - init_lo = 0 - else: - n_trig_lo += 1 - - if ( - self.scan_parameter.break_enable_high - and n_trig_hi >= self.scan_parameter.cycle_high - ): - n_trig_hi = 1 - calc_duration = calc_duration + self.scan_parameter.break_time_high - if init_hi: - hi_done = 1 - init_hi = 0 - else: - n_trig_hi += 1 - - if lo_done and hi_done: - n = np.floor(self.scan_parameter.scan_duration / calc_duration) - total_osc = total_osc * n - if self.scan_parameter.break_enable_low: - total_trig_lo = n + 1 - if self.scan_parameter.break_enable_high: - total_trig_hi = n + 1 - calc_duration = calc_duration * n - lo_done = 0 - hi_done = 0 - - if calc_duration >= self.scan_parameter.scan_duration: - break - - return total_trig_lo + total_trig_hi + return total_trig_lo, total_trig_hi ######################################## # Beamline Specific Implementations # @@ -480,6 +465,14 @@ class Pilatus(PSIDeviceBase, ADBase): """ # self.stop_live_mode() # Make sure that live mode is stopped if scan runs + # If user has activated alignment mode on qt panel, switch back to multitrigger and stop acquisition + if self.cam.trigger_mode.get() != TRIGGERMODE.MULT_TRIGGER.value: + self.cam.trigger_mode.set(TRIGGERMODE.MULT_TRIGGER.value).wait(5) + if self.cam.acquire.get() == ACQUIREMODE.ACQUIRING.value: + self.cam.acquire.put(0) + status_cam = CompareStatus(self.cam.acquire, ACQUIREMODE.DONE.value) + status_cam.wait(timeout=5) + scan_msg: ScanStatusMessage = self.scan_info.msg if scan_msg.scan_name in self.xas_xrd_scan_names: self._update_scan_parameter() diff --git a/debye_bec/devices/pilatus_curtain.py b/debye_bec/devices/pilatus_curtain.py index d129673..b607ae9 100644 --- a/debye_bec/devices/pilatus_curtain.py +++ b/debye_bec/devices/pilatus_curtain.py @@ -69,11 +69,11 @@ class PilatusCurtain(PSIDeviceBase): def on_unstage(self) -> DeviceStatus | None: """Called while unstaging the device.""" - return self.close() + # return self.close() def on_stop(self) -> DeviceStatus | None: """Called when the device is stopped.""" - return self.close() + # return self.close() def open(self) -> DeviceStatus | None: """Open the cover""" diff --git a/debye_bec/devices/reffoilchanger.py b/debye_bec/devices/reffoilchanger.py index d22c997..b59970d 100644 --- a/debye_bec/devices/reffoilchanger.py +++ b/debye_bec/devices/reffoilchanger.py @@ -52,9 +52,15 @@ class Reffoilchanger(PSIDeviceBase): status = Cpt( EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status" ) + status_string = Cpt( + EpicsSignal, suffix="ES2-REF:SELN-FilterState-ENUM_RBV", kind="config", doc="Status", string=True + ) op_mode = Cpt( EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status" ) + op_mode_string = Cpt( + EpicsSignalWithRBV, suffix="ES2-REF:SELN-OpMode-ENUM", kind="config", doc="Status", string=True + ) ref_set = Cpt(EpicsSignal, suffix="ES2-REF:SELN-SET", kind="config", doc="Requested reference") ref_rb = Cpt( EpicsSignalRO, suffix="ES2-REF:SELN-RB", kind="config", doc="Currently set reference" diff --git a/pyproject.toml b/pyproject.toml index 10cab05..f34c291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering", ] -dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices"] +dependencies = ["numpy", "scipy", "bec_lib", "h5py", "ophyd_devices", "opencv-python==4.11.0.86"] [project.optional-dependencies] dev = [