diff --git a/csaxs_bec/bec_ipython_client/plugins/cSAXS/filter_transmission.py b/csaxs_bec/bec_ipython_client/plugins/cSAXS/filter_transmission.py index a31d82c..3268f54 100644 --- a/csaxs_bec/bec_ipython_client/plugins/cSAXS/filter_transmission.py +++ b/csaxs_bec/bec_ipython_client/plugins/cSAXS/filter_transmission.py @@ -31,6 +31,8 @@ from importlib import resources # Resolve the filter_data/ folder via importlib.resources import csaxs_bec.bec_ipython_client.plugins.cSAXS.filter_transmission as ft_pkg +from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get + from bec_lib import bec_logger import builtins @@ -155,8 +157,6 @@ class cSAXSFilterTransmission: # ----------------------------- # Public API # ----------------------------- - - def fil_trans( self, transmission: Optional[float] = None, @@ -167,17 +167,27 @@ class cSAXSFilterTransmission: Set exposure-box filters to achieve a target transmission. If called without 'transmission', prints usage and current status. + + Safety: + - fil_trans(1) is always allowed. + - fil_trans(<1) is only allowed if: + - epics_get("X12SA-OP-DMM-EMLS-3010:THRU") == 1 (DMM translation THROUGH) + - epics_get("X12SA-OP-DMM-EMLS-3030:THRU") == 1 (DMM rotation THROUGH) + - epics_get("X12SA-OP-CCM1:ENERGY-GET") > 1 (CCM active, energy in keV) + Otherwise, prompt with default NO. """ + # --- No-arg usage helper --- if transmission is None: print("\nUsage example:") print(" csaxs.fil_trans(0.10, energy_kev=6.2)") - print(" First parameter is the transmission factor requested.\nIf energy is not specified it will be read from the monochromator (in the future)") + print(" First parameter is the transmission factor requested.") + print(" If energy is not specified it will be read from the CCM energy PV.") print("\nCurrent filter transmission:") self._fil_trans_report(energy_kev=energy_kev) return None - # --- Validation --- + # --- Validation of transmission --- try: transmission = float(transmission) except Exception: @@ -186,17 +196,64 @@ class cSAXSFilterTransmission: if not (0.0 < transmission <= 1.0): raise ValueError("Transmission must be between 0 and 1.") - # --- Energy handling --- + # ------------------------------------------------------- + # SAFETY CHECK (before any calculation/motion): + # Only allow fil_trans < 1 when DMM is in THROUGH (both) + # and CCM energy > 1 keV. fil_trans(1) is always allowed. + # ------------------------------------------------------- + if transmission < 1.0: + try: + dmm_trans = float(epics_get("X12SA-OP-DMM-EMLS-3010:THRU")) + except Exception: + dmm_trans = -1 + try: + dmm_rot = float(epics_get("X12SA-OP-DMM-EMLS-3030:THRU")) + except Exception: + dmm_rot = -1 + try: + ccm_energy = float(epics_get("X12SA-OP-CCM1:ENERGY-GET")) + except Exception: + ccm_energy = -1 + + allowed = (dmm_trans == 1) and (dmm_rot == 1) and (ccm_energy > 1) + + if not allowed: + print("\n⚠️ SAFETY WARNING: Reducing transmission (< 1) typically requires:") + print(" - DMM translation in THROUGH (THRU == 1)") + print(" - DMM rotation in THROUGH (THRU == 1)") + print(" - CCM energy > 1 keV") + print("\nCurrent state:") + print(f" DMM translation THRU : {dmm_trans}") + print(f" DMM rotation THRU : {dmm_rot}") + print(f" CCM energy (keV) : {ccm_energy}") + + # Ask user (default = NO) + if hasattr(self, "OMNYTools") and hasattr(self.OMNYTools, "yesno"): + proceed = self.OMNYTools.yesno( + "Conditions not satisfied. Proceed anyway?", + default="n", + ) + else: + # Safe fallback + proceed = False + + if not proceed: + print("Aborted. Transmission unchanged.") + return None + + # --- Energy handling (EPICS only) --- if energy_kev is None: try: - energy_kev = float(dev.mokev.read()) # using global dev + energy_kev = float(epics_get("X12SA-OP-CCM1:ENERGY-GET")) except Exception as exc: raise RuntimeError( - "Energy not specified and could not read current beam energy." + "Energy not specified and could not read EPICS PV " + "'X12SA-OP-CCM1:ENERGY-GET'." ) from exc else: energy_kev = float(energy_kev) + # --- Summary header --- print("\nExposure-box filter transmission request") print("-" * 60) @@ -221,20 +278,23 @@ class cSAXSFilterTransmission: # --- Dry run prompt --- if print_only: print("\n[DRY RUN] No motion executed yet.") - if self.OMNYTools.yesno( - "Execute motion to the selected filter combination now?", - "y", # default YES - ): - self._execute_combination(best, energy_kev) + if hasattr(self, "OMNYTools") and hasattr(self.OMNYTools, "yesno"): + if self.OMNYTools.yesno( + "Execute motion to the selected filter combination now?", + "y", # default YES + ): + self._execute_combination(best, energy_kev) + else: + print("Execution skipped.") else: - print("Execution skipped.") + # If yesno not available, default to 'skip' on print_only + print("No interactive prompt available. Execution skipped (print_only=True).") return None # --- Execute motion directly --- self._execute_combination(best, energy_kev) return None - # ----------------------------- # Physics helpers # ----------------------------- @@ -522,13 +582,18 @@ class cSAXSFilterTransmission: print("ERROR: Global 'dev' object not found.") return - # Determine energy + # --- Energy handling (EPICS only) --- if energy_kev is None: try: - energy_kev = float(dev.mokev.read()) - except Exception: - print("WARNING: Could not read current beam energy. Assuming 6.2 keV.") - energy_kev = 6.2 + energy_kev = float(epics_get("X12SA-OP-CCM1:ENERGY-GET")) + except Exception as exc: + raise RuntimeError( + "Energy not specified and could not read EPICS PV " + "'X12SA-OP-CCM1:ENERGY-GET'." + ) from exc + else: + energy_kev = float(energy_kev) + print("\nCurrent filter transmission report") print("-" * 60) diff --git a/csaxs_bec/device_configs/user_template.yaml b/csaxs_bec/device_configs/user_template.yaml new file mode 100644 index 0000000..2cf6beb --- /dev/null +++ b/csaxs_bec/device_configs/user_template.yaml @@ -0,0 +1,85 @@ +############################################################ +#################### OWIS LTM80 ############################ +############################################################ +samx: + description: Owis motor stage samx + deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME + deviceConfig: + prefix: X12SA-ES2-ES02 + motor_resolution: 0.00125 + base_velocity: 0.0625 + velocity: 10 + backlash_distance: 0.125 + acceleration: 0.2 + user_offset_dir: 0 + deviceTags: + - cSAXS + - owis_samx + onFailure: buffer + enabled: true + readoutPriority: baseline + softwareTrigger: false +############################################################ +#################### OWIS Rotation DMT65 ################### +############################################################ +rotx: + description: Rotation stage rotx + deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME + deviceConfig: + prefix: X12SA-ES2-ES03 + motor_resolution: 0.0025 + base_velocity: 0.5 + velocity: 7.5 + backlash_distance: 0.25 + acceleration: 0.2 + user_offset_dir: 1 + limits: + - -0.1 + - 0.1 + deviceTags: + - cSAXS + - rotx + onFailure: buffer + enabled: true + readoutPriority: baseline + softwareTrigger: false + + +############################################################ +#################### npoint motors ######################### +############################################################ + +npx: + description: nPoint x axis on the big npoint controller + deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis + deviceConfig: + axis_Id: A + host: "nPoint000003.psi.ch" + limits: + - -50 + - 50 + port: 23 + sign: 1 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: baseline + deviceTags: + - npoint +npy: + description: nPoint y axis on the big npoint controller + deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis + deviceConfig: + axis_Id: B + host: "nPoint000003.psi.ch" + limits: + - -50 + - 50 + port: 23 + sign: 1 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: baseline + deviceTags: + - npoint \ No newline at end of file