WIP big becclient refactor

This commit is contained in:
2026-08-06 14:32:54 +02:00
parent 9f85493f39
commit 85fb41aa59
7 changed files with 118 additions and 173 deletions
@@ -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
+18 -1
View File
@@ -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: ...
@@ -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):
@@ -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
@@ -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
+7 -75
View File
@@ -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
+41 -90
View File
@@ -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}"]