diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py b/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py index af52575..080726e 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/gui_tools.py @@ -156,7 +156,7 @@ class flomniGuiTools: ) self.progressbar.set_value([progress, subtomo_progress, 0]) if self.text_box is not None: - text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}" + text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}" self.text_box.set_plain_text(text) diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py b/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py index f156aad..1c16d7a 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py @@ -17,7 +17,7 @@ umv = builtins.__dict__.get("umv") umvr = builtins.__dict__.get("umvr") if TYPE_CHECKING: - from bec_ipython_client.plugins.flomni import Flomni + from bec_ipython_client.plugins.flomni.flomni import Flomni class XrayEyeAlign: diff --git a/csaxs_bec/device_configs/flomni_config.yaml b/csaxs_bec/device_configs/flomni_config.yaml index e2a20c5..4b9703c 100644 --- a/csaxs_bec/device_configs/flomni_config.yaml +++ b/csaxs_bec/device_configs/flomni_config.yaml @@ -365,6 +365,18 @@ rtz: readOnly: false readoutPriority: on_request +rt_flyer: + description: flomni rt flyer + deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniFlyer + deviceConfig: + host: mpc2844.psi.ch + port: 2222 + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: async + + ############################################################ ####################### Cameras ############################ ############################################################ diff --git a/csaxs_bec/devices/npoint/npoint.py b/csaxs_bec/devices/npoint/npoint.py index 58f9c50..6148aa7 100644 --- a/csaxs_bec/devices/npoint/npoint.py +++ b/csaxs_bec/devices/npoint/npoint.py @@ -412,10 +412,11 @@ class NPointAxis(Device, PositionerBase): sign=1, socket_cls=SocketIO, tolerance: float = 0.05, + device_manager=None, **kwargs, ): self.controller = NPointController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.axis_Id = axis_Id self.sign = sign diff --git a/csaxs_bec/devices/omny/galil/fgalil_ophyd.py b/csaxs_bec/devices/omny/galil/fgalil_ophyd.py index de5a1f0..5790f76 100644 --- a/csaxs_bec/devices/omny/galil/fgalil_ophyd.py +++ b/csaxs_bec/devices/omny/galil/fgalil_ophyd.py @@ -175,7 +175,7 @@ class FlomniGalilMotor(Device, PositionerBase): **kwargs, ): self.controller = FlomniGalilController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.axis_Id = axis_Id self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) @@ -212,6 +212,10 @@ class FlomniGalilMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) @@ -342,10 +346,10 @@ class FlomniGalilMotor(Device, PositionerBase): Drive an axis to the limit in a specified direction. Args: - direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'. + direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'. """ self.controller.drive_axis_to_limit(self.axis_Id_numeric, direction) - #now force position read to cache + # now force position read to cache val = self.readback.read() self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time()) diff --git a/csaxs_bec/devices/omny/galil/fupr_ophyd.py b/csaxs_bec/devices/omny/galil/fupr_ophyd.py index 23b75db..dd257d5 100644 --- a/csaxs_bec/devices/omny/galil/fupr_ophyd.py +++ b/csaxs_bec/devices/omny/galil/fupr_ophyd.py @@ -149,7 +149,7 @@ class FuprGalilMotor(Device, PositionerBase): **kwargs, ): self.controller = FuprGalilController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.axis_Id = axis_Id self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) @@ -185,6 +185,10 @@ class FuprGalilMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) diff --git a/csaxs_bec/devices/omny/galil/lgalil_ophyd.py b/csaxs_bec/devices/omny/galil/lgalil_ophyd.py index 05f12be..f87a912 100644 --- a/csaxs_bec/devices/omny/galil/lgalil_ophyd.py +++ b/csaxs_bec/devices/omny/galil/lgalil_ophyd.py @@ -73,6 +73,7 @@ class LamniGalilController(GalilController): air_off = bool(self.socket_put_and_receive("MG@OUT[13]")) return rt_not_blocked_by_galil and air_off + class LamniGalilReadbackSignal(GalilSignalRO): @retry_once @threadlocked @@ -99,6 +100,7 @@ class LamniGalilReadbackSignal(GalilSignalRO): logger.warning("Failed to set RT value during readback.") return val + class LamniGalilMotor(Device, PositionerBase): USER_ACCESS = ["controller", "drive_axis_to_limit", "find_reference"] readback = Cpt(LamniGalilReadbackSignal, signal_name="readback", kind="hinted") @@ -132,7 +134,7 @@ class LamniGalilMotor(Device, PositionerBase): **kwargs, ): self.controller = LamniGalilController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.axis_Id = axis_Id self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) @@ -168,6 +170,10 @@ class LamniGalilMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) @@ -292,7 +298,7 @@ class LamniGalilMotor(Device, PositionerBase): Find the reference of the axis. """ self.controller.find_reference(self.axis_Id_numeric) - #now force position read to cache + # now force position read to cache val = self.readback.read() self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time()) @@ -301,10 +307,10 @@ class LamniGalilMotor(Device, PositionerBase): Drive an axis to the limit in a specified direction. Args: - direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'. + direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'. """ self.controller.drive_axis_to_limit(self.axis_Id_numeric, direction) - #now force position read to cache + # now force position read to cache val = self.readback.read() self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time()) diff --git a/csaxs_bec/devices/omny/galil/ogalil_ophyd.py b/csaxs_bec/devices/omny/galil/ogalil_ophyd.py index 527ae18..2dcd72e 100644 --- a/csaxs_bec/devices/omny/galil/ogalil_ophyd.py +++ b/csaxs_bec/devices/omny/galil/ogalil_ophyd.py @@ -46,7 +46,7 @@ class GalilMotorResolution(GalilSignalRO): @threadlocked def _socket_get(self): if self.controller.sock.port == 8083 and self.parent.axis_Id_numeric == 2: - # rotation stage + # rotation stage return 89565.8666667 else: return 51200 @@ -69,37 +69,43 @@ class OMNYGalilReadbackSignal(GalilSignalRO): current_pos = float(self.controller.socket_put_and_receive(f"TP{self.parent.axis_Id}")) current_pos *= self.parent.sign step_mm = self.parent.motor_resolution.get() - #here we introduce an offset of 25 to the rotation axis - #when setting a position this is taken into account in the controller - #that way we just do tomography from 0 to 180 degrees + # here we introduce an offset of 25 to the rotation axis + # when setting a position this is taken into account in the controller + # that way we just do tomography from 0 to 180 degrees if self.parent.axis_Id_numeric == 2 and self.controller.sock.port == 8083: - return (current_pos / step_mm)+25 + return (current_pos / step_mm) + 25 else: return current_pos / step_mm def read(self): self._metadata["timestamp"] = time.time() val = super().read() - - #if reading rotation stage angle + + # if reading rotation stage angle if self.parent.axis_Id_numeric == 2 and self.controller.sock.port == 8083: current_readback_value = val[self.parent.name]["value"] - #print (f"previous rotation angle {self.previous_rotation_angle}, current readback {current_readback_value}.") + # print (f"previous rotation angle {self.previous_rotation_angle}, current readback {current_readback_value}.") - if np.fabs((self.previous_rotation_angle-current_readback_value)>10): + if np.fabs((self.previous_rotation_angle - current_readback_value) > 10): message = f"Glitch detected in rotation stage. Previous rotation angle {self.previous_rotation_angle}, current readback {current_readback_value}." print(message) - self.parent.device_manager.connector.send_client_info(message, scope="glitch detector", show_asap=True) - + self.parent.device_manager.connector.send_client_info( + message, scope="glitch detector", show_asap=True + ) + val = super().read() current_readback_value = val[self.parent.name]["value"] - if np.fabs((self.previous_rotation_angle-current_readback_value)>10): + if np.fabs((self.previous_rotation_angle - current_readback_value) > 10): message = f"Glitch detected in rotation stage second read. Previous rotation angle {self.previous_rotation_angle}, current readback {current_readback_value}. Disabling the controller." print(message) - self.parent.device_manager.connector.send_client_info(message, scope="glitch detector", show_asap=True) - - self.parent.device_manager.devices["osamroy"].obj.controller.socket_put_confirmed("allaxref=0") + self.parent.device_manager.connector.send_client_info( + message, scope="glitch detector", show_asap=True + ) + + self.parent.device_manager.devices["osamroy"].obj.controller.socket_put_confirmed( + "allaxref=0" + ) self.parent.device_manager.devices["osamroy"].obj.enabled = False return val @@ -108,13 +114,12 @@ class OMNYGalilReadbackSignal(GalilSignalRO): try: rt = self.parent.device_manager.devices["rtx"] if rt.enabled: - rt.obj.controller.set_rotation_angle(val[self.parent.name]["value"]-25+54) + rt.obj.controller.set_rotation_angle(val[self.parent.name]["value"] - 25 + 54) except KeyError: - logger.warning("Failed to set RT value during ogalil readback.") + logger.warning("Failed to set RT value during ogalil readback.") return val - class OMNYGalilController(GalilController): USER_ACCESS = [ "describe", @@ -132,18 +137,18 @@ class OMNYGalilController(GalilController): "_ogalil_folerr_not_ignore", ] - OKBLUE = '\033[94m' - OKCYAN = '\033[96m' - OKGREEN = '\033[92m' - WARNING = '\033[93m' - FAIL = '\033[91m' - ENDC = '\033[0m' - - def on(self) -> None: + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + + def on(self, timeout: int = 10) -> None: """Open a new socket connection to the controller""" self._ogalil_switchsocket_switch_all_on() time.sleep(0.3) - super().on() + super().on(timeout=timeout) def _ogalil_switchsocket(self, number: int, switch: bool): # number is socket number ranging from 1 to 4 @@ -185,15 +190,16 @@ class OMNYGalilController(GalilController): self.socket_put_confirmed("IgNoFol=1") self.socket_put_confirmed("XQ#STOP,1") - - def _ogalil_set_axis_to_pos_wo_reference_search(self, axis_id_numeric, axis_id, pos_mm, motor_resolution, motor_sign): + def _ogalil_set_axis_to_pos_wo_reference_search( + self, axis_id_numeric, axis_id, pos_mm, motor_resolution, motor_sign + ): self.socket_put_confirmed("IgNoFol=1") # pos_mm = pos_encoder / motor_resolution pos_encoder = pos_mm * motor_resolution * motor_sign - #print(motor_resolution) - + # print(motor_resolution) + self.socket_put_confirmed(f"DE{axis_id}={pos_encoder:.0f}") self.socket_put_confirmed(f"DP{axis_id}=_TP{axis_id}*ratio[{axis_id_numeric:.0f}]") @@ -203,7 +209,6 @@ class OMNYGalilController(GalilController): self._ogalil_folerr_not_ignore() - def _ogalil_folerr_not_ignore(self): self.socket_put_confirmed("IgNoFol=0") @@ -240,7 +245,18 @@ class OMNYGalilController(GalilController): class OMNYGalilMotor(Device, PositionerBase): - USER_ACCESS = ["controller", "find_reference", "omny_osamx_to_scan_center", "drive_axis_to_limit", "_ogalil_folerr_reset_and_ignore", "_ogalil_set_axis_to_pos_wo_reference_search", "get_motor_limit_switch", "axis_is_referenced", "get_motor_temperature", "folerr_status"] + USER_ACCESS = [ + "controller", + "find_reference", + "omny_osamx_to_scan_center", + "drive_axis_to_limit", + "_ogalil_folerr_reset_and_ignore", + "_ogalil_set_axis_to_pos_wo_reference_search", + "get_motor_limit_switch", + "axis_is_referenced", + "get_motor_temperature", + "folerr_status", + ] readback = Cpt(OMNYGalilReadbackSignal, signal_name="readback", kind="hinted") user_setpoint = Cpt(GalilSetpointSignal, signal_name="setpoint") motor_resolution = Cpt(GalilMotorResolution, signal_name="resolution", kind="config") @@ -272,7 +288,7 @@ class OMNYGalilMotor(Device, PositionerBase): **kwargs, ): self.controller = OMNYGalilController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.axis_Id = axis_Id self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) @@ -308,6 +324,10 @@ class OMNYGalilMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) @@ -433,8 +453,10 @@ class OMNYGalilMotor(Device, PositionerBase): def _ogalil_set_axis_to_pos_wo_reference_search(self, pos_mm): motor_resolution = self.motor_resolution.get() - self.controller._ogalil_set_axis_to_pos_wo_reference_search(self.axis_Id_numeric, self.axis_Id, pos_mm, motor_resolution, self.sign) - #now force position read to cache + self.controller._ogalil_set_axis_to_pos_wo_reference_search( + self.axis_Id_numeric, self.axis_Id, pos_mm, motor_resolution, self.sign + ) + # now force position read to cache val = self.readback.read() self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time()) @@ -442,9 +464,9 @@ class OMNYGalilMotor(Device, PositionerBase): """ Find the reference of the axis. """ - verbose=1 + verbose = 1 self.controller.find_reference(self.axis_Id_numeric, verbose, raise_error) - #now force position read to cache + # now force position read to cache val = self.readback.read() self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time()) @@ -453,10 +475,10 @@ class OMNYGalilMotor(Device, PositionerBase): Drive an axis to the limit in a specified direction. Args: - direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'. + direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'. """ self.controller.drive_axis_to_limit(self.axis_Id_numeric, direction, verbose=1) - #now force position read to cache + # now force position read to cache val = self.readback.read() self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time()) @@ -487,29 +509,31 @@ class OMNYGalilMotor(Device, PositionerBase): def omny_osamx_to_scan_center(self, cenx): if self.controller.sock.port == 8082 and self.axis_Id_numeric == 0: # get last setpoint - osamx = self.device_manager.devices["osamx"] - osamx_current_setpoint = osamx.obj.readback.get() - omny_samx_in = self._get_user_param_safe("osamx","in") - if np.fabs(osamx_current_setpoint-(omny_samx_in+cenx/1000)) > 0.025: - message=f"Moving osamx to scan center. new osamx target {omny_samx_in+cenx/1000:.3f}." - logger.info(message) + osamx = self.device_manager.devices["osamx"] + osamx_current_setpoint = osamx.obj.readback.get() + omny_samx_in = self._get_user_param_safe("osamx", "in") + if np.fabs(osamx_current_setpoint - (omny_samx_in + cenx / 1000)) > 0.025: + message = ( + f"Moving osamx to scan center. new osamx target {omny_samx_in+cenx/1000:.3f}." + ) + logger.info(message) - osamx.read_only = False - #osamx.controller.("osamx", "controller.socket_put_confirmed('axspeed[0]=1000')") - osamx.set(omny_samx_in+cenx/1000) - time.sleep(0.1) - while(osamx.motor_is_moving.get()): - time.sleep(0.05) - osamx.read_only = True - time.sleep(2) - rt = self.device_manager.devices["rtx"] - if rt.enabled: - rt.obj.controller.laser_tracker_on() - rt.obj.controller.laser_tracker_check_and_wait_for_signalstrength() + osamx.read_only = False + # osamx.controller.("osamx", "controller.socket_put_confirmed('axspeed[0]=1000')") + osamx.set(omny_samx_in + cenx / 1000) + time.sleep(0.1) + while osamx.motor_is_moving.get(): + time.sleep(0.05) + osamx.read_only = True + time.sleep(2) + rt = self.device_manager.devices["rtx"] + if rt.enabled: + rt.obj.controller.laser_tracker_on() + rt.obj.controller.laser_tracker_check_and_wait_for_signalstrength() def folerr_status(self) -> bool: return self.controller.folerr_status(self.axis_Id_numeric) - + def stop(self, *, success=False): self.controller.stop_all_axes() return super().stop(success=success) diff --git a/csaxs_bec/devices/omny/galil/sgalil_ophyd.py b/csaxs_bec/devices/omny/galil/sgalil_ophyd.py index d88b191..76af34e 100644 --- a/csaxs_bec/devices/omny/galil/sgalil_ophyd.py +++ b/csaxs_bec/devices/omny/galil/sgalil_ophyd.py @@ -52,33 +52,12 @@ class GalilController(Controller): "fly_grid_scan", "read_encoder_position", ] + _axes_per_controller = 8 - def __init__( - self, - *, - name="GalilController", - kind=None, - parent=None, - socket=None, - attr_name="", - labels=None, - ): - if not hasattr(self, "_initialized") or not self._initialized: - self._galil_axis_per_controller = 8 - self._axis = [None for axis_num in range(self._galil_axis_per_controller)] - super().__init__( - name=name, - socket=socket, - attr_name=attr_name, - parent=parent, - labels=labels, - kind=kind, - ) - - def on(self, controller_num=0) -> None: + def on(self, timeout: int = 10) -> None: """Open a new socket connection to the controller""" if not self.connected: - self.sock.open() + self.sock.open(timeout=timeout) self.connected = True else: logger.info("The connection has already been established.") @@ -165,11 +144,11 @@ class GalilController(Controller): def show_running_threads(self) -> None: t = PrettyTable() t.title = f"Threads on {self.sock.host}:{self.sock.port}" - t.field_names = [str(ax) for ax in range(self._galil_axis_per_controller)] + t.field_names = [str(ax) for ax in range(self._axes_per_controller)] t.add_row( [ "active" if self.is_thread_active(t) else "inactive" - for t in range(self._galil_axis_per_controller) + for t in range(self._axes_per_controller) ] ) print(t) @@ -199,7 +178,7 @@ class GalilController(Controller): "Limits", "Position", ] - for ax in range(self._galil_axis_per_controller): + for ax in range(self._axes_per_controller): axis = self._axis[ax] if axis is not None: t.add_row( @@ -516,7 +495,9 @@ class SGalilMotor(Device, PositionerBase): ): self.axis_Id = axis_Id self.sign = sign - self.controller = GalilController(socket=socket_cls(host=host, port=port)) + self.controller = GalilController( + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager + ) self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) self.tolerance = kwargs.pop("tolerance", 0.5) self.device_mapping = kwargs.pop("device_mapping", {}) @@ -549,6 +530,10 @@ class SGalilMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) diff --git a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py index 116ae10..98a55b8 100644 --- a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py @@ -1,14 +1,16 @@ +from __future__ import annotations + import threading import time -from typing import List +from typing import TYPE_CHECKING, Literal import numpy as np -from bec_lib import bec_logger, messages -from bec_lib.endpoints import MessageEndpoints +from bec_lib.logger import bec_logger from ophyd import Component as Cpt -from ophyd import Device, PositionerBase, Signal +from ophyd import Device, DeviceStatus, PositionerBase, Signal from ophyd.status import wait as status_wait from ophyd.utils import LimitError +from ophyd_devices.utils.bec_signals import AsyncMultiSignal, ProgressSignal from ophyd_devices.utils.controller import Controller, threadlocked from ophyd_devices.utils.socket import SocketIO, raise_if_disconnected from prettytable import PrettyTable @@ -23,6 +25,9 @@ from csaxs_bec.devices.omny.rt.rt_ophyd import ( retry_once, ) +if TYPE_CHECKING: + from bec_server.device_server.devices.devicemanager import DeviceManagerDS + logger = bec_logger.logger @@ -57,6 +62,7 @@ class RtFlomniController(Controller): socket_cls=None, socket_host=None, socket_port=None, + device_manager=None, attr_name="", parent=None, labels=None, @@ -67,6 +73,7 @@ class RtFlomniController(Controller): socket_cls=socket_cls, socket_host=socket_host, socket_port=socket_port, + device_manager=device_manager, attr_name=attr_name, parent=parent, labels=labels, @@ -167,10 +174,10 @@ class RtFlomniController(Controller): rtx = self.get_device_manager().devices.rtx rtx.update_user_parameter({"rt_pid_voltage": self.rt_pid_voltage}) - self.set_device_enabled("fsamx", False) - self.set_device_enabled("fsamy", False) - self.set_device_enabled("foptx", False) - self.set_device_enabled("fopty", False) + self.set_device_read_write("fsamx", False) + self.set_device_read_write("fsamy", False) + self.set_device_read_write("foptx", False) + self.set_device_read_write("fopty", False) def move_samx_to_scan_region(self, fovx: float, cenx: float): time.sleep(0.05) @@ -223,20 +230,20 @@ class RtFlomniController(Controller): print("Feedback is not running; likely an error in the interferometer.") raise RtError("Feedback is not running; likely an error in the interferometer.") - self.set_device_enabled("fsamx", False) - self.set_device_enabled("fsamy", False) - self.set_device_enabled("foptx", False) - self.set_device_enabled("fopty", False) + self.set_device_read_write("fsamx", False) + self.set_device_read_write("fsamy", False) + self.set_device_read_write("foptx", False) + self.set_device_read_write("fopty", False) def feedback_disable(self): self.clear_trajectory_generator() self.move_to_zero() self.socket_put("l0") - self.set_device_enabled("fsamx", True) - self.set_device_enabled("fsamy", True) - self.set_device_enabled("foptx", True) - self.set_device_enabled("fopty", True) + self.set_device_read_write("fsamx", True) + self.set_device_read_write("fsamy", True) + self.set_device_read_write("foptx", True) + self.set_device_read_write("fopty", True) fsamx = self.get_device_manager().devices.fsamx fsamx.obj.controller.socket_put_confirmed("axspeed[4]=025*stppermm[4]") @@ -268,8 +275,7 @@ class RtFlomniController(Controller): self.laser_update_tracker_info() if self.tracker_info["enabled_z"] and self.tracker_info["enabled_y"]: return True - else: - return False + return False def laser_tracker_on(self): if not self.laser_tracker_check_enabled(): @@ -381,66 +387,61 @@ class RtFlomniController(Controller): val = float(self.socket_put_and_receive(f"j{axis_number}").strip()) return val - def laser_tracker_check_signalstrength(self): + def laser_tracker_check_signalstrength(self) -> Literal["ok", "low", "toolow", "disabled"]: + """ + Check the signal strength of the laser tracker interferometer. + Returns: + str: "ok" if signal is above low limit, "low" if signal is below low limit but above min limit, + "toolow" if signal is below min limit, "disabled" if tracker is not enabled. + """ if not self.laser_tracker_check_enabled(): - returnval = "disabled" - else: - returnval = "ok" - self.laser_tracker_wait_on_target() + return "disabled" - signal = self.read_ssi_interferometer(1) - rtx = self.get_device_manager().devices.rtx - min_signal = rtx.user_parameter.get("min_signal") - low_signal = rtx.user_parameter.get("low_signal") - print(f"low signal: {low_signal}") - print(f"min signal: {min_signal}") - print(f"signal: {signal}") + returnval = "ok" + self.laser_tracker_wait_on_target() + + signal = self.read_ssi_interferometer(1) + rtx = self.get_device_manager().devices.rtx + min_signal = rtx.user_parameter.get("min_signal") + low_signal = rtx.user_parameter.get("low_signal") + print(f"low signal: {low_signal}") + print(f"min signal: {min_signal}") + print(f"signal: {signal}") + if signal < min_signal: + time.sleep(1) if signal < min_signal: - time.sleep(1) - if signal < min_signal: - print( - f"\x1b[91mThe signal of the tracker {signal} is below the minimum required signal of {min_signal}. Readjustment requred!\x1b[0m" - ) - returnval = "toolow" - # raise RtError("The interferometer signal of tracker is too low.") - elif signal < low_signal: print( - f"\x1b[91mThe signal of the tracker {signal} is below the warning limit of {low_signal}. Readjustment recommended!\x1b[0m" + f"\x1b[91mThe signal of the tracker {signal} is below the minimum required signal of {min_signal}. Readjustment requred!\x1b[0m" ) - returnval = "low" + returnval = "toolow" + # raise RtError("The interferometer signal of tracker is too low.") + elif signal < low_signal: + print( + f"\x1b[91mThe signal of the tracker {signal} is below the warning limit of {low_signal}. Readjustment recommended!\x1b[0m" + ) + returnval = "low" return returnval def show_signal_strength_interferometer(self): + """ + Show the signal strength of all four axes of the interferometer. + """ t = PrettyTable() - t.title = f"Interferometer signal strength" + t.title = "Interferometer signal strength" t.field_names = ["Axis", "Value"] for i in range(4): t.add_row([i, self.read_ssi_interferometer(i)]) print(t) - def _get_signals_from_table(self, return_table) -> dict: - self.average_stdeviations_x_st_fzp += float(return_table[4]) - self.average_stdeviations_y_st_fzp += float(return_table[7]) - signals = { - "target_x": {"value": float(return_table[2])}, - "average_x_st_fzp": {"value": float(return_table[3])}, - "stdev_x_st_fzp": {"value": float(return_table[4])}, - "target_y": {"value": float(return_table[5])}, - "average_y_st_fzp": {"value": float(return_table[6])}, - "stdev_y_st_fzp": {"value": float(return_table[7])}, - "average_rotz": {"value": float(return_table[8])}, - "stdev_rotz": {"value": float(return_table[9])}, - "average_stdeviations_x_st_fzp": { - "value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1) - }, - "average_stdeviations_y_st_fzp": { - "value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1) - }, - } - return signals - @threadlocked def start_scan(self): + """ + Start the scan. Before starting the scan, it is checked that the feedback loop is running. + Furthermore, it is checked that at least one target position is planned. + + Raises: + RtError: Raised if feedback loop is not running or if no target positions are planned. + """ if not self.feedback_is_running(): logger.error( "Cannot start scan because feedback loop is not running or there is an" @@ -451,18 +452,23 @@ class RtFlomniController(Controller): " interferometer error." ) # here exception - (mode, number_of_positions_planned, current_position_in_scan) = self.get_scan_status() + (_mode, number_of_positions_planned, _current_position_in_scan) = self.get_scan_status() if number_of_positions_planned == 0: logger.error("Cannot start scan because no target positions are planned.") raise RtError("Cannot start scan because no target positions are planned.") - # hier exception # start a point-by-point scan (for cont scan in flomni it would be "sa") self.socket_put_and_receive("sd") @retry_once @threadlocked - def get_scan_status(self): + def get_scan_status(self) -> tuple[int, int, int]: + """ + Get the current scan status of the controller. + + Returns: + tuple: A tuple containing the mode, number of positions planned, and current position in scan. + """ return_table = (self.socket_put_and_receive("sr")).split(",") if len(return_table) != 3: raise RtCommunicationError( @@ -478,94 +484,15 @@ class RtFlomniController(Controller): current_position_in_scan = int(float(return_table[2])) return (mode, number_of_positions_planned, current_position_in_scan) - def get_device_manager(self): + def get_device_manager(self) -> DeviceManagerDS: + """ + Helper function to get the device manager from one of the axes. + """ for axis in self._axis: - if hasattr(axis, "device_manager") and axis.device_manager: - return axis.device_manager + if hasattr(axis, "device_manager") and axis.device_manager: # type: ignore + return axis.device_manager # type: ignore raise BECConfigError("Could not access the device_manager") - def read_positions_from_sampler(self): - # this was for reading after the scan completed - number_of_samples_to_read = 1 # self.get_scan_status()[1] #number of valid samples, will be updated upon first data read - - read_counter = 0 - - self.average_stdeviations_x_st_fzp = 0 - self.average_stdeviations_y_st_fzp = 0 - self.average_lamni_angle = 0 - - mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status() - - # if not (mode==2 or mode==3): - # error - self.get_device_manager().connector.set( - MessageEndpoints.device_status("rt_scan"), - messages.DeviceStatusMessage( - device="rt_scan", status=1, metadata=self.readout_metadata - ), - ) - # while scan is running - while mode > 0: - # 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) - if current_position_in_scan > 5: - while current_position_in_scan > read_counter + 1: - return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",") - # logger.info(f"{return_table}") - logger.info(f"Read {read_counter} out of {number_of_positions_planned}") - - read_counter = read_counter + 1 - - signals = self._get_signals_from_table(return_table) - - self.publish_device_data(signals=signals, point_id=int(return_table[0])) - - time.sleep(0.05) - - # read the last samples even though scan is finished already - while number_of_positions_planned > read_counter: - return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",") - logger.info(f"Read {read_counter} out of {number_of_positions_planned}") - # logger.info(f"{return_table}") - read_counter = read_counter + 1 - - signals = self._get_signals_from_table(return_table) - self.publish_device_data(signals=signals, point_id=int(return_table[0])) - - self.get_device_manager().connector.set( - MessageEndpoints.device_status("rt_scan"), - messages.DeviceStatusMessage( - device="rt_scan", status=0, metadata=self.readout_metadata - ), - ) - - logger.info( - "Flomni statistics: Average of all standard deviations: x" - f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y" - f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}" - ) - - def publish_device_data(self, signals, point_id): - self.get_device_manager().connector.set_and_publish( - MessageEndpoints.device_read("rt_flomni"), - messages.DeviceMessage( - signals=signals, metadata={"point_id": point_id, **self.readout_metadata} - ), - ) - - def start_readout(self): - readout = threading.Thread(target=self.read_positions_from_sampler) - readout.start() - - def kickoff(self, metadata): - self.readout_metadata = metadata - while not self._min_scan_buffer_reached: - time.sleep(0.001) - self.start_scan() - time.sleep(0.1) - self.start_readout() - class RtFlomniReadbackSignal(RtReadbackSignal): @retry_once @@ -658,7 +585,7 @@ class RtFlomniMotor(Device, PositionerBase): self.axis_Id = axis_Id self.sign = sign self.controller = RtFlomniController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) self.device_manager = device_manager @@ -686,6 +613,10 @@ class RtFlomniMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) @@ -797,9 +728,6 @@ class RtFlomniMotor(Device, PositionerBase): else: raise TypeError(f"Expected value of type int but received {type(val)}") - def kickoff(self, metadata, **kwargs) -> None: - self.controller.kickoff(metadata) - @property def egu(self): """The engineering units (EGU) for positions""" @@ -811,9 +739,181 @@ class RtFlomniMotor(Device, PositionerBase): return super().stop(success=success) +class RtFlomniFlyer(Device): + USER_ACCESS = ["controller"] + data = Cpt( + AsyncMultiSignal, + name="data", + signals=[ + "target_x", + "average_x_st_fzp", + "stdev_x_st_fzp", + "target_y", + "average_y_st_fzp", + "stdev_y_st_fzp", + "average_rotz", + "stdev_rotz", + "average_stdeviations_x_st_fzp", + "average_stdeviations_y_st_fzp", + ], + ndim=0, + max_size=10000, + async_update={"type": "add", "max_shape": [None]}, + ) + progress = Cpt(ProgressSignal, name="progress") + + def __init__( + self, + prefix="", + *, + name, + kind=None, + read_attrs=None, + configuration_attrs=None, + parent=None, + host="mpc2844.psi.ch", + port=2222, + socket_cls=SocketIO, + device_manager=None, + **kwargs, + ): + super().__init__( + prefix=prefix, + name=name, + kind=kind, + read_attrs=read_attrs, + configuration_attrs=configuration_attrs, + parent=parent, + **kwargs, + ) + self.shutdown_event = threading.Event() + self.controller = RtFlomniController( + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager + ) + self.average_stdeviations_x_st_fzp = 0 + self.average_stdeviations_y_st_fzp = 0 + self.average_lamni_angle = 0 + + def read_positions_from_sampler(self, status: DeviceStatus): + """ + Read the positions from the sampler and update the data signal. + This function runs in a separate thread and continuously checks the + scan status. + + Args: + status (DeviceStatus): The status object to update when the readout is complete. + """ + read_counter = 0 + self.average_stdeviations_x_st_fzp = 0 + self.average_stdeviations_y_st_fzp = 0 + self.average_lamni_angle = 0 + + mode, number_of_positions_planned, current_position_in_scan = ( + self.controller.get_scan_status() + ) + + # while scan is running + while mode > 0 and not self.shutdown_event.wait(0.01): + # 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.controller.get_scan_status() + ) + if current_position_in_scan > 5: + while current_position_in_scan > read_counter + 1: + return_table = ( + self.controller.socket_put_and_receive(f"r{read_counter}") + ).split(",") + logger.info(f"Read {read_counter} out of {number_of_positions_planned}") + read_counter = read_counter + 1 + signals = self._get_signals_from_table(return_table) + self.data.set(signals) + self.progress.put( + value=read_counter, + max_value=number_of_positions_planned, + done=(number_of_positions_planned == read_counter), + ) + logger.info(f"Updating progress: {read_counter} / {number_of_positions_planned}") + + + if self.shutdown_event.wait(0.05): + logger.info("Shutdown event set, stopping readout.") + # if we are here, the shutdown_event is set. We can exit the readout loop. + status.set_finished() + return + + # read the last samples even though scan is finished already + while number_of_positions_planned > read_counter and not self.shutdown_event.is_set(): + return_table = (self.controller.socket_put_and_receive(f"r{read_counter}")).split(",") + logger.info(f"Read {read_counter} out of {number_of_positions_planned}") + read_counter = read_counter + 1 + + signals = self._get_signals_from_table(return_table) + logger.info(f"Setting data: {signals}") + self.data.set(signals) + self.progress.put( + value=read_counter, + max_value=number_of_positions_planned, + done=(number_of_positions_planned == read_counter), + ) + logger.info(f"Updating progress: {read_counter} / {number_of_positions_planned}") + + + # NOTE: No need to set the status to failed if the shutdown_event is set. + # The stop() method will take care of that. + status.set_finished() + + if read_counter != 0: + logger.info( + "Flomni statistics: Average of all standard deviations: x" + f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y" + f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}" + ) + + + def _get_signals_from_table(self, return_table) -> dict: + self.average_stdeviations_x_st_fzp += float(return_table[4]) + self.average_stdeviations_y_st_fzp += float(return_table[7]) + signals = { + f"{self.name}_data_target_x": {"value": float(return_table[2])}, + f"{self.name}_data_average_x_st_fzp": {"value": float(return_table[3])}, + f"{self.name}_data_stdev_x_st_fzp": {"value": float(return_table[4])}, + f"{self.name}_data_target_y": {"value": float(return_table[5])}, + f"{self.name}_data_average_y_st_fzp": {"value": float(return_table[6])}, + f"{self.name}_data_stdev_y_st_fzp": {"value": float(return_table[7])}, + f"{self.name}_data_average_rotz": {"value": float(return_table[8])}, + f"{self.name}_data_stdev_rotz": {"value": float(return_table[9])}, + f"{self.name}_data_average_stdeviations_x_st_fzp": { + "value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1) + }, + f"{self.name}_data_average_stdeviations_y_st_fzp": { + "value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1) + }, + } + return signals + + def start_readout(self, status: DeviceStatus): + logger.info("Starting readout thread.") + readout = threading.Thread(target=self.read_positions_from_sampler, args=(status,)) + readout.start() + + def kickoff(self) -> DeviceStatus: + self.shutdown_event.clear() + while not self.controller._min_scan_buffer_reached and not self.shutdown_event.wait(0.001): + ... + self.controller.start_scan() + self.shutdown_event.wait(0.1) + status = DeviceStatus(self) + self.start_readout(status) + return status + + def stop(self, *, success=False): + self.shutdown_event.set() + return super().stop(success=success) + + if __name__ == "__main__": rtcontroller = RtFlomniController( - socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222 + socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222, device_manager=None ) rtcontroller.on() rtcontroller.laser_tracker_on() diff --git a/csaxs_bec/devices/omny/rt/rt_lamni_ophyd.py b/csaxs_bec/devices/omny/rt/rt_lamni_ophyd.py index 1d50e6e..026e4c2 100644 --- a/csaxs_bec/devices/omny/rt/rt_lamni_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_lamni_ophyd.py @@ -71,6 +71,7 @@ class RtLamniController(Controller): socket_cls=None, socket_host=None, socket_port=None, + device_manager=None, attr_name="", parent=None, labels=None, @@ -81,6 +82,7 @@ class RtLamniController(Controller): socket_cls=socket_cls, socket_host=socket_host, socket_port=socket_port, + device_manager=device_manager, attr_name=attr_name, parent=parent, labels=labels, @@ -92,11 +94,11 @@ class RtLamniController(Controller): def feedback_disable(self): self.socket_put("J0") logger.info("LamNI Feedback disabled.") - self.set_device_enabled("lsamx", True) - self.set_device_enabled("lsamy", True) - self.set_device_enabled("loptx", True) - self.set_device_enabled("lopty", True) - self.set_device_enabled("loptz", True) + self.set_device_read_write("lsamx", True) + self.set_device_read_write("lsamy", True) + self.set_device_read_write("loptx", True) + self.set_device_read_write("lopty", True) + self.set_device_read_write("loptz", True) def is_axis_moving(self, axis_Id) -> bool: # this checks that axis is on target @@ -150,25 +152,25 @@ class RtLamniController(Controller): # set these as closed loop target position self.socket_put(f"pa0,{x_curr:.4f}") self.socket_put(f"pa1,{y_curr:.4f}") - self.get_device_manager().devices.rtx.obj.user_setpoint.set_with_feedback_disabled(x_curr) - self.get_device_manager().devices.rty.obj.user_setpoint.set_with_feedback_disabled(y_curr) + self.dm.devices.rtx.obj.user_setpoint.set_with_feedback_disabled(x_curr) + self.dm.devices.rty.obj.user_setpoint.set_with_feedback_disabled(y_curr) self.socket_put("J5") logger.info("LamNI Feedback enabled (without reset).") - self.set_device_enabled("lsamx", False) - self.set_device_enabled("lsamy", False) - self.set_device_enabled("loptx", False) - self.set_device_enabled("lopty", False) - self.set_device_enabled("loptz", False) + self.set_device_read_write("lsamx", False) + self.set_device_read_write("lsamy", False) + self.set_device_read_write("loptx", False) + self.set_device_read_write("lopty", False) + self.set_device_read_write("loptz", False) @threadlocked def feedback_disable_and_even_reset_lamni_angle_interferometer(self): self.socket_put("J6") logger.info("LamNI Feedback disabled including the angular interferometer.") - self.set_device_enabled("lsamx", True) - self.set_device_enabled("lsamy", True) - self.set_device_enabled("loptx", True) - self.set_device_enabled("lopty", True) - self.set_device_enabled("loptz", True) + self.set_device_read_write("lsamx", True) + self.set_device_read_write("lsamy", True) + self.set_device_read_write("loptx", True) + self.set_device_read_write("lopty", True) + self.set_device_read_write("loptz", True) @threadlocked def clear_trajectory_generator(self): @@ -405,11 +407,11 @@ class RtLamniController(Controller): (self.socket_put_and_receive("J2")).split(",")[0] ) - self.set_device_enabled("lsamx", False) - self.set_device_enabled("lsamy", False) - self.set_device_enabled("loptx", False) - self.set_device_enabled("lopty", False) - self.set_device_enabled("loptz", False) + self.set_device_read_write("lsamx", False) + self.set_device_read_write("lsamy", False) + self.set_device_read_write("loptx", False) + self.set_device_read_write("lopty", False) + self.set_device_read_write("loptz", False) if interferometer_feedback_not_running == 1: logger.error( @@ -559,7 +561,7 @@ class RtLamniMotor(Device, PositionerBase): self.axis_Id = axis_Id self.sign = sign self.controller = RtLamniController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) self.device_manager = device_manager @@ -586,6 +588,10 @@ class RtLamniMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) diff --git a/csaxs_bec/devices/omny/rt/rt_omny_ophyd.py b/csaxs_bec/devices/omny/rt/rt_omny_ophyd.py index 5160f9f..ade0094 100644 --- a/csaxs_bec/devices/omny/rt/rt_omny_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_omny_ophyd.py @@ -1,8 +1,8 @@ +import builtins +import socket import threading import time from typing import List -import builtins -import socket import numpy as np from bec_lib import bec_logger, messages @@ -34,12 +34,15 @@ from csaxs_bec.devices.omny.rt.rt_ophyd import ( logger = bec_logger.logger + class RtOMNY_mirror_switchbox_Error(Exception): pass + class RtOMNY_Error(Exception): pass + class RtOMNYController(Controller): _axes_per_controller = 3 red = "\x1b[91m" @@ -87,6 +90,7 @@ class RtOMNYController(Controller): socket_cls=None, socket_host=None, socket_port=None, + device_manager=None, attr_name="", parent=None, labels=None, @@ -97,6 +101,7 @@ class RtOMNYController(Controller): socket_cls=socket_cls, socket_host=socket_host, socket_port=socket_port, + device_manager=device_manager, attr_name=attr_name, parent=parent, labels=labels, @@ -234,7 +239,7 @@ class RtOMNYController(Controller): "opt_amplitude1_neg": 3000, "opt_amplitude2_pos": 3000, "opt_amplitude2_neg": 3000, - } + }, } # def is_axis_moving(self, axis_Id) -> bool: @@ -261,42 +266,60 @@ class RtOMNYController(Controller): threading.Thread(target=send_positions, args=(self, positions), daemon=True).start() - def get_mirror_parameters(self,channel): + def get_mirror_parameters(self, channel): return self.mirror_parameters[channel] - def laser_tracker_check_and_wait_for_signalstrength(self): - self.get_device_manager().connector.send_client_info("Checking laser tracker...", scope="", show_asap=True) + self.get_device_manager().connector.send_client_info( + "Checking laser tracker...", scope="", show_asap=True + ) if not self.laser_tracker_check_enabled(): - print("laser_tracker_check_and_wait_for_signalstrength: The laser tracker is not even enabled.") + print( + "laser_tracker_check_and_wait_for_signalstrength: The laser tracker is not even enabled." + ) return - #first check on target + # first check on target self.laser_tracker_wait_on_target() - #when on target, check interferometer signal - signal = self._omny_interferometer_get_signalsample("ssi_4",0.1) + # when on target, check interferometer signal + signal = self._omny_interferometer_get_signalsample("ssi_4", 0.1) rtx = self.get_device_manager().devices.rtx min_signal = rtx.user_parameter.get("min_signal") low_signal = rtx.user_parameter.get("low_signal") wait_counter = 0 - while signal < min_signal and wait_counter<10: - self.get_device_manager().connector.send_client_info(f"The signal of the tracker {signal} is below the minimum required signal of {min_signal}. Waiting...", scope="laser_tracker_check_and_wait_for_signalstrength", show_asap=True) + while signal < min_signal and wait_counter < 10: + self.get_device_manager().connector.send_client_info( + f"The signal of the tracker {signal} is below the minimum required signal of {min_signal}. Waiting...", + scope="laser_tracker_check_and_wait_for_signalstrength", + show_asap=True, + ) - wait_counter+=1 + wait_counter += 1 time.sleep(0.2) - signal = self._omny_interferometer_get_signalsample("ssi_4",0.1) + signal = self._omny_interferometer_get_signalsample("ssi_4", 0.1) if signal < low_signal: - self.get_device_manager().connector.send_client_info(f"\x1b[91mThe signal of the tracker {signal} is below the low limit of {low_signal}. Auto readjustment...\x1b[0m", scope="laser_tracker_check_and_wait_for_signalstrength", show_asap=True) + self.get_device_manager().connector.send_client_info( + f"\x1b[91mThe signal of the tracker {signal} is below the low limit of {low_signal}. Auto readjustment...\x1b[0m", + scope="laser_tracker_check_and_wait_for_signalstrength", + show_asap=True, + ) - self.omny_interferometer_align_tracking() - self.get_device_manager().connector.send_client_info("Checking laser tracker completed.", scope="", show_asap=True) + self.omny_interferometer_align_tracking() + self.get_device_manager().connector.send_client_info( + "Checking laser tracker completed.", scope="", show_asap=True + ) def omny_interferometer_align_tracking(self): - mirror_channel=6 - signal = self._omny_interferometer_get_signalsample(self.mirror_parameters[mirror_channel]["opt_signalchannel"], self.mirror_parameters[mirror_channel]["opt_averaging_time"]) + mirror_channel = 6 + signal = self._omny_interferometer_get_signalsample( + self.mirror_parameters[mirror_channel]["opt_signalchannel"], + self.mirror_parameters[mirror_channel]["opt_averaging_time"], + ) if signal > self.mirror_parameters[mirror_channel]["opt_signal_stop"]: - print(f"Interferometer signal of axis {self.mirror_parameters[mirror_channel]['opt_mirrorname']} is good, no alignment needed.") + print( + f"Interferometer signal of axis {self.mirror_parameters[mirror_channel]['opt_mirrorname']} is good, no alignment needed." + ) else: self._omny_interferometer_switch_channel(mirror_channel) time.sleep(0.1) @@ -307,16 +330,19 @@ class RtOMNYController(Controller): self._omny_interferometer_switch_alloff() self.show_signal_strength_interferometer() - mirror_channel=-1 - - + mirror_channel = -1 def omny_interferometer_align_incoupling_angle(self): - mirror_channel=1 - signal = self._omny_interferometer_get_signalsample(self.mirror_parameters[mirror_channel]["opt_signalchannel"], self.mirror_parameters[mirror_channel]["opt_averaging_time"]) + mirror_channel = 1 + signal = self._omny_interferometer_get_signalsample( + self.mirror_parameters[mirror_channel]["opt_signalchannel"], + self.mirror_parameters[mirror_channel]["opt_averaging_time"], + ) if signal > self.mirror_parameters[mirror_channel]["opt_signal_stop"]: - print(f"Interferometer signal of axis {self.mirror_parameters[mirror_channel]['opt_mirrorname']} is good, no alignment needed.") + print( + f"Interferometer signal of axis {self.mirror_parameters[mirror_channel]['opt_mirrorname']} is good, no alignment needed." + ) else: self._omny_interferometer_switch_channel(mirror_channel) time.sleep(0.1) @@ -327,18 +353,17 @@ class RtOMNYController(Controller): self._omny_interferometer_switch_alloff() self.show_signal_strength_interferometer() - mirror_channel=-1 - + mirror_channel = -1 def _omny_interferometer_openloop_steps(self, channel, steps, amplitude): - if channel not in range(3,5): + if channel not in range(3, 5): raise RtOMNY_Error(f"invalid channel number {channel}.") if amplitude > 4090: amplitude = 4090 elif amplitude < 10: amplitude = 10 - + oshield = self.get_device_manager().devices.oshield oshield.obj.controller.move_open_loop_steps( @@ -351,7 +376,7 @@ class RtOMNYController(Controller): def _omny_interferometer_optimize(self, mirror_channel, channel): if mirror_channel == -1: raise RtOMNY_Error("no mirror channel selected") - #mirror channel is mirror number and channel is smaract channel, i.e. axis of the mirror + # mirror channel is mirror number and channel is smaract channel, i.e. axis of the mirror if channel == 3: steps_pos = self.mirror_parameters[mirror_channel]["opt_steps1_pos"] steps_neg = self.mirror_parameters[mirror_channel]["opt_steps1_neg"] @@ -365,67 +390,80 @@ class RtOMNYController(Controller): else: raise RtOMNY_Error(f"invalid channel number {channel}.") - previous_signal = self._omny_interferometer_get_signalsample(self.mirror_parameters[mirror_channel]["opt_signalchannel"], self.mirror_parameters[mirror_channel]["opt_averaging_time"]) + previous_signal = self._omny_interferometer_get_signalsample( + self.mirror_parameters[mirror_channel]["opt_signalchannel"], + self.mirror_parameters[mirror_channel]["opt_averaging_time"], + ) - min_begin = self.mirror_parameters[mirror_channel]["opt_signal_min_begin"] + min_begin = self.mirror_parameters[mirror_channel]["opt_signal_min_begin"] if previous_signal < min_begin: - #raise OMNY_rt_clientError("error1") #(f"Minimum signal of axis {self.mirror_parameters[mirror_channel]["opt_mirrorname"]} to start alignment not present.") + # raise OMNY_rt_clientError("error1") #(f"Minimum signal of axis {self.mirror_parameters[mirror_channel]["opt_mirrorname"]} to start alignment not present.") print(f"\rMinimum signal for auto alignment {min_begin} not reached.") return elif previous_signal > self.mirror_parameters[mirror_channel]["opt_signal_stop"]: - print(f"\rInterferometer signal of axis is good") # {self.mirror_parameters[mirror_channel]["opt_mirrorname"]} is good.") - return + print( + f"\rInterferometer signal of axis is good" + ) # {self.mirror_parameters[mirror_channel]["opt_mirrorname"]} is good.") + return else: direction = 1 - cycle_counter=0 - cycle_max=20 - reversal_counter=0 - reversal_max=4 - self.mirror_amplitutde_increase=0 + cycle_counter = 0 + cycle_max = 20 + reversal_counter = 0 + reversal_max = 4 + self.mirror_amplitutde_increase = 0 - current_sample = self._omny_interferometer_get_signalsample(self.mirror_parameters[mirror_channel]["opt_signalchannel"], self.mirror_parameters[mirror_channel]["opt_averaging_time"]) - max=current_sample + current_sample = self._omny_interferometer_get_signalsample( + self.mirror_parameters[mirror_channel]["opt_signalchannel"], + self.mirror_parameters[mirror_channel]["opt_averaging_time"], + ) + max = current_sample - while current_sample < self.mirror_parameters[mirror_channel]["opt_signal_stop"] and cycle_counter0: + if direction > 0: self._omny_interferometer_openloop_steps(channel, steps_pos, opt_amplitude_pos) verbose_str = f"channel {channel}, steps {steps_pos}" else: self._omny_interferometer_openloop_steps(channel, -steps_neg, opt_amplitude_neg) verbose_str = f"auto action {channel}, steps {-steps_pos}" - #print(f"Aligning axis ") #{self.mirror_parameters[mirror_channel]["opt_mirrorname"]}. Target: {self.mirror_parameters[mirror_channel]["opt_signal_stop"]}, current {current_sample}") + # print(f"Aligning axis ") #{self.mirror_parameters[mirror_channel]["opt_mirrorname"]}. Target: {self.mirror_parameters[mirror_channel]["opt_signal_stop"]}, current {current_sample}") - current_sample = self._omny_interferometer_get_signalsample(self.mirror_parameters[mirror_channel]["opt_signalchannel"], self.mirror_parameters[mirror_channel]["opt_averaging_time"]) + current_sample = self._omny_interferometer_get_signalsample( + self.mirror_parameters[mirror_channel]["opt_signalchannel"], + self.mirror_parameters[mirror_channel]["opt_averaging_time"], + ) opt_mirrorname = self.mirror_parameters[mirror_channel]["opt_mirrorname"] info_str = f"\rAuto aligning Channel {mirror_channel}, {opt_mirrorname}, Current signal: {current_sample:.0f}" - message=info_str +" (q)uit \r" - self.get_device_manager().connector.send_client_info(message, scope="_omny_interferometer_optimize", show_asap=True) - - - - if previous_signal>current_sample: - if direction<0: - steps_pos=int(steps_pos/2) - steps_neg=int(steps_neg/2) - if steps_pos<1: - steps_pos=1 - if steps_neg<1: - steps_neg=1 - direction=direction*(-1) - reversal_counter+=1 - previous_signal=current_sample - cycle_counter+=1 - - print(f"\r\nFinished aligning channel {channel} of mirror {mirror_channel}\n\r") # {self.mirror_parameters[mirror_channel]["opt_mirrorname"]}. Target: {self.mirror_parameters[mirror_channel]["opt_signal_stop"]}, current {current_sample}") - - + message = info_str + " (q)uit \r" + self.get_device_manager().connector.send_client_info( + message, scope="_omny_interferometer_optimize", show_asap=True + ) + if previous_signal > current_sample: + if direction < 0: + steps_pos = int(steps_pos / 2) + steps_neg = int(steps_neg / 2) + if steps_pos < 1: + steps_pos = 1 + if steps_neg < 1: + steps_neg = 1 + direction = direction * (-1) + reversal_counter += 1 + previous_signal = current_sample + cycle_counter += 1 + print( + f"\r\nFinished aligning channel {channel} of mirror {mirror_channel}\n\r" + ) # {self.mirror_parameters[mirror_channel]["opt_mirrorname"]}. Target: {self.mirror_parameters[mirror_channel]["opt_signal_stop"]}, current {current_sample}") def move_to_zero(self): self.socket_put("pa0,0") @@ -457,7 +495,7 @@ class RtOMNYController(Controller): if ret == 1: return True return False - + def feedback_is_running(self) -> bool: self.feedback_get_status_and_ssi() interferometer_feedback_not_running = int(self.ssi["feedback_error"]) @@ -466,7 +504,9 @@ class RtOMNYController(Controller): return True def feedback_enable_with_reset(self): - self.get_device_manager().connector.send_client_info(f"Enabling the feedback...", scope="", show_asap=True) + self.get_device_manager().connector.send_client_info( + f"Enabling the feedback...", scope="", show_asap=True + ) self.socket_put("J0") # disable feedback time.sleep(0.01) @@ -488,8 +528,10 @@ class RtOMNYController(Controller): osamroy = self.get_device_manager().devices.osamroy # the following read will also upate the angle in rt during readout readback = osamroy.obj.readback.get() - if (np.fabs(readback) > 0.1): - self.get_device_manager().connector.send_client_info(f"Rotating to zero", scope="feedback enable", show_asap=True) + if np.fabs(readback) > 0.1: + self.get_device_manager().connector.send_client_info( + f"Rotating to zero", scope="feedback enable", show_asap=True + ) osamroy.obj.move(0, wait=True) osamx = self.get_device_manager().devices.osamx @@ -514,16 +556,15 @@ class RtOMNYController(Controller): time.sleep(1.5) - self.set_device_enabled("osamx", False) - self.set_device_enabled("osamy", False) - self.set_device_enabled("ofzpx", False) - self.set_device_enabled("ofzpy", False) - self.set_device_enabled("oosax", False) - self.set_device_enabled("oosax", False) + self.set_device_read_write("osamx", False) + self.set_device_read_write("osamy", False) + self.set_device_read_write("ofzpx", False) + self.set_device_read_write("ofzpy", False) + self.set_device_read_write("oosax", False) + self.set_device_read_write("oosax", False) print("Feedback is running.") - @threadlocked def clear_trajectory_generator(self): self.socket_put("sc") @@ -534,16 +575,15 @@ class RtOMNYController(Controller): self.move_to_zero() self.socket_put("J0") - self.set_device_enabled("osamx", True) - self.set_device_enabled("osamy", True) - self.set_device_enabled("ofzpx", True) - self.set_device_enabled("ofzpy", True) - self.set_device_enabled("oosax", True) - self.set_device_enabled("oosax", True) + self.set_device_read_write("osamx", True) + self.set_device_read_write("osamy", True) + self.set_device_read_write("ofzpx", True) + self.set_device_read_write("ofzpy", True) + self.set_device_read_write("oosax", True) + self.set_device_read_write("oosax", True) print("rt feedback is now disabled.") - def set_rotation_angle(self, val: float) -> None: self.socket_put(f"a{val/180*np.pi}") @@ -578,12 +618,13 @@ class RtOMNYController(Controller): "enabled_z": bool(tracker_values[10]), } - def laser_tracker_on(self): - #update variables and enable if not yet active + # update variables and enable if not yet active if not self.laser_tracker_check_enabled(): logger.info("Enabling the laser tracker. Please wait...") - self.get_device_manager().connector.send_client_info(f"Enabling the laser tracker. Please wait...", scope="", show_asap=True) + self.get_device_manager().connector.send_client_info( + f"Enabling the laser tracker. Please wait...", scope="", show_asap=True + ) tracker_intensity = self.tracker_info["tracker_intensity"] if ( @@ -609,7 +650,6 @@ class RtOMNYController(Controller): logger.info("Laser tracker running!") print("Laser tracker running!") - def laser_tracker_check_enabled(self) -> bool: self.laser_update_tracker_info() if self.tracker_info["enabled_z"] and self.tracker_info["enabled_y"]: @@ -628,11 +668,10 @@ class RtOMNYController(Controller): return True return False - def laser_tracker_wait_on_target(self): max_repeat = 15 count = 0 - while not self.laser_tracker_check_on_target() and count dict: self.average_stdeviations_x_st_fzp += float(return_table[16]) self.average_stdeviations_y_st_fzp += float(return_table[18]) @@ -831,7 +866,6 @@ class RtOMNYController(Controller): "stdev_x_st_fzp": {"value": float(return_table[16])}, "average_y_st_fzp": {"value": float(return_table[17])}, "stdev_y_st_fzp": {"value": float(return_table[18])}, - "average_stdeviations_x_st_fzp": { "value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1) }, @@ -840,7 +874,7 @@ class RtOMNYController(Controller): }, } return signals - + @threadlocked def start_scan(self): if not self.feedback_is_running(): @@ -862,7 +896,6 @@ class RtOMNYController(Controller): # start a point-by-point scan (for cont scan in flomni it would be "sa") self.socket_put_and_receive("sd") - @retry_once @threadlocked def get_scan_status(self): @@ -881,7 +914,6 @@ class RtOMNYController(Controller): current_position_in_scan = int(float(return_table[2])) return (mode, number_of_positions_planned, current_position_in_scan) - def get_device_manager(self): for axis in self._axis: if hasattr(axis, "device_manager") and axis.device_manager: @@ -953,8 +985,9 @@ class RtOMNYController(Controller): "OMNY statistics: Average of all standard deviations [nm]: x" f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y" f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}.", - scope="", show_asap=True) - + scope="", + show_asap=True, + ) def publish_device_data(self, signals, point_id): self.get_device_manager().connector.set_and_publish( @@ -1068,7 +1101,7 @@ class RtOMNYMotor(Device, PositionerBase): self.axis_Id = axis_Id self.sign = sign self.controller = RtOMNYController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric) self.device_manager = device_manager @@ -1096,6 +1129,10 @@ class RtOMNYMotor(Device, PositionerBase): self.low_limit_travel.put(limits[0]) self.high_limit_travel.put(limits[1]) + def wait_for_connection(self, timeout: int = 10, **kwargs) -> None: + """Wait for the device to be connected.""" + self.controller.on(timeout=timeout) + @property def limits(self): return (self.low_limit_travel.get(), self.high_limit_travel.get()) @@ -1182,7 +1219,6 @@ class RtOMNYMotor(Device, PositionerBase): return status - @property def axis_Id(self): return self._axis_Id_alpha @@ -1227,7 +1263,7 @@ class RtOMNYMotor(Device, PositionerBase): if __name__ == "__main__": rtcontroller = RtOMNYController( - socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222 + socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222, device_manager=None ) rtcontroller.on() rtcontroller.laser_tracker_on() diff --git a/csaxs_bec/devices/smaract/smaract_controller.py b/csaxs_bec/devices/smaract/smaract_controller.py index 772b678..8b2e513 100644 --- a/csaxs_bec/devices/smaract/smaract_controller.py +++ b/csaxs_bec/devices/smaract/smaract_controller.py @@ -93,6 +93,7 @@ class SmaractController(Controller): socket_cls=None, socket_host=None, socket_port=None, + device_manager=None, attr_name="", labels=None, ): @@ -102,6 +103,7 @@ class SmaractController(Controller): socket_cls=socket_cls, socket_host=socket_host, socket_port=socket_port, + device_manager=device_manager, attr_name=attr_name, parent=parent, labels=labels, diff --git a/csaxs_bec/devices/smaract/smaract_ophyd.py b/csaxs_bec/devices/smaract/smaract_ophyd.py index ff088ac..0e810c2 100644 --- a/csaxs_bec/devices/smaract/smaract_ophyd.py +++ b/csaxs_bec/devices/smaract/smaract_ophyd.py @@ -123,10 +123,11 @@ class SmaractMotor(Device, PositionerBase): limits=None, sign=1, socket_cls=SocketIO, + device_manager=None, **kwargs, ): self.controller = SmaractController( - socket_cls=socket_cls, socket_host=host, socket_port=port + socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager ) self.axis_Id = axis_Id self.sign = sign diff --git a/csaxs_bec/scans/flomni_fermat_scan.py b/csaxs_bec/scans/flomni_fermat_scan.py index 045ef1e..ae4ed1d 100644 --- a/csaxs_bec/scans/flomni_fermat_scan.py +++ b/csaxs_bec/scans/flomni_fermat_scan.py @@ -26,12 +26,12 @@ import numpy as np 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 bec_server.scan_server.scans import AsyncFlyScanBase logger = bec_logger.logger -class FlomniFermatScan(SyncFlyScanBase): +class FlomniFermatScan(AsyncFlyScanBase): scan_name = "flomni_fermat_scan" scan_type = "fly" required_kwargs = ["fovx", "fovy", "exp_time", "step", "angle"] @@ -97,6 +97,7 @@ class FlomniFermatScan(SyncFlyScanBase): logger.warning("The zshift is smaller than -100 um. It will be limited to -100 um.") self.zshift = -100 self.flomni_rotation_status = None + self.flyer_num_pos = 0 def initialize(self): self.scan_motors = [] @@ -107,10 +108,6 @@ class FlomniFermatScan(SyncFlyScanBase): self.positions, corridor_size=self.optim_trajectory_corridor ) - @property - def monitor_sync(self): - return "rt_flomni" - def reverse_trajectory(self): """ Reverse the trajectory. Every other scan should be reversed to @@ -135,13 +132,13 @@ class FlomniFermatScan(SyncFlyScanBase): if flip_axes: self.positions = np.flipud(self.positions) - self.num_pos = len(self.positions) + self.flyer_num_pos = len(self.positions) self._check_min_positions() def _check_min_positions(self): - if self.num_pos < 20: + if self.flyer_num_pos < 20: raise ScanAbortion( - f"The number of positions must exceed 20. Currently: {self.num_pos}." + f"The number of positions must exceed 20. Currently: {self.flyer_num_pos}." ) def _prepare_setup(self): @@ -150,6 +147,8 @@ class FlomniFermatScan(SyncFlyScanBase): yield from self.stubs.send_rpc_and_wait("rty", "set", self.positions[0][1]) + yield from self.stubs.scan_report_instruction({"device_progress": ["rt_flyer"]}) + def _prepare_setup_part2(self): if self.flomni_rotation_status: self.flomni_rotation_status.wait() @@ -169,12 +168,16 @@ class FlomniFermatScan(SyncFlyScanBase): tracker_signal_status = yield from self.stubs.send_rpc_and_wait( "rtx", "controller.laser_tracker_check_signalstrength" ) - #self.device_manager.connector.send_client_info(tracker_signal_status) + # self.device_manager.connector.send_client_info(tracker_signal_status) if tracker_signal_status == "low": self.device_manager.connector.raise_alarm( severity=0, alarm_type="LaserTrackerSignalStrength", - source={"device": "rtx", "reason": "low signal strength", "method": "_prepare_setup_part2"}, + source={ + "device": "rtx", + "reason": "low signal strength", + "method": "_prepare_setup_part2", + }, metadata={}, msg="Signal strength of the laser tracker is low, sufficient to continue. Realignment recommended!", ) @@ -276,26 +279,13 @@ class FlomniFermatScan(SyncFlyScanBase): return np.array(positions) def scan_core(self): - # use a device message to receive the scan number and - # scan ID before sending the message to the device server - yield from self.stubs.kickoff(device="rtx") - while True: - yield from self.stubs.read(group="monitored") - status = self.connector.get(MessageEndpoints.device_status("rt_scan")) - if status: - status_id = status.content.get("status", 1) - request_id = status.metadata.get("RID") - if status_id == 0 and self.metadata.get("RID") == request_id: - break - if status_id == 2 and self.metadata.get("RID") == request_id: - raise ScanAbortion( - "An error occured during the flomni readout:" - f" {status.metadata.get('error')}" - ) - + status = yield from self.stubs.kickoff(device="rt_flyer", wait=False) + while not status.done: + yield from self.stubs.read(group="monitored", point_id=self.point_id) time.sleep(1) + self.point_id += 1 logger.debug("reading monitors") - # yield from self.device_rpc("rtx", "controller.kickoff") + self.num_pos = self.point_id def move_to_start(self): """return to the start position""" diff --git a/tests/tests_devices/test_fupr_ophyd.py b/tests/tests_devices/test_fupr_ophyd.py index 615e088..2683a14 100644 --- a/tests/tests_devices/test_fupr_ophyd.py +++ b/tests/tests_devices/test_fupr_ophyd.py @@ -7,10 +7,15 @@ from csaxs_bec.devices.omny.galil.fupr_ophyd import FuprGalilController, FuprGal @pytest.fixture -def fsamroy(): +def fsamroy(dm_with_devices): FuprGalilController._reset_controller() fsamroy_motor = FuprGalilMotor( - "A", name="fsamroy", host="mpc2680.psi.ch", port=8081, socket_cls=SocketMock + "A", + name="fsamroy", + host="mpc2680.psi.ch", + port=8081, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) fsamroy_motor.controller.on() assert isinstance(fsamroy_motor.controller, FuprGalilController) diff --git a/tests/tests_devices/test_galil.py b/tests/tests_devices/test_galil.py index b76854f..6ae437b 100644 --- a/tests/tests_devices/test_galil.py +++ b/tests/tests_devices/test_galil.py @@ -8,10 +8,15 @@ from csaxs_bec.devices.omny.galil.lgalil_ophyd import LamniGalilController, Lamn @pytest.fixture(scope="function") -def leyey(): +def leyey(dm_with_devices): LamniGalilController._reset_controller() leyey_motor = LamniGalilMotor( - "H", name="leyey", host="mpc2680.psi.ch", port=8081, socket_cls=SocketMock + "H", + name="leyey", + host="mpc2680.psi.ch", + port=8081, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) leyey_motor.controller.on() yield leyey_motor @@ -20,10 +25,15 @@ def leyey(): @pytest.fixture(scope="function") -def leyex(): +def leyex(dm_with_devices): LamniGalilController._reset_controller() leyex_motor = LamniGalilMotor( - "A", name="leyey", host="mpc2680.psi.ch", port=8081, socket_cls=SocketMock + "A", + name="leyey", + host="mpc2680.psi.ch", + port=8081, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) leyex_motor.controller.on() yield leyex_motor diff --git a/tests/tests_devices/test_galil_flomni.py b/tests/tests_devices/test_galil_flomni.py index fe24030..f3dcbc2 100644 --- a/tests/tests_devices/test_galil_flomni.py +++ b/tests/tests_devices/test_galil_flomni.py @@ -7,10 +7,15 @@ from csaxs_bec.devices.omny.galil.fgalil_ophyd import FlomniGalilController, Flo @pytest.fixture(scope="function") -def leyey(): +def leyey(dm_with_devices): FlomniGalilController._reset_controller() leyey_motor = FlomniGalilMotor( - "H", name="leyey", host="mpc2680.psi.ch", port=8081, socket_cls=SocketMock + "H", + name="leyey", + host="mpc2680.psi.ch", + port=8081, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) leyey_motor.controller.on() yield leyey_motor @@ -19,10 +24,15 @@ def leyey(): @pytest.fixture(scope="function") -def leyex(): +def leyex(dm_with_devices): FlomniGalilController._reset_controller() leyex_motor = FlomniGalilMotor( - "H", name="leyey", host="mpc2680.psi.ch", port=8081, socket_cls=SocketMock + "H", + name="leyey", + host="mpc2680.psi.ch", + port=8081, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) leyex_motor.controller.on() yield leyex_motor @@ -157,11 +167,16 @@ def test_find_reference(leyex, axis_nr, socket_put_messages, socket_get_messages ], ) def test_fosaz_light_curtain_is_triggered( - axis_Id, socket_put_messages, socket_get_messages, triggered + axis_Id, socket_put_messages, socket_get_messages, triggered, dm_with_devices ): """test that the light curtain is triggered""" fosaz = FlomniGalilMotor( - axis_Id, name="fosaz", host="mpc2680.psi.ch", port=8081, socket_cls=SocketMock + axis_Id, + name="fosaz", + host="mpc2680.psi.ch", + port=8081, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) fosaz.controller.on() fosaz.controller.sock.flush_buffer() diff --git a/tests/tests_devices/test_npoint_piezo.py b/tests/tests_devices/test_npoint_piezo.py index a8157d7..85e6e54 100644 --- a/tests/tests_devices/test_npoint_piezo.py +++ b/tests/tests_devices/test_npoint_piezo.py @@ -16,7 +16,10 @@ def controller(): """ with mock.patch("ophyd_devices.utils.socket.SocketIO") as socket_cls: controller = NPointController( - socket_cls=socket_cls, socket_host="localhost", socket_port=1234 + socket_cls=socket_cls, + socket_host="localhost", + socket_port=1234, + device_manager=mock.MagicMock(), ) controller.on() controller.sock.reset_mock() @@ -25,13 +28,18 @@ def controller(): @pytest.fixture -def npointx(): +def npointx(dm_with_devices): """ Fixture to create a NPointAxis object. """ controller = mock.MagicMock() npointx = NPointAxis( - axis_Id="A", name="npointx", host="localhost", port=1234, socket_cls=controller + axis_Id="A", + name="npointx", + host="localhost", + port=1234, + socket_cls=controller, + device_manager=dm_with_devices, ) npointx.controller.on() npointx.controller.sock.reset_mock() @@ -107,13 +115,18 @@ def test_axis_get_in(npointx, axis, msg_in, msg_out): npointx.controller.sock.put.assert_called_once_with(msg_in) -def test_axis_out_of_range(controller): +def test_axis_out_of_range(dm_with_devices): """ Test that an error is raised when trying to create an NPointAxis object with an invalid axis ID. """ with pytest.raises(ValueError): npointx = NPointAxis( - axis_Id="G", name="npointx", host="localhost", port=1234, socket_cls=mock.MagicMock() + axis_Id="G", + name="npointx", + host="localhost", + port=1234, + socket_cls=mock.MagicMock(), + device_manager=dm_with_devices, ) diff --git a/tests/tests_devices/test_rt_flomni.py b/tests/tests_devices/test_rt_flomni.py index e6f7ae8..471d1b5 100644 --- a/tests/tests_devices/test_rt_flomni.py +++ b/tests/tests_devices/test_rt_flomni.py @@ -11,7 +11,11 @@ from csaxs_bec.devices.omny.rt.rt_ophyd import RtError def rt_flomni(): RtFlomniController._reset_controller() rt_flomni = RtFlomniController( - name="rt_flomni", socket_cls=SocketMock, socket_host="localhost", socket_port=8081 + name="rt_flomni", + socket_cls=SocketMock, + socket_host="localhost", + socket_port=8081, + device_manager=mock.MagicMock(), ) with mock.patch.object(rt_flomni, "get_device_manager"): with mock.patch.object(rt_flomni, "sock"): @@ -76,16 +80,16 @@ def test_move_samx_to_scan_region(rt_flomni): def test_feedback_enable_without_reset(rt_flomni): - with mock.patch.object(rt_flomni, "set_device_enabled") as set_device_enabled: + with mock.patch.object(rt_flomni, "set_device_read_write") as set_device_read_write: with mock.patch.object(rt_flomni, "feedback_is_running", return_value=True): with mock.patch.object(rt_flomni, "laser_tracker_on") as laser_tracker_on: rt_flomni.feedback_enable_without_reset() laser_tracker_on.assert_called_once() assert mock.call(b"l3\n") in rt_flomni.sock.put.mock_calls - assert mock.call("fsamx", False) in set_device_enabled.mock_calls - assert mock.call("fsamy", False) in set_device_enabled.mock_calls - assert mock.call("foptx", False) in set_device_enabled.mock_calls - assert mock.call("fopty", False) in set_device_enabled.mock_calls + assert mock.call("fsamx", False) in set_device_read_write.mock_calls + assert mock.call("fsamy", False) in set_device_read_write.mock_calls + assert mock.call("foptx", False) in set_device_read_write.mock_calls + assert mock.call("fopty", False) in set_device_read_write.mock_calls def test_feedback_enable_without_reset_raises(rt_flomni): diff --git a/tests/tests_devices/test_smaract.py b/tests/tests_devices/test_smaract.py index f4ef4de..3e77ca0 100644 --- a/tests/tests_devices/test_smaract.py +++ b/tests/tests_devices/test_smaract.py @@ -10,19 +10,27 @@ from csaxs_bec.devices.smaract.smaract_ophyd import SmaractMotor @pytest.fixture -def controller(): +def controller(dm_with_devices): SmaractController._reset_controller() - controller = SmaractController(socket_cls=SocketMock, socket_host="dummy", socket_port=123) + controller = SmaractController( + socket_cls=SocketMock, socket_host="dummy", socket_port=123, device_manager=dm_with_devices + ) controller.on() controller.sock.flush_buffer() yield controller @pytest.fixture -def lsmarA(): +def lsmarA(dm_with_devices): SmaractController._reset_controller() motor_a = SmaractMotor( - "A", name="lsmarA", host="mpc2680.psi.ch", port=8085, sign=1, socket_cls=SocketMock + "A", + name="lsmarA", + host="mpc2680.psi.ch", + port=8085, + sign=1, + socket_cls=SocketMock, + device_manager=dm_with_devices, ) motor_a.controller.on() motor_a.controller.sock.flush_buffer()