diff --git a/src/aare/beamline_dispatch/default/beamline_dispatch.py b/src/aare/beamline_dispatch/default/beamline_dispatch.py index c432da92..500dd6cf 100644 --- a/src/aare/beamline_dispatch/default/beamline_dispatch.py +++ b/src/aare/beamline_dispatch/default/beamline_dispatch.py @@ -1,6 +1,8 @@ import os -from aare.beamline_dispatch.protocols import AuthDispatch, BeamlineDispatch +from aarecommon.config.beamline import MXBeamline + +from aare.beamline_dispatch.protocols import AuthDispatch, BeamlineDispatch, ConfigKeys class DefaultAuthDispatch(AuthDispatch): @@ -12,13 +14,49 @@ class DefaultAuthDispatch(AuthDispatch): return key +class DefaultConfigKeys(ConfigKeys): + def __init__(self, beamline: MXBeamline): + self._beamline = beamline + + @property + def bl_lower(self) -> str: + return self._beamline.lower() + + @property + def bl_upper(self) -> str: + return self._beamline.upper() + + @property + def bl_name(self) -> str: + match self._beamline: + case MXBeamline.X06SA: + return "pxi" + case MXBeamline.X10SA: + return "pxii" + case MXBeamline.X06DA: + return "pxiii" + case MXBeamline.SIMULATED: + return "simulated" + case _: + raise ValueError(f"Beamline {self._beamline} has no common name!") + + @property + def device_yml_path(self) -> str: + return f"/sls/{self.bl_lower}/config/bec/production/{self.bl_name}_bec/{self.bl_name}_bec/device_configs/{self.bl_name}-devices.yaml" + + class DefaultDispatch(BeamlineDispatch): """Default implementation for anything which can vary between beamlines and/or simulation. Should be safe and fail rather than assuming anything.""" - def __init__(self) -> None: + def __init__(self, beamline: MXBeamline) -> None: self._auth = DefaultAuthDispatch() + self._config = DefaultConfigKeys(beamline) @property def auth(self): return self._auth + + @property + def config(self): + return self._config diff --git a/src/aare/beamline_dispatch/protocols.py b/src/aare/beamline_dispatch/protocols.py index d0675a7a..e562c9d4 100644 --- a/src/aare/beamline_dispatch/protocols.py +++ b/src/aare/beamline_dispatch/protocols.py @@ -22,12 +22,26 @@ class BecMacros(ABC): def bl_energy(energy_ev, move_gap=True, mono_scan=True, plot=True): ... @staticmethod @abstractmethod - def get_current_energy(): ... + def get_current_energy() -> float: ... @staticmethod @abstractmethod def mono_pitch_scan(plot=True): ... +class ConfigKeys(ABC): + @property + @abstractmethod + def bl_lower(self) -> str: ... + @property + @abstractmethod + def bl_upper(self) -> str: ... + @property + @abstractmethod + def bl_name(self) -> str: ... + @property + def device_yml_path(self) -> str: ... + + class BeamlineDispatch(ABC): @property @abstractmethod @@ -35,3 +49,6 @@ class BeamlineDispatch(ABC): @property @abstractmethod def bec_macros(self) -> BecMacros: ... + @property + @abstractmethod + def config(self) -> ConfigKeys: ... diff --git a/src/aare/beamline_dispatch/simulated/beamline_dispatch.py b/src/aare/beamline_dispatch/simulated/beamline_dispatch.py index 59fcbd14..b023bf79 100644 --- a/src/aare/beamline_dispatch/simulated/beamline_dispatch.py +++ b/src/aare/beamline_dispatch/simulated/beamline_dispatch.py @@ -1,6 +1,8 @@ from typing import Any -from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch +from aarecommon.config.beamline import MXBeamline + +from aare.beamline_dispatch.default.beamline_dispatch import DefaultConfigKeys, DefaultDispatch from aare.beamline_dispatch.protocols import AuthDispatch, BecMacros @@ -32,6 +34,7 @@ class SimulatedDispatch(DefaultDispatch): def __init__(self) -> None: self._auth = SimulatedAuthDispatch() self._bec_macros = SimulatedBecMacros() + self._config = DefaultConfigKeys(MXBeamline.SIMULATED) @property def bec_macros(self): diff --git a/src/aare/beamline_dispatch/x06da/beamline_dispatch.py b/src/aare/beamline_dispatch/x06da/beamline_dispatch.py index da0dbb87..1ef2578a 100644 --- a/src/aare/beamline_dispatch/x06da/beamline_dispatch.py +++ b/src/aare/beamline_dispatch/x06da/beamline_dispatch.py @@ -1,5 +1,7 @@ from typing import Any +from aarecommon.config.beamline import MXBeamline + from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch from aare.beamline_dispatch.protocols import BecMacros @@ -45,8 +47,8 @@ class X06daBecMacros(BecMacros): class X06daDispatch(DefaultDispatch): - def __init__(self) -> None: - super().__init__() + def __init__(self, beamline: MXBeamline) -> None: + super().__init__(beamline) self._bec_macros = X06daBecMacros() @property diff --git a/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py b/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py index 380513cb..6ef814dd 100644 --- a/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py +++ b/src/aare/beamline_dispatch/x10sa/beamline_dispatch.py @@ -1,5 +1,7 @@ from typing import Any +from aarecommon.config.beamline import MXBeamline + from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch from aare.beamline_dispatch.protocols import BecMacros @@ -39,8 +41,8 @@ class X10SaBecMacros(BecMacros): class X10saDispatch(DefaultDispatch): - def __init__(self) -> None: - super().__init__() + def __init__(self, beamline: MXBeamline) -> None: + super().__init__(beamline) self._bec_macros = X10SaBecMacros() @property diff --git a/src/aare/daq/devices.py b/src/aare/daq/devices.py index 252d5121..cf4ec4be 100644 --- a/src/aare/daq/devices.py +++ b/src/aare/daq/devices.py @@ -11,16 +11,12 @@ from aarecommon.config.logger_events import log_timing from aarecommon.math.coordinate import AerotechCoordinate, SmargonCoordinate from aarecommon.models.beamline import MXBeamline from aarecommon.models.models import SampleCameraSettings, StagePositionEnum -from epics import PV from aare.devices import aerotech, smargon from aare.devices.area_detector import AutoEnum, epicsAD from aare.devices.bec_worker import BECClientWorker -from aare.devices.enum_pv import EnumPV from aare.devices.experimental_hutch_shutter import ExperimentalHutchShutter -from aare.devices.my_motor import MyMotor from aare.devices.pss_state import PssState -from aare.devices.set_get_pv import PredefinedPV, SetGetPV from aare.devices.tell_client import make_tell_client logger = setup_logger("aareDAQ") @@ -37,78 +33,14 @@ class BeamlineDevices: logger.debug("initialising BEC worker done") self._smargon = smargon.Smargon(beamline) self.exp_shutter = ExperimentalHutchShutter(beamline=self._beamline) + self.dtz_mod = cfg_get("daq.detector_distance_limit_modifier", 1.0) + + # TODO convert epics pvs to BEC -> need to get all samcam signals into a device + # BEC device for pss state also good # Personnel Safety System: gates whether the robot is allowed to move. self.pss = PssState(beamline=self._beamline) - - # faster to define the dtz object here than in functions and then use - self._dtz = self.bec_worker.dev.det_z - self.dtz_mod = cfg_get("daq.detector_distance_limit_modifier", 1.0) - # TODO convert epics pvs to BEC self._sample_cam = epicsAD(f"{BEAMLINE}-ES-MS:") - self._front_light = PredefinedPV( - name="front_light", - setpv=f"{BEAMLINE}-ES-FL:SET", - getpv=f"{BEAMLINE}-ES-FL:SET", - predefs={"off": 1.49, "half": 2.0, "max": 3.0}, - timeout=10.0, - ) - self._back_light = PredefinedPV( - name="back_light", - setpv=f"{BEAMLINE}-ES-BL:SET", - getpv=f"{BEAMLINE}-ES-BL:SET", - predefs={"off": 0, "half": 0.98, "max": 1.2}, - timeout=10.0, - ) - - # self._front_light = self.bec_worker.dev.fl_bright # need wrapper on bec_worker layer - # self._back_light = self.bec_worker.dev.bl_bright #need wrapper on bec_worker layer - - self._back_light_pos = EnumPV( - name="back_light_pos", - setpv=f"{BEAMLINE}-ES-BL:POS-SET", - getpv=f"{BEAMLINE}-ES-BL:POS-GET", - timeout=10.0, - ) - - self._ringcurrent = self.bec_worker.ring_current - - self._zoom = SetGetPV( - name="zoom", setpv=f"{BEAMLINE}-ES-MS:ZOOM.VAL", getpv=f"{BEAMLINE}-ES-MS:ZOOM.RBV" - ) - - self._cryojet_pos = EnumPV( - name="cryojet_pos", - setpv=f"{BEAMLINE}-ES-CS:POS-SET", - getpv=f"{BEAMLINE}-ES-CS:POS-GET", - timeout=10.0, - ) - - self._cryojet_x = MyMotor(f"{BEAMLINE}-ES-CS:TRX") # currently in is 5 out is 15? - - self._cryojet_temperature_get = PV(f"{BEAMLINE}-ES-CS:TEMP_RBV") - self._cryojet_temperature_set = PV(f"{BEAMLINE}-ES-CS:TEMP.VAL") - - if self._beamline == MXBeamline.X06DA: - self._transmission = SetGetPV( - name="transmission", - setpv=f"{BEAMLINE}-ES-BCFI:TRANSM-SET", - getpv=f"{BEAMLINE}-ES-BCFI:TRANSM-GET", - ) - else: - self._transmission = SetGetPV( - name="transmission", - setpv=f"{BEAMLINE}-ES-SSFI:TRANSM-SET", - getpv=f"{BEAMLINE}-ES-SSFI:TRANSM-GET", - ) - - self._fast_shutter = PV(f"{BEAMLINE}-ES-SHUTTER:SET") - - self.magnet_position_sensor = PV(f"{BEAMLINE}-ES-DFS:CBOX-CMP1") - self.magnet_position_sensor_readout = PV(f"{BEAMLINE}-ES-DFS:CBOX-USER1") - # self.magnet_position_sensor_readout = PV(f"{BEAMLINE}-ES-DFS:CBOX-REFVAL1") - self.magnet_position_sensor_state = PV(f"{BEAMLINE}-ES-DFS:CBOX-STATE") - def restart_bec_worker(self, simulated: bool = False) -> None: try: if getattr(self, "bec_worker", None) is not None and not simulated: @@ -141,7 +73,7 @@ class BeamlineDevices: # Transmission @property def transmission(self) -> float: - return self._transmission.value + return self.bec_worker.tra @transmission.setter def transmission(self, value: float): @@ -178,14 +110,14 @@ class BeamlineDevices: # Zoom @property def zoom(self) -> float: - return self._zoom.value + return self._samcam_zoom.get() @zoom.setter def zoom(self, value: float): self.set_zoom(value, wait=True) def set_zoom(self, value: float, /, wait: bool = True): - self._zoom.move(value, wait=wait) + self._samcam_zoom.put(value, wait=wait) # Optics @property diff --git a/src/aare/devices/bec_worker.py b/src/aare/devices/bec_worker.py index 28e908b1..2b3d3384 100644 --- a/src/aare/devices/bec_worker.py +++ b/src/aare/devices/bec_worker.py @@ -1,15 +1,16 @@ import sys import time -from enum import Enum +from enum import Enum, StrEnum from aarecommon.config.beamline import cfg_get, mx_beamline from aarecommon.config.logger import setup_logger from aarecommon.config.logger_events import log_timing -from aarecommon.errors.exception_handler import BECCommunicationError +from aarecommon.errors.exception_handler import BECCommunicationError, BECException from aarecommon.models.beamline import MXBeamline from bec_ipython_client import BECIPythonClient from bec_ipython_client.signals import OperationMode from bec_lib.device import RPCError, ScanRequestError +from bec_lib.devicemanager import DeviceContainer from bec_lib.procedures.helper import FrontendProcedureHelper from bec_lib.service_config import ServiceConfig @@ -49,39 +50,49 @@ class BeamlineState(str, Enum): XTAL_SNAPSHOT = "xtal_snapshot" +class BecDevices(StrEnum): + DET_Z = "det_z" + FL_BRIGHT = "fl_bright" + BL_BRIGHT = "bl_bright" + BL_POS = "bl_pos" + SCAM_ZOOM = "scam_zoom" + CRYO_POS = "cryo_pos" + CRYO_TEMP = "cryo_temp" + CRYO_X = "cryo_x" + TRANSM = "transm" + BCU_SHUTTER = "bcu_shutter" + MAG_OK = "mag_ok" + MAG_RBV = "mag_rbv" + + +class MissingDevicesError(BECException): ... + + class BECClientWorker: def __init__(self, beamline: MXBeamline, name: str = "default"): - BEAMLINE = beamline.value.lower() - self.beamline = beamline - if self.beamline is MXBeamline.X06DA: - self._beamline_name = "pxiii" - elif self.beamline is MXBeamline.X10SA: - self._beamline_name = "pxii" - elif self.beamline is MXBeamline.X06DA: - self._beamline_name = "pxi" - elif self.beamline is MXBeamline.SIMULATED: - self._beamline_name = "SIMULATED" - else: - raise ValueError(f"Unknown beamline: {beamline}") - self.dispatch = get_beamline_dispatch() - if self.beamline is MXBeamline.SIMULATED: + if beamline is MXBeamline.SIMULATED: self.simulated = True else: self.simulated = False - logger.debug(f"Initializing BECClientWorker for {BEAMLINE} beamline") - host = cfg_get("daq.hardware.bec_url", f"{BEAMLINE}-bec-001.psi.ch") + logger.debug( + f"Initializing BECClientWorker for {self.dispatch.config.bl_lower} beamline" + ) + host = cfg_get( + "daq.hardware.bec_url", f"{self.dispatch.config.bl_lower}-bec-001.psi.ch" + ) service_config = ServiceConfig(redis={"host": host, "port": 6379}) service_config.config["log_writer"]["base_path"] = "/tmp/logs" - # service_config.config["user_macros"]["base_path"]=f'/sls/{BEAMLINE}/config/bec/production/pxiii_bec/pxiii_bec' - # print(service_config.config) self.client = BECIPythonClient(config=service_config, mode=OperationMode.Procedure) self.client.start() - # self.client.config.update_session_with_file("/sls/x10sa/config/bec/production/bec/bec_lib/bec_lib/config_helper.py") - self.dev = self.client.device_manager.devices - print(self.dev.keys()) + self.dev: DeviceContainer = self.client.device_manager.devices + missing_devices = {str(d) for d in list(BecDevices) if d not in self.dev} + if missing_devices: + raise MissingDevicesError( + f"{missing_devices} were not found in the BEC session. Available: {list(self.dev.keys())}. If no devices are available, load a BEC device config." + ) self.scans = self.client.scans self.macros = self.dispatch.bec_macros self.helper = FrontendProcedureHelper(self.client.connector) @@ -94,26 +105,11 @@ class BECClientWorker: logger.debug(f"simulated is {self.simulated}") def _init_beamline_environment(self): - try: - self.position_devices, self.planner = self.macros.init_beamline_environment() - self._backlight_brightness = self.position_devices["bl_bright"] - self._frontlight_brightness = self.position_devices["fl_bright"] - self._zoom = self.dev.scam_zoom - self._ring_current = self.dev.sls_current - except Exception as e: - logger.error(f"Error initialising planar and position devices: {e}") - self.position_devices = None - self.planner = None - self._backlight_brightness = None - self._frontlight_brightness = None - try: - self._zoom = self.dev.scam_zoom - self._ring_current = self.dev.sls_current - except Exception as e: - logger.exception("Error initialising zoom and ring_current") - self._zoom = None - self.ring_current = None - raise RuntimeError(f"Error initialising BEC devices: {e}") from e + self.position_devices, self.planner = self.macros.init_beamline_environment() + self._backlight_brightness = self.position_devices["bl_bright"] + self._frontlight_brightness = self.position_devices["fl_bright"] + self._zoom = self.dev[BecDevices.SCAM_ZOOM] + self._ring_current = self.dev.sls_current def _bec_error( self, exc: Exception, *, operation: str, tags: list[str] | None = None @@ -219,28 +215,6 @@ class BECClientWorker: except Exception: logger.exception("Error sending scilog message") - def run_macro(self, macro_name: str, *args, queue: str = "default", **kwargs): - if self.simulated: - logger.debug(f"Simulating macro {macro_name}") - return None - try: - return self.client.proc.run_macro(macro_name, *args, queue=queue) - except Exception as e: - raise self._bec_error(e, operation=f"run_macro:{macro_name}") from e - - def run_macro_blocked(self, macro_name: str, *args, queue: str = "default", **kwargs): - if self.simulated: - logger.debug(f"Simulating macro {macro_name}") - return None - try: - status = self.run_macro(macro_name, *args, queue=queue) - print(status) - status.wait() - print(status) - return status - except Exception as e: - raise self._bec_error(e, operation=f"run_macro_blocked:{macro_name}") from e - @log_timing(logger, "BEC move_to") def move_to(self, state: BeamlineState): start = time.perf_counter() @@ -299,27 +273,6 @@ class BECClientWorker: except Exception as e: raise self._bec_error(e, operation="list_all_user_macros") from e - def _list_all_macros(self): - result = self.client.macros.list_user_macros() - if result is None: - return [] - return result - - def load_user_macros(self): - if self.simulated: - logger.debug("Simulating load_user_macros") - return None - try: - return self._load_user_macros() - except Exception as e: - raise self._bec_error(e, operation="load_user_macros") from e - - def _load_user_macros(self): - result = self.macros.load_all_user_macros() - if result is None: - logger.warning("BEC load_all_user_macros returned None") - return result - def reinitialise_planner_and_position_devices(self, method: str = "auto"): """ Reinitialise BEC position devices and planner. @@ -337,9 +290,7 @@ class BECClientWorker: logger.debug(f"Simulating reinitialise_planner_and_position_devices(method={method})") return [] try: - self.client.config.update_session_with_file( - f"/sls/{self.beamline}/config/bec/production/{self._beamline_name}_bec/{self._beamline_name}_bec/device_configs/{self._beamline_name}-devices.yaml" - ) + self.client.config.update_session_with_file(self.dispatch.config.device_yml_path) self._init_beamline_environment() logger.info(f"Reinitialised BEC planner and position devices using method={method}") return self.list_position_devices() @@ -356,7 +307,7 @@ class BECClientWorker: self.macros.mono_pitch_scan(plot) except Exception as e: raise self._bec_error(e, operation="mono_pitch_scan", tags=["mono_pitch_scan"]) from e - if self.beamline is MXBeamline.X06DA: + if self.dispatch.config.bl_upper == MXBeamline.X06DA: addtional_text = [f"New dcm_pitch position: {self.dev.dcm_pitch.position:5f}"] else: addtional_text = [f"New dcm_theta2 position: {self.dev.dccm_theta2.position:5f}"]