diff --git a/csaxs_bec/device_configs/bl_endstation.yaml b/csaxs_bec/device_configs/bl_endstation.yaml index 79f286d..ffc291b 100644 --- a/csaxs_bec/device_configs/bl_endstation.yaml +++ b/csaxs_bec/device_configs/bl_endstation.yaml @@ -37,6 +37,21 @@ mcs: readoutPriority: monitored softwareTrigger: false + + +########################################################################## +########################### FAST SHUTTER ################################# +########################################################################## + +fsh: + description: Fast shutter manual control and readback + deviceClass: csaxs_bec.devices.epics.fast_shutter.cSAXSFastEpicsShutter + deviceConfig: + prefix: 'X12SA-ES1-TTL:' + onFailure: raise + enabled: true + readoutPriority: monitored + ########################################################################## ######################## SMARACT STAGES ################################## ########################################################################## @@ -60,6 +75,7 @@ xbpm3x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -22.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -82,6 +98,7 @@ xbpm3y: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -2 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -104,6 +121,7 @@ sl3trxi: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -5.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -126,6 +144,7 @@ sl3trxo: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 6 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -148,6 +167,7 @@ sl3trxb: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -5.8 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -170,6 +190,7 @@ sl3trxt: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 5.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -192,6 +213,7 @@ fast_shutter_n1_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -7 in: 0 @@ -215,6 +237,7 @@ fast_shutter_o1_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -15.8 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -237,6 +260,7 @@ fast_shutter_o2_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -15.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -259,6 +283,7 @@ filter_array_1_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 25 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -281,6 +306,7 @@ filter_array_2_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 25.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -303,6 +329,7 @@ filter_array_3_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 25.8 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -325,6 +352,7 @@ filter_array_4_x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 25 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -347,6 +375,7 @@ sl4trxi: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -5.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -369,6 +398,7 @@ sl4trxo: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 6 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -391,6 +421,7 @@ sl4trxb: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -5.8 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -413,6 +444,7 @@ sl4trxt: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 5.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -425,7 +457,7 @@ sl5trxi: deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor deviceConfig: axis_Id: C - host: x12sa-eb-smaract-mcs-02.psi.ch + host: x12sa-eb-smaract-mcs-05.psi.ch limits: - -200 - 200 @@ -437,6 +469,7 @@ sl5trxi: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -6 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -447,7 +480,7 @@ sl5trxo: deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor deviceConfig: axis_Id: D - host: x12sa-eb-smaract-mcs-02.psi.ch + host: x12sa-eb-smaract-mcs-05.psi.ch limits: - -200 - 200 @@ -459,6 +492,7 @@ sl5trxo: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 5.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -469,7 +503,7 @@ sl5trxb: deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor deviceConfig: axis_Id: E - host: x12sa-eb-smaract-mcs-02.psi.ch + host: x12sa-eb-smaract-mcs-05.psi.ch limits: - -200 - 200 @@ -481,6 +515,7 @@ sl5trxb: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -5.5 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -491,7 +526,7 @@ sl5trxt: deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor deviceConfig: axis_Id: F - host: x12sa-eb-smaract-mcs-02.psi.ch + host: x12sa-eb-smaract-mcs-05.psi.ch limits: - -200 - 200 @@ -503,6 +538,7 @@ sl5trxt: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 6 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -513,7 +549,7 @@ xbimtrx: deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor deviceConfig: axis_Id: A - host: x12sa-eb-smaract-mcs-02.psi.ch + host: x12sa-eb-smaract-mcs-05.psi.ch limits: - -200 - 200 @@ -525,6 +561,7 @@ xbimtrx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: -14.7 # bl_smar_stage to use csaxs reference method. assign number according to axis channel @@ -535,7 +572,7 @@ xbimtry: deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor deviceConfig: axis_Id: B - host: x12sa-eb-smaract-mcs-02.psi.ch + host: x12sa-eb-smaract-mcs-05.psi.ch limits: - -200 - 200 @@ -547,6 +584,7 @@ xbimtry: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: init_position: 0 # bl_smar_stage to use csaxs reference method. assign number according to axis channel diff --git a/csaxs_bec/device_configs/bl_optics_hutch.yaml b/csaxs_bec/device_configs/bl_optics_hutch.yaml index 7ce2815..5dcf9f9 100644 --- a/csaxs_bec/device_configs/bl_optics_hutch.yaml +++ b/csaxs_bec/device_configs/bl_optics_hutch.yaml @@ -89,6 +89,7 @@ xbpm2x: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: # bl_smar_stage to use csaxs reference method. assign number according to axis channel bl_smar_stage: 0 @@ -108,6 +109,7 @@ xbpm2y: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: # bl_smar_stage to use csaxs reference method. assign number according to axis channel bl_smar_stage: 1 @@ -127,6 +129,7 @@ cu_foilx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: # bl_smar_stage to use csaxs reference method. assign number according to axis channel bl_smar_stage: 2 @@ -146,6 +149,7 @@ scinx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: # bl_smar_stage to use csaxs reference method. assign number according to axis channel bl_smar_stage: 3 diff --git a/csaxs_bec/device_configs/ptycho_flomni.yaml b/csaxs_bec/device_configs/ptycho_flomni.yaml index e2a20c5..fd2bda1 100644 --- a/csaxs_bec/device_configs/ptycho_flomni.yaml +++ b/csaxs_bec/device_configs/ptycho_flomni.yaml @@ -17,6 +17,7 @@ feyex: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -16.267 out: -1 @@ -35,6 +36,7 @@ feyey: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -10.467 fheater: @@ -52,6 +54,7 @@ fheater: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 foptx: description: Optics X deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor @@ -67,6 +70,7 @@ foptx: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -13.761 fopty: @@ -84,6 +88,7 @@ fopty: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0.552 out: 0.752 @@ -102,6 +107,7 @@ foptz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 23 fsamroy: @@ -119,6 +125,7 @@ fsamroy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 fsamx: description: Sample coarse X deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor @@ -134,6 +141,7 @@ fsamx: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -1.1 fsamy: @@ -151,6 +159,7 @@ fsamy: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 2.75 ftracky: @@ -168,6 +177,7 @@ ftracky: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 ftrackz: description: Laser Tracker coarse Z deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor @@ -183,6 +193,7 @@ ftrackz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 ftransx: description: Sample transer X deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor @@ -198,6 +209,7 @@ ftransx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 ftransy: description: Sample transer Y deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor @@ -213,6 +225,7 @@ ftransy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: sensor_voltage: -2.4 ftransz: @@ -230,6 +243,7 @@ ftransz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 ftray: description: Sample transfer tray deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor @@ -245,6 +259,7 @@ ftray: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 ############################################################ @@ -279,6 +294,7 @@ fosax: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 9.124 out: 5.3 @@ -297,6 +313,7 @@ fosay: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0.367 fosaz: @@ -314,6 +331,7 @@ fosaz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 8.5 out: 6 @@ -334,6 +352,7 @@ rtx: onFailure: buffer readOnly: false readoutPriority: on_request + connectionTimeout: 20 userParameter: low_signal: 10000 min_signal: 9000 @@ -350,6 +369,7 @@ rty: onFailure: buffer readOnly: false readoutPriority: on_request + connectionTimeout: 20 userParameter: tomo_additional_offsety: 0 rtz: @@ -364,6 +384,7 @@ rtz: onFailure: buffer readOnly: false readoutPriority: on_request + connectionTimeout: 20 ############################################################ ####################### Cameras ############################ @@ -408,20 +429,20 @@ cam_xeye: readOnly: false readoutPriority: async -cam_ids_rgb: - description: Camera flOMNI Xray eye ID203 - deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera - deviceConfig: - camera_id: 203 - bits_per_pixel: 24 - num_rotation_90: 3 - transpose: false - force_monochrome: true - m_n_colormode: 1 - enabled: true - onFailure: buffer - readOnly: false - readoutPriority: async +# cam_ids_rgb: +# description: Camera flOMNI Xray eye ID203 +# deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera +# deviceConfig: +# camera_id: 203 +# bits_per_pixel: 24 +# num_rotation_90: 3 +# transpose: false +# force_monochrome: true +# m_n_colormode: 1 +# enabled: true +# onFailure: buffer +# readOnly: false +# readoutPriority: async # ############################################################ @@ -439,8 +460,8 @@ flomni_temphum: # ########## OMNY / flOMNI / LamNI fast shutter ############## # ############################################################ omnyfsh: - description: omnyfsh connects to read fast shutter at X12 if in that network - deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastEpicsShutter + description: omnyfsh connects to fast shutter at X12 if device fsh exists + deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastShutter deviceConfig: {} enabled: true onFailure: buffer diff --git a/csaxs_bec/device_configs/ptycho_lamni.yaml b/csaxs_bec/device_configs/ptycho_lamni.yaml index cb9bcd1..cd3c250 100644 --- a/csaxs_bec/device_configs/ptycho_lamni.yaml +++ b/csaxs_bec/device_configs/ptycho_lamni.yaml @@ -19,6 +19,7 @@ leyex: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 14.117 leyey: @@ -38,6 +39,7 @@ leyey: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 48.069 out: 0.5 @@ -58,6 +60,7 @@ loptx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -0.244 out: -0.699 @@ -78,6 +81,7 @@ lopty: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 3.724 out: 3.53 @@ -98,6 +102,7 @@ loptz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 lsamrot: description: Sample rotation deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor @@ -115,6 +120,7 @@ lsamrot: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 lsamx: description: Sample coarse X deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor @@ -132,6 +138,7 @@ lsamx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: center: 8.768 lsamy: @@ -151,6 +158,7 @@ lsamy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: center: 10.041 @@ -176,6 +184,7 @@ losax: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -1.442 losay: @@ -195,6 +204,7 @@ losay: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -0.171 out: 3.8 @@ -215,6 +225,7 @@ losaz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -1 out: -3 @@ -238,6 +249,7 @@ rtx: deviceTags: - lamni readoutPriority: baseline + connectionTimeout: 20 enabled: true readOnly: False rty: @@ -255,6 +267,7 @@ rty: deviceTags: - lamni readoutPriority: baseline + connectionTimeout: 20 enabled: true readOnly: False diff --git a/csaxs_bec/device_configs/ptycho_omny.yaml b/csaxs_bec/device_configs/ptycho_omny.yaml index edce1a7..cd894a1 100755 --- a/csaxs_bec/device_configs/ptycho_omny.yaml +++ b/csaxs_bec/device_configs/ptycho_omny.yaml @@ -69,6 +69,7 @@ rtx: onFailure: buffer readOnly: false readoutPriority: on_request + connectionTimeout: 20 userParameter: low_signal: 8500 min_signal: 8000 @@ -84,6 +85,7 @@ rty: onFailure: buffer readOnly: false readoutPriority: on_request + connectionTimeout: 20 userParameter: tomo_additional_offsety: 0 rtz: @@ -98,6 +100,7 @@ rtz: onFailure: buffer readOnly: false readoutPriority: on_request + connectionTimeout: 20 # ############################################################ # ##################### OMNY samples ######################### @@ -165,6 +168,7 @@ ofzpx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -0.4317 ofzpy: @@ -184,6 +188,7 @@ ofzpy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0.7944 out: 0.6377 @@ -204,6 +209,7 @@ ofzpz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 otransx: @@ -223,6 +229,7 @@ otransx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 otransy: @@ -242,6 +249,7 @@ otransy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: up_position: -1.2 gripper_sensorvoltagetarget: -2.30 @@ -262,6 +270,7 @@ otransz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 osamx: @@ -281,6 +290,7 @@ osamx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: -0.1 osamz: @@ -300,6 +310,7 @@ osamz: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 oosay: @@ -319,6 +330,7 @@ oosay: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: near_field_in: 0.531 far_field_in: 0.4122 @@ -339,6 +351,7 @@ oosax: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: near_field_in: 3.2044 far_field_in: 3.022 @@ -359,6 +372,7 @@ oosaz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: near_field_in: -0.4452 far_field_in: 6.5 @@ -379,6 +393,7 @@ oparkz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 oshuttleopen: @@ -398,6 +413,7 @@ oshuttleopen: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 oshuttlealign: @@ -417,6 +433,7 @@ oshuttlealign: onFailure: buffer readOnly: true readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 osamy: @@ -436,6 +453,7 @@ osamy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 otracky: @@ -455,6 +473,7 @@ otracky: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: start_pos: -4.3431 osamroy: @@ -474,6 +493,7 @@ osamroy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: in: 0 otrackz: @@ -493,6 +513,7 @@ otrackz: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: start_pos: -0.6948 oeyex: @@ -512,6 +533,7 @@ oeyex: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: xray_in: -45.7394 oeyez: @@ -531,6 +553,7 @@ oeyez: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: xray_in: -2 oeyey: @@ -550,6 +573,7 @@ oeyey: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: xray_in: 0.0229 @@ -572,6 +596,7 @@ ocsx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: nothing: 0 ocsy: @@ -589,6 +614,7 @@ ocsy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: nothing: 0 oshield: @@ -606,5 +632,6 @@ oshield: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 userParameter: nothing: 0 diff --git a/csaxs_bec/device_configs/user_template.yaml b/csaxs_bec/device_configs/user_template.yaml index 2cf6beb..689b53a 100644 --- a/csaxs_bec/device_configs/user_template.yaml +++ b/csaxs_bec/device_configs/user_template.yaml @@ -64,6 +64,7 @@ npx: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 deviceTags: - npoint npy: @@ -81,5 +82,6 @@ npy: onFailure: buffer readOnly: false readoutPriority: baseline + connectionTimeout: 20 deviceTags: - npoint \ No newline at end of file diff --git a/csaxs_bec/devices/epics/delay_generator_csaxs/README.md b/csaxs_bec/devices/epics/delay_generator_csaxs/README.md index 439d920..acc9c4f 100644 --- a/csaxs_bec/devices/epics/delay_generator_csaxs/README.md +++ b/csaxs_bec/devices/epics/delay_generator_csaxs/README.md @@ -37,7 +37,7 @@ to interrupt and ongoing sequence if needed. - a = t0 + 2ms (2ms delay to allow the shutter to open) - b = a + 1us (short pulse) - c = t0 -- d = a + exp_time * burst_count + 1ms (to allow the shutter to close) +- d = a + exp_time * burst_count - e = d - f = e + 1us (short pulse to OR gate for MCS triggering) diff --git a/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py b/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py index 29a0623..79e1bcd 100644 --- a/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py +++ b/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_1.py @@ -24,7 +24,7 @@ DELAY CHANNELS: - a = t0 + 2ms (2ms delay to allow the shutter to open) - b = a + 1us (short pulse) - c = t0 -- d = a + exp_time * burst_count + 1ms (to allow the shutter to close) +- d = a + exp_time * burst_count - e = d - f = e + 1us (short pulse to OR gate for MCS triggering) """ @@ -37,7 +37,9 @@ import traceback from typing import TYPE_CHECKING from bec_lib.logger import bec_logger -from ophyd_devices import CompareStatus, DeviceStatus, TransitionStatus +from ophyd import Component as Cpt +from ophyd import EpicsSignalRO, Kind +from ophyd_devices import CompareStatus, DeviceStatus, StatusBase, TransitionStatus from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import ( @@ -69,7 +71,7 @@ logger = bec_logger.logger # This can be adapted as needed, or fine-tuned per channel. On every reload of the # device configuration in BEC, these values will be set into the DDG1 device. _DEFAULT_CHANNEL_CONFIG: ChannelConfig = { - "amplitude": 5.0, + "amplitude": 4.5, "offset": 0.0, "polarity": OUTPUTPOLARITY.POSITIVE, "mode": "ttl", @@ -131,6 +133,27 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): device_manager (DeviceManagerBase | None, optional): Device manager. Defaults to None. """ + USER_ACCESS = ["keep_shutter_open_during_scan", "set_trigger"] + + # TODO Consider using the 'fsh' device instead. + fast_shutter_readback = Cpt( + EpicsSignalRO, + read_pv="X12SA-ES1-TTL:INP_01", + add_prefix=("",), # Add this to prevent the prefix to be added to the signal + kind=Kind.omitted, + auto_monitor=True, + ) + # The shutter control PV can indicate if the shutter is requested to be kept open. If that + # is the case, we can not use the signal shutter_readback signal to check if the delay cycle + # finishes but have to use the polling of the event status register to check if the burst finished. + fast_shutter_control = Cpt( + EpicsSignalRO, + read_pv="X12SA-ES1-TTL:OUT_01", + add_prefix=("",), # Add this to prevent the prefix to be added to the signal + kind=Kind.omitted, + auto_monitor=True, + ) + def __init__( self, name: str, @@ -142,6 +165,7 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): super().__init__( name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs ) + self._shutter_to_open_delay = 2e-3 self.device_manager = device_manager self._poll_thread = threading.Thread(target=self._poll_event_status, daemon=True) self._poll_thread_run_event = threading.Event() @@ -192,6 +216,21 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): self.burst_delay.put(0) self.burst_count.put(1) + def keep_shutter_open_during_scan(self, open: True) -> None: + """ + Method to configure the delay generator for keeping the shutter open during a scans. + This means that the additional delay to open the shutter needs to be removed (2e-3) + from the timing of the signals. + + Args: + open (bool): If True, the shutter will be kept open during the scan. + If False, the shutter will be opened and closed for each trigger cycle. + """ + if open is True: + self._shutter_to_open_delay = 0 + else: + self._shutter_to_open_delay = 2e-3 + def on_stage(self) -> None: """ @@ -230,6 +269,18 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): if self.burst_count.get() != 1: self.burst_count.put(1) + ##################################### + ## Setup trigger source if needed ### + ##################################### + + # NOTE Some scans may change the trigger source to an external trigger, + # so we will make sure that the default trigger source is set for the DDG1 + # before each scan. If a scan requires a different trigger source, i.e. + # external triggers then the scan should implement this change after the + # on_stage method was called. + if self.trigger_source.get() != DEFAULT_TRIGGER_SOURCE: + self.set_trigger(DEFAULT_TRIGGER_SOURCE) + ######################################### ### Setup timing for burst and delays ### ######################################### @@ -239,27 +290,34 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): # Burst Period DDG1 # Set burst_period to shutter width - # c/t0 + 2ms + exp_time * burst_count + 1ms - shutter_width = 2e-3 + exp_time * frames_per_trigger + 1e-3 + # c/t0 + self._shutter_to_open_delay + exp_time * burst_count + shutter_width = ( + self._shutter_to_open_delay + exp_time * frames_per_trigger + ) # Shutter starts closing at end of exposure if self.burst_period.get() != shutter_width: self.burst_period.put(shutter_width) # Trigger DDG2 # a = t0 + 2ms, b = a + 1us # a has reference to t0, b has reference to a - # Add delay of 2ms to allow shutter to open - self.set_delay_pairs(channel="ab", delay=2e-3, width=1e-6) + # Add delay of self._shutter_to_open_delay to allow shutter to open + self.set_delay_pairs(channel="ab", delay=self._shutter_to_open_delay, width=1e-6) # Trigger shutter - # d = c/t0 + 2ms + exp_time * burst_count + 1ms + # d = c/t0 + self._shutter_to_open_delay + exp_time * burst_count + 1ms # c has reference to t0, d has reference to c - # Shutter opens without delay at t0, closes after exp_time * burst_count + 3ms (2ms open, 1ms close) + # Shutter opens without delay at t0, closes after exp_time * burst_count + 2ms (self._shutter_to_open_delay) self.set_delay_pairs(channel="cd", delay=0, width=shutter_width) + self.set_delay_pairs(channel="gh", delay=self._shutter_to_open_delay, width=(shutter_width-self._shutter_to_open_delay)) + # Trigger extra pulse for MCS OR gate # f = e + 1us # e has refernce to d, f has reference to e - self.set_delay_pairs(channel="ef", delay=0, width=1e-6) + if self.scan_info.msg.scan_type == "fly": + self.set_delay_pairs(channel="ef", delay=0, width=0) + else: + self.set_delay_pairs(channel="ef", delay=0, width=1e-6) # NOTE Add additional sleep to make sure that the IOC and DDG HW process the values properly # This value has been choosen empirically after testing with the HW. It's @@ -431,7 +489,19 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): def on_trigger(self) -> DeviceStatus: """ - This method is called from BEC as a software trigger. + This method is called from BEC as a software trigger. Here the logic is as follows: + + We first check if the trigger_source is set to SINGLE_SHOT. Only then will we received, + otherwise we return a status object directly as the DDG is triggered by an external + source which will have to implement its own logic to wait for trigger signals to + be received. + + I SINGLE_SHOT, the implementation here will send a software trigger. Now there are + two options to wait for the trigger (burst) cycle to be done. One is to rely on the + signal of the "mcs" card if it is present. However, this is only possible if the + scan_type is not "fly" as in fly scans the ef channel is not triggered to send the last + pulse to the card (but the card is finishing its acquisition in complete itself). Then + we rely on the polling of the event status register to check if the burst cycle is done. It follows a specific procedure to ensure that the DDG1 and MCS card are properly handled on a trigger event. The established logic is as follows: @@ -450,33 +520,46 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS): - Return the status object to BEC which will automatically resolve once the status register has the END_OF_BURST bit set. The callback of the status object will also stop the polling loop. """ + overall_start = time.time() self._stop_polling() - self._poll_thread_poll_loop_done.wait(timeout=1) - # NOTE: This sleep is important to ensure that the HW is ready to process new commands. - # It has been empirically determined after long testing that this improves stability. - time.sleep(0.02) + + # NOTE If the trigger source is not SINGLE_SHOT, the DDG is triggered by an external source + # thus we can not expect that trigger signals are meant to be awaited for. In this case, + # we can directly return. + if self.trigger_source.get() != TRIGGERSOURCE.SINGLE_SHOT.value: + status = StatusBase(obj=self) + status.set_finished() + return status # NOTE If the MCS card is present in the current session of BEC, # we prepare the card for the next trigger. The procedure is implemented - # in the '_prepare_mcs_on_trigger' method. - # Prepare the MCS card for the next software trigger + # in the '_prepare_mcs_on_trigger' method. We will also use the mcs card + # to indicate when the burst cycle is done. If no mcs card is available + # the fallback is to use the polling of the DDG mcs = self.device_manager.devices.get("mcs", None) - if mcs is None or mcs.enabled is False: - logger.info("Did not find mcs card with name 'mcs' in current session") + if mcs is None or mcs.enabled is False or self.scan_info.msg.scan_type == "fly": + self._poll_thread_poll_loop_done.wait(timeout=1) + logger.warning("Did not find mcs card with name 'mcs' in current session") + time.sleep(0.02) + # Shutter is kept open, we can only rely on the event status register + status = self._prepare_trigger_status_event() + # Start polling thread again to monitor event status + self._start_polling() else: + start_time = time.time() + logger.debug(f"Preparing mcs card ") status_mcs = self._prepare_mcs_on_trigger(mcs) # NOTE Timeout of 3s should be plenty, any longer wait should checked. If this happens to crash # an acquisition regularly with a WaitTimeoutError, the timeout can be increased but it should # be investigated why the EPICS interface is slow to respond. status_mcs.wait(timeout=3) + status = TransitionStatus(mcs.acquiring, [ACQUIRING.ACQUIRING, ACQUIRING.DONE]) + logger.debug(f"Finished preparing mcs card {time.time()-start_time}") - # Prepare StatusBitsCompareStatus to resolve once the END_OF_BURST bit was set. - status = self._prepare_trigger_status_event() - - # Start polling thread again to monitor event status - self._start_polling() - # Trigger the DDG1 + # Send trigger self.trigger_shot.put(1, use_complete=True) + self.cancel_on_stop(status) + logger.info(f"Configured ddg in {time.time()-overall_start}") return status def on_stop(self) -> None: diff --git a/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_2.py b/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_2.py index bf781f3..e9ee1da 100644 --- a/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_2.py +++ b/csaxs_bec/devices/epics/delay_generator_csaxs/ddg_2.py @@ -37,6 +37,7 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import ChannelConfig, DelayGeneratorCSAXS, LiteralChannels, + BURSTCONFIG, ) logger = bec_logger.logger @@ -47,7 +48,7 @@ logger = bec_logger.logger # NOTE Default channel configuration for the DDG2 delay generator channels _DEFAULT_CHANNEL_CONFIG: ChannelConfig = { - "amplitude": 5.0, + "amplitude": 4.5, "offset": 0.0, "polarity": OUTPUTPOLARITY.POSITIVE, "mode": "ttl", @@ -134,6 +135,9 @@ class DDG2(PSIDeviceBase, DelayGeneratorCSAXS): self.set_trigger(DEFAULT_TRIGGER_SOURCE) self.set_references_for_channels(DEFAULT_REFERENCES) + # Set burst config + self.burst_config.put(BURSTCONFIG.FIRST_CYCLE.value) + def on_stage(self) -> DeviceStatus | StatusBase | None: """ diff --git a/csaxs_bec/devices/epics/delay_generator_csaxs/delay_generator_csaxs.py b/csaxs_bec/devices/epics/delay_generator_csaxs/delay_generator_csaxs.py index d0f1c1d..4789f5c 100644 --- a/csaxs_bec/devices/epics/delay_generator_csaxs/delay_generator_csaxs.py +++ b/csaxs_bec/devices/epics/delay_generator_csaxs/delay_generator_csaxs.py @@ -488,6 +488,7 @@ class DelayGeneratorCSAXS(Device): name="trigger_source", kind=Kind.omitted, doc="Trigger Source for the DDG, options in TRIGGERSOURCE", + auto_monitor=True, ) trigger_level = Cpt( EpicsSignal, diff --git a/csaxs_bec/devices/epics/fast_shutter.py b/csaxs_bec/devices/epics/fast_shutter.py new file mode 100644 index 0000000..134d17c --- /dev/null +++ b/csaxs_bec/devices/epics/fast_shutter.py @@ -0,0 +1,67 @@ +""" +Shutter device for the cSAXS beamline with 2 PVs. One is connected to a +signal can be set to control the shutter signal, and the other is a readback signal +that can be monitored to check the shutter status as it may be controlled directly by +the delay generator.""" + +from ophyd import Component as Cpt +from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind + + +class cSAXSFastEpicsShutter(Device): + """ + Fast EPICS shutter with automatic PV selection based on host subnet. IOC prefix is 'X12SA-ES1-TTL:' + """ + + USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "fshstatus_readback", "help"] + SUB_VALUE = "value" + _default_sub = SUB_VALUE + + # PVs + shutter = Cpt(EpicsSignal, "OUT_01", kind=Kind.normal, auto_monitor=True) + shutter_readback = Cpt(EpicsSignalRO, "INP_01", kind=Kind.normal, auto_monitor=True) + + # ----------------------------------------------------- + # User-facing shutter control functions + # ----------------------------------------------------- + + # pylint: disable=protetced-access + def fshopen(self) -> None: + """Open the fast shutter.""" + self.shutter.set(1).wait(timeout=self.shutter._timeout) # 2s default for ES + + # pylint: disable=protetced-access + def fshclose(self) -> None: + """Close the fast shutter.""" + self.shutter.set(0).wait(timeout=self.shutter._timeout) # 2s default for ES + + def fshstatus(self) -> int: + """Return the fast shutter control status (0=closed, 1=open).""" + return self.shutter.get() # Ensure we have the latest value from EPICS + + def fshstatus_readback(self) -> int: + """Return the fast shutter status (0=closed, 1=open).""" + return self.shutter_readback.get() # Ensure we have the latest value from EPICS + + def fshinfo(self) -> None: + """Print information about which EPICS PV channel is being used.""" + pvname = self.shutter.pvname + shutter_readback_pvname = self.shutter_readback.pvname + print( + f"Fast shutter connected to EPICS channel: {pvname} with shutter readback: {shutter_readback_pvname}" + ) + + def stop(self, *, success: bool = False) -> None: + """Stop the shutter device. Make sure to close it.""" + self.shutter.put(0) + super().stop(success=success) + + def help(self): + """Display available user methods.""" + print("Available methods:") + for method in self.USER_ACCESS: + print(f" - {method}") + + +if __name__ == "__main__": + fsh = cSAXSFastEpicsShutter(name="fsh", prefix="X12SA-ES1-TTL:") diff --git a/csaxs_bec/devices/epics/mcs_card/mcs_card_csaxs.py b/csaxs_bec/devices/epics/mcs_card/mcs_card_csaxs.py index fdda685..9ceaf98 100644 --- a/csaxs_bec/devices/epics/mcs_card/mcs_card_csaxs.py +++ b/csaxs_bec/devices/epics/mcs_card/mcs_card_csaxs.py @@ -22,7 +22,13 @@ import numpy as np from bec_lib.logger import bec_logger from ophyd import Component as Cpt from ophyd import EpicsSignalRO, Kind -from ophyd_devices import AsyncMultiSignal, CompareStatus, ProgressSignal, StatusBase +from ophyd_devices import ( + AsyncMultiSignal, + CompareStatus, + ProgressSignal, + StatusBase, + TransitionStatus, +) from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase from csaxs_bec.devices.epics.mcs_card.mcs_card import ( @@ -255,6 +261,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): **kwargs: Additional keyword arguments from the subscription, including 'obj' (the EpicsSignalRO instance). """ with self._rlock: + logger.info(f"Received update on mcs card {self.name}") if self._omit_mca_callbacks.is_set(): return # Suppress callbacks when erasing all channels self._mca_counter_index += 1 @@ -286,7 +293,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): ) # Once we have received all channels, push data to BEC and reset for next accumulation - logger.debug( + logger.info( f"Received update for {attr_name}, index {self._mca_counter_index}/{self.NUM_MCA_CHANNELS}" ) if len(self._current_data) == self.NUM_MCA_CHANNELS: @@ -363,7 +370,10 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): self._num_total_triggers = triggers * num_points self._acquisition_group = "monitored" if triggers == 1 else "burst_group" self.preset_real.set(0).wait(timeout=self._pv_timeout) - self.num_use_all.set(triggers).wait(timeout=self._pv_timeout) + if self.scan_info.msg.scan_type == "step": + self.num_use_all.set(triggers).wait(timeout=self._pv_timeout) + elif self.scan_info.msg.scan_type == "fly": + self.num_use_all.set(self._num_total_triggers).wait(timeout=self._pv_timeout) # Clear any previous data, just to be sure with self._rlock: @@ -385,6 +395,21 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): self._omit_mca_callbacks.clear() logger.info(f"MCS Card {self.name} on_stage completed in {time.time() - start_time:.3f}s.") + # For a fly scan we need to start the mcs card ourselves + if self.scan_info.msg.scan_type == "fly": + self.erase_start.put(1) + + def on_prescan(self) -> None | StatusBase: + """ + This method is called after on_stage and before the scan starts. For the MCS card, we need to make sure + that the card is properly started for fly scans. For step scans, this will be handled by the DDG, + so no action is required here. + """ + if self.scan_info.msg.scan_type == "fly": + status_acquiring = CompareStatus(self.acquiring, ACQUIRING.ACQUIRING) + self.cancel_on_stop(status_acquiring) + return status_acquiring + return None def on_unstage(self) -> None: """ @@ -422,9 +447,16 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): hasattr(self.scan_info.msg, "num_points") and self.scan_info.msg.num_points is not None ): - if self._current_data_index == self.scan_info.msg.num_points: - for callback in self._scan_done_callbacks: - callback(exception=None) + if self.scan_info.msg.scan_type == "step": + if self._current_data_index == self.scan_info.msg.num_points: + for callback in self._scan_done_callbacks: + callback(exception=None) + else: + logger.info(f"Current data index is {self._current_data_index}") + if self._current_data_index >= 1: + for callback in self._scan_done_callbacks: + callback(exception=None) + time.sleep(0.02) # 20ms delay to avoid busy loop except Exception as exc: # pylint: disable=broad-except content = traceback.format_exc() @@ -452,7 +484,6 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): """Callback for status failure, the monitoring thread should be stopped.""" # NOTE Check for status.done and status.success is important to avoid if status.done: - self._start_monitor_async_data_emission.clear() # Stop monitoring def on_complete(self) -> CompareStatus: @@ -478,6 +509,13 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): monitoring thread is stopped properly. """ + # NOTE For fly scans with EXT/EN enabled triggering, the MCS card needs to receive an + # additional trigger at the end of the scan to advance the channel. This will ensure + # that the acquisition finishes on the card and that data is emitted to BEC. If the acquisition + # was already finished (i.e. normal step scan sends 1 extra pulse per burst cycle), this will + # not have any effect as the card will already be in DONE state and signal. + self.software_channel_advance.put(1) + # Prepare and register status callback for the async monitoring loop status_async_data = StatusBase(obj=self) self._scan_done_callbacks.append(partial(self._status_callback, status_async_data)) @@ -491,7 +529,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard): # Combine both statuses ret_status = status & status_async_data - # Handle external stop/cancel, and stop monitoring + # NOTE: Handle external stop/cancel, and stop monitoring ret_status.add_callback(self._status_failed_callback) self.cancel_on_stop(ret_status) return ret_status diff --git a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py index a4eaee4..e0ee6f2 100644 --- a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py @@ -498,6 +498,9 @@ class RtFlomniController(Controller): ) # while scan is running while mode > 0: + + #TODO here?: scan abortion if no progress in scan *raise error + # logger.info(f"Current scan position {current_position_in_scan} out of {number_of_positions_planned}") mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status() time.sleep(0.01) @@ -629,6 +632,8 @@ class RtFlomniMotor(Device, PositionerBase): SUB_CONNECTION_CHANGE = "connection_change" _default_sub = SUB_READBACK + connectionTimeout = 20 + def __init__( self, axis_Id, diff --git a/csaxs_bec/devices/omny/shutter.py b/csaxs_bec/devices/omny/shutter.py index 3ffd7e6..c318ebf 100644 --- a/csaxs_bec/devices/omny/shutter.py +++ b/csaxs_bec/devices/omny/shutter.py @@ -1,82 +1,87 @@ -import time -import socket +""" +Fast Shutter control for OMNY setup. If started with a config file in which the device_manager +has a 'fsh' device (cSAXSFastEpicsShutter), this device will be used as the shutter. +Otherwise, the device will create a dummy shutter device that will log warnings when shutter +methods are called, but will not raise exceptions. +""" + +from bec_lib.logger import bec_logger from ophyd import Component as Cpt -from ophyd import Device -from ophyd import EpicsSignal +from ophyd import Device, Signal +from ophyd_devices import PSIDeviceBase + +logger = bec_logger.logger -class OMNYFastEpicsShutterError(Exception): - pass - - -def _detect_host_pv(): - """Detect host subnet and return appropriate PV name.""" - try: - hostname = socket.gethostname() - local_ip = socket.gethostbyname(hostname) - if local_ip.startswith("129.129.122."): - return "X12SA-ES1-TTL:OUT_01" - else: - return "XOMNYI-XEYE-DUMMYSHUTTER:0" - except Exception as ex: - print(f"Warning: could not detect IP subnet ({ex}), using dummy shutter.") - return "XOMNYI-XEYE-DUMMYSHUTTER:0" - - -class OMNYFastEpicsShutter(Device): +class OMNYFastShutter(PSIDeviceBase, Device): """ - Fast EPICS shutter with automatic PV selection based on host subnet. + Fast Shutter control for OMNY setup. If started with at the beamline, it will expose + the shutter control methods (fshopen, fshclose, fshstatus, fshinfo) from the + cSAXSFastEpicsShutter device. The device is identified by the 'fsh' name in the device manager. + If the 'fsh' device is not found in the device manager, this device will create a dummy shutter + and log warnings when shutter methods are called, but will not raise exceptions. """ - USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "help"] + USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "help", "fshstatus_readback"] SUB_VALUE = "value" _default_sub = SUB_VALUE - # PV is detected dynamically at import time - shutter = Cpt(EpicsSignal, name="shutter", read_pv=_detect_host_pv(), auto_monitor=True) - - def __init__(self, prefix="", *, name, **kwargs): - super().__init__(prefix, name=name, **kwargs) - self.shutter.subscribe(self._emit_value) - - def _emit_value(self, **kwargs): - timestamp = kwargs.pop("timestamp", time.time()) - self.wait_for_connection() - self._run_subs(sub_type=self.SUB_VALUE, timestamp=timestamp, obj=self) + shutter = Cpt(Signal, name="shutter") # ----------------------------------------------------- # User-facing shutter control functions # ----------------------------------------------------- + # pylint: disable=invalid-name + def _check_if_cSAXS_shutter_exists_in_config(self) -> bool: + """ + Check on the device manager if the shutter device exists. + + Returns: + bool: True if the 'fsh' device exists in the device manager, False otherwise + """ + if self.device_manager.devices.get("fsh", None) is None: + logger.warning(f"Fast shutter device not found for {self.name}.") + return False + return True + def fshopen(self): """Open the fast shutter.""" - try: - self.shutter.put(1, wait=True) - except Exception as ex: - raise OMNYFastEpicsShutterError(f"Failed to open shutter: {ex}") + if self._check_if_cSAXS_shutter_exists_in_config(): + return self.device_manager.devices["fsh"].fshopen() + else: + self.shutter.put(1) def fshclose(self): """Close the fast shutter.""" - try: - self.shutter.put(0, wait=True) - except Exception as ex: - raise OMNYFastEpicsShutterError(f"Failed to close shutter: {ex}") + if self._check_if_cSAXS_shutter_exists_in_config(): + return self.device_manager.devices["fsh"].fshclose() + else: + self.shutter.put(0) def fshstatus(self): """Return the fast shutter status (0=closed, 1=open).""" - try: + if self._check_if_cSAXS_shutter_exists_in_config(): + return self.device_manager.devices["fsh"].fshstatus() + else: return self.shutter.get() - except Exception as ex: - raise OMNYFastEpicsShutterError(f"Failed to read shutter status: {ex}") def fshinfo(self): """Print information about which EPICS PV channel is being used.""" - pvname = self.shutter.pvname - print(f"Fast shutter connected to EPICS channel: {pvname}") - return pvname + if self._check_if_cSAXS_shutter_exists_in_config(): + return self.device_manager.devices["fsh"].fshinfo() + else: + print("Using dummy fast shutter device. No EPICS channel is connected.") def help(self): """Display available user methods.""" print("Available methods:") for method in self.USER_ACCESS: print(f" - {method}") + + def fshstatus_readback(self): + """Return the fast shutter status (0=closed, 1=open) from the readback signal.""" + if self._check_if_cSAXS_shutter_exists_in_config(): + return self.device_manager.devices["fsh"].fshstatus_readback() + else: + self.shutter.get() diff --git a/csaxs_bec/scans/flomni_fermat_scan.py b/csaxs_bec/scans/flomni_fermat_scan.py index 045ef1e..7402358 100644 --- a/csaxs_bec/scans/flomni_fermat_scan.py +++ b/csaxs_bec/scans/flomni_fermat_scan.py @@ -27,6 +27,7 @@ from bec_lib import bec_logger, messages from bec_lib.endpoints import MessageEndpoints from bec_server.scan_server.errors import ScanAbortion from bec_server.scan_server.scans import SyncFlyScanBase +from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE logger = bec_logger.logger @@ -73,14 +74,13 @@ class FlomniFermatScan(SyncFlyScanBase): >>> scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01) """ - super().__init__(parameter=parameter, **kwargs) + super().__init__(parameter=parameter, exp_time=exp_time, **kwargs) self.show_live_table = False self.axis = [] self.fovx = fovx self.fovy = fovy self.cenx = cenx self.ceny = ceny - self.exp_time = exp_time self.step = step self.zshift = zshift self.angle = angle @@ -151,6 +151,9 @@ class FlomniFermatScan(SyncFlyScanBase): yield from self.stubs.send_rpc_and_wait("rty", "set", self.positions[0][1]) def _prepare_setup_part2(self): + # Prepare DDG1 to use + yield from self.stubs.send_rpc_and_wait("ddg1", "set_trigger", TRIGGERSOURCE.EXT_RISING_EDGE.value) + if self.flomni_rotation_status: self.flomni_rotation_status.wait() @@ -307,6 +310,10 @@ class FlomniFermatScan(SyncFlyScanBase): logger.warning("No positions found to return to start") + def cleanup(self): + yield from self.stubs.send_rpc_and_wait("ddg1", "set_trigger", TRIGGERSOURCE.SINGLE_SHOT.value) + yield from super().cleanup() + def run(self): self.initialize() yield from self.read_scan_motors() diff --git a/tests/tests_devices/test_delay_generator_csaxs.py b/tests/tests_devices/test_delay_generator_csaxs.py index 18079c6..6ba4ecb 100644 --- a/tests/tests_devices/test_delay_generator_csaxs.py +++ b/tests/tests_devices/test_delay_generator_csaxs.py @@ -282,10 +282,11 @@ def test_ddg1_stage(mock_ddg1: DDG1): mock_ddg1.scan_info.msg.scan_parameters["exp_time"] = exp_time mock_ddg1.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger + mock_ddg1.fast_shutter_control._read_pv.mock_data = 0 # Simulate shutter control mock_ddg1.stage() - shutter_width = 2e-3 + exp_time * frames_per_trigger + 1e-3 + shutter_width = mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger assert np.isclose(mock_ddg1.burst_mode.get(), 1) # burst mode is enabled assert np.isclose(mock_ddg1.burst_delay.get(), 0) @@ -302,6 +303,25 @@ def test_ddg1_stage(mock_ddg1: DDG1): assert np.isclose(mock_ddg1.ef.width.get(), 1e-6) assert mock_ddg1.staged == ophyd.Staged.yes + mock_ddg1.unstage() + + # Test if shutter is kept open.. + mock_ddg1.fast_shutter_control._read_pv.mock_data = 1 # Simulate shutter control is kept open + # Test method + mock_ddg1.keep_shutter_open_during_scan(True) + shutter_width = mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger + assert np.isclose( + shutter_width, exp_time * frames_per_trigger + ) # Shutter to open delay is not added as shutter is kept open + # Simulate fly scan, so no extra trigger for MCS card. + mock_ddg1.scan_info.msg.scan_type = "fly" + mock_ddg1.stage() + # Shutter channel cd + assert np.isclose(mock_ddg1.cd.delay.get(), 0) + assert np.isclose(mock_ddg1.cd.width.get(), shutter_width) + # MCS channel ef or gate + assert np.isclose(mock_ddg1.ef.delay.get(), 0) + assert np.isclose(mock_ddg1.ef.width.get(), 0) # No triggering of MCS due to shutter fly scan def test_ddg1_on_trigger(mock_ddg1: DDG1): @@ -331,9 +351,28 @@ def test_ddg1_on_trigger(mock_ddg1: DDG1): ################################# with mock.patch.object(ddg, "_prepare_mcs_on_trigger") as mock_prepare_mcs: mock_prepare_mcs.return_value = ophyd.StatusBase(done=True, success=True) + # MCS card is present and enabled, should call prepare_mcs_on_trigger + # and the status should resolve once acuiring goes from 1 to 0. status = ddg.trigger() + assert status.done is False + mcs = ddg.device_manager.devices.get("mcs", None) + assert mcs is not None + mcs.acquiring._read_pv.mock_data = 1 # Simulate acquiring started + assert status.done is False + mcs.acquiring._read_pv.mock_data = 0 # Simulate acquiring stopped + status.wait(timeout=1) # Wait for the status to be done + assert status.done is True + assert status.success is True + mock_prepare_mcs.assert_called_once() + # Now we disable the mcs card, and trigger again. This should not call prepare_mcs_on_trigger + # and should fallback to polling the DDG for END_OF_BURST status bit. + + # Disable mcs card + mcs.enabled = False + status = ddg.trigger() # Check that the poll thread run event is set + # Careful in debugger, there is a timeout based on the exp_time + 5s default assert ddg._poll_thread_run_event.is_set() assert not ddg._poll_thread_poll_loop_done.is_set() diff --git a/tests/tests_devices/test_epics_devices.py b/tests/tests_devices/test_epics_devices.py new file mode 100644 index 0000000..327847b --- /dev/null +++ b/tests/tests_devices/test_epics_devices.py @@ -0,0 +1,37 @@ +"""Module to test epics devices.""" + +import pytest +from ophyd_devices.tests.utils import patched_device + +from csaxs_bec.devices.epics.fast_shutter import cSAXSFastEpicsShutter + + +@pytest.fixture +def fast_shutter_device(): + """Fixture to create a patched cSAXSFastEpicsShutter device for testing.""" + with patched_device(cSAXSFastEpicsShutter, name="fsh", prefix="X12SA-ES1-TTL:") as device: + yield device + + +def test_fast_shutter_methods(fast_shutter_device): + """Test the user-facing methods of the cSAXSFastEpicsShutter device.""" + assert fast_shutter_device.name == "fsh", "Device name should be 'fsh'" + assert fast_shutter_device.prefix == "X12SA-ES1-TTL:", "Device prefix is 'X12SA-ES1-TTL:'" + # Test fshopen and fshclose + fast_shutter_device.fshopen() + assert fast_shutter_device.shutter.get() == 1, "Shutter should be open (1) after fshopen()" + assert fast_shutter_device.fshstatus() == 1, "fshstatus should return 1 when shutter is open" + + fast_shutter_device.fshclose() + assert fast_shutter_device.shutter.get() == 0, "Shutter should be closed (0) after fshclose()" + assert fast_shutter_device.fshstatus() == 0, "fshstatus should return 0 when shutter is closed" + + # shutter_readback is connected to separate PV. + fast_shutter_device.shutter_readback._read_pv.mock_data = 1 # Simulate readback showing open + assert ( + fast_shutter_device.fshstatus_readback() == 1 + ), "fshstatus_readback should return 1 when shutter readback shows open" + fast_shutter_device.shutter_readback._read_pv.mock_data = 0 # Simulate readback showing closed + assert ( + fast_shutter_device.fshstatus_readback() == 0 + ), "fshstatus_readback should return 0 when shutter readback shows closed"