Compare commits
10 Commits
fix/refact
...
sastt_conf
| Author | SHA1 | Date | |
|---|---|---|---|
| d99b44b619 | |||
|
|
dbab981ac2 | ||
|
|
bdd7f1767f | ||
| 0f41648053 | |||
| ec45bb4c33 | |||
| ac8177a132 | |||
| 36e8d87411 | |||
| f56a834db5 | |||
| 90d2c99c4a | |||
| 6a4bfc73f6 |
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_ipython_client.plugins.flomni.flomni import Flomni
|
||||
from bec_ipython_client.plugins.flomni import Flomni
|
||||
|
||||
|
||||
class XrayEyeAlign:
|
||||
|
||||
@@ -61,25 +61,3 @@ bec._beamline_mixin._bl_info_register(OperatorInfo)
|
||||
# SETUP PROMPTS
|
||||
bec._ip.prompts.session_name = _session_name
|
||||
bec._ip.prompts.status = 1
|
||||
|
||||
# REGISTER BEAMLINE CHECKS
|
||||
from bec_lib.bl_conditions import (
|
||||
FastOrbitFeedbackCondition,
|
||||
LightAvailableCondition,
|
||||
ShutterCondition,
|
||||
)
|
||||
|
||||
if "sls_machine_status" in dev:
|
||||
print("Registering light available condition for SLS machine status")
|
||||
_light_available_condition = LightAvailableCondition(dev.sls_machine_status)
|
||||
bec.bl_checks.register(_light_available_condition)
|
||||
|
||||
if "x12sa_es1_shutter_status" in dev:
|
||||
print("Registering shutter condition for X12SA ES1 shutter status")
|
||||
_shutter_condition = ShutterCondition(dev.x12sa_es1_shutter_status)
|
||||
bec.bl_checks.register(_shutter_condition)
|
||||
|
||||
# if hasattr(dev, "sls_fast_orbit_feedback"):
|
||||
# print("Registering fast orbit feedback condition for SLS fast orbit feedback")
|
||||
# _fast_orbit_feedback_condition = FastOrbitFeedbackCondition(dev.sls_fast_orbit_feedback)
|
||||
# bec.bl_checks.register(_fast_orbit_feedback_condition)
|
||||
|
||||
@@ -115,7 +115,7 @@ samy:
|
||||
softwareTrigger: false
|
||||
micfoc:
|
||||
description: Focusing motor of Microscope stage
|
||||
deviceClass: ophyd_devices.devices.EpicsMotorEx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES06
|
||||
motor_resolution: 0.00125
|
||||
@@ -133,7 +133,7 @@ micfoc:
|
||||
softwareTrigger: false
|
||||
owis_samx:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.EpicsMotorEx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES01
|
||||
motor_resolution: 0.00125
|
||||
@@ -151,7 +151,7 @@ owis_samx:
|
||||
softwareTrigger: false
|
||||
owis_samy:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.EpicsMotorEx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES02
|
||||
motor_resolution: 0.00125
|
||||
@@ -169,7 +169,7 @@ owis_samy:
|
||||
softwareTrigger: false
|
||||
rotx:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.EpicsMotorEx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES05
|
||||
motor_resolution: 0.0025
|
||||
@@ -190,7 +190,7 @@ rotx:
|
||||
softwareTrigger: false
|
||||
roty:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.EpicsMotorEx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES04
|
||||
motor_resolution: 0.0025
|
||||
|
||||
@@ -365,18 +365,6 @@ 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 ############################
|
||||
############################################################
|
||||
|
||||
96
csaxs_bec/device_configs/sastt.yaml
Normal file
96
csaxs_bec/device_configs/sastt.yaml
Normal file
@@ -0,0 +1,96 @@
|
||||
samx:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES01
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.0625
|
||||
velocity: 10
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- owis_samx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
samy:
|
||||
description: Owis motor stage samy
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES02
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.0625
|
||||
velocity: 10
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 0
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- owis_samx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
rotx:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES03
|
||||
motor_resolution: 0.0025
|
||||
base_velocity: 0.5
|
||||
velocity: 7.5
|
||||
backlash_distance: 0.25
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 1
|
||||
limits:
|
||||
- -0.1
|
||||
- 0.1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- rotx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
roty:
|
||||
description: Rotation stage roty
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES04
|
||||
motor_resolution: 0.0025
|
||||
base_velocity: 0.5
|
||||
velocity: 7.5
|
||||
backlash_distance: 0.25
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 0
|
||||
limits:
|
||||
- -0.1
|
||||
- 0.1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- roty
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
micfoc:
|
||||
description: Focusing motor of Microscope stage
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES05
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.25
|
||||
velocity: 2.5
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.4
|
||||
user_offset_dir: 0
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- micfoc
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
@@ -37,8 +37,7 @@ import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import DeviceStatus
|
||||
from ophyd_devices import CompareStatus, TransitionStatus
|
||||
from ophyd_devices import CompareStatus, DeviceStatus, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
|
||||
@@ -143,7 +143,7 @@ class StatusBitsCompareStatus(SubscriptionStatus):
|
||||
self._add_delay = add_delay or 0
|
||||
self._raise_states = raise_states or []
|
||||
super().__init__(
|
||||
device=signal,
|
||||
obj=signal,
|
||||
callback=self._compare_callback,
|
||||
timeout=timeout,
|
||||
settle_time=settle_time,
|
||||
|
||||
@@ -212,7 +212,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -59,12 +59,12 @@ class GalilController(Controller):
|
||||
"all_axes_referenced",
|
||||
]
|
||||
|
||||
OKBLUE = '\033[94m'
|
||||
OKCYAN = '\033[96m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
OKBLUE = "\033[94m"
|
||||
OKCYAN = "\033[96m"
|
||||
OKGREEN = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
FAIL = "\033[91m"
|
||||
ENDC = "\033[0m"
|
||||
|
||||
@threadlocked
|
||||
def socket_put(self, val: str) -> None:
|
||||
@@ -115,29 +115,29 @@ class GalilController(Controller):
|
||||
|
||||
def axis_is_referenced(self, axis_Id_numeric) -> bool:
|
||||
return bool(float(self.socket_put_and_receive(f"MG axisref[{axis_Id_numeric}]").strip()))
|
||||
|
||||
|
||||
def folerr_status(self, axis_Id_numeric) -> bool:
|
||||
return bool(float(self.socket_put_and_receive(f"MG folaxerr[{axis_Id_numeric}]").strip()))
|
||||
|
||||
def motor_temperature(self, axis_Id_numeric) -> float:
|
||||
#this is only valid for omny. consider moving to ogalil
|
||||
# this is only valid for omny. consider moving to ogalil
|
||||
voltage = float(self.socket_put_and_receive(f"MG @AN[{axis_Id_numeric+1}]").strip())
|
||||
voltage2 = float(self.socket_put_and_receive(f"MG @AN[{axis_Id_numeric+1}]").strip())
|
||||
if voltage2 < voltage:
|
||||
voltage = voltage2
|
||||
# convert from [-10,10]V to [0,300]degC
|
||||
temperature_degC = round((voltage+10.0) / 20.0 * 300.0, 1)
|
||||
temperature_degC = round((voltage + 10.0) / 20.0 * 300.0, 1)
|
||||
|
||||
#the motors of the parking station have a different offset
|
||||
#the range is reduced, so if at the limit, we show an extreme value
|
||||
# the motors of the parking station have a different offset
|
||||
# the range is reduced, so if at the limit, we show an extreme value
|
||||
if self.sock.port == 8082:
|
||||
#controller 2
|
||||
# controller 2
|
||||
if axis_Id_numeric == 6:
|
||||
temperature_degC = round((voltage+10.0-11.4) / 20.0 * 300.0, 1)
|
||||
temperature_degC = round((voltage + 10.0 - 11.4) / 20.0 * 300.0, 1)
|
||||
if voltage > 9.9:
|
||||
temperature_degC = 300
|
||||
if axis_Id_numeric == 7:
|
||||
temperature_degC = round((voltage+.0-12) / 20.0 * 300.0, 1)
|
||||
temperature_degC = round((voltage + 0.0 - 12) / 20.0 * 300.0, 1)
|
||||
if voltage > 9.9:
|
||||
temperature_degC = 300
|
||||
return temperature_degC
|
||||
@@ -147,16 +147,15 @@ class GalilController(Controller):
|
||||
Check if all axes are referenced.
|
||||
"""
|
||||
return bool(float(self.socket_put_and_receive("MG allaxref").strip()))
|
||||
|
||||
def _omny_get_microstep_position(self,axis_Id):
|
||||
|
||||
def _omny_get_microstep_position(self, axis_Id):
|
||||
return float(self.socket_put_and_receive(f"MG _TD{axis_Id}").strip())
|
||||
|
||||
|
||||
def _omny_get_reference_limit(self,axis_Id):
|
||||
|
||||
def _omny_get_reference_limit(self, axis_Id):
|
||||
get_axis_no = float(self.socket_put_and_receive(f"MG frmmv").strip())
|
||||
if(get_axis_no>0):
|
||||
if get_axis_no > 0:
|
||||
reference_is_before = float(self.socket_put_and_receive(f"MG _FL{axis_Id}").strip())
|
||||
elif(get_axis_no<0):
|
||||
elif get_axis_no < 0:
|
||||
reference_is_before = float(self.socket_put_and_receive(f"MG _BL{axis_Id}").strip())
|
||||
else:
|
||||
reference_is_before = 0
|
||||
@@ -187,7 +186,11 @@ class GalilController(Controller):
|
||||
while self.is_axis_moving(None, axis_Id_numeric):
|
||||
time.sleep(0.01)
|
||||
if verbose:
|
||||
self.get_device_manager().connector.send_client_info(f"Current microstep position {self._omny_get_microstep_position(axis_Id):.0f}", scope="drive axis to limit", show_asap=True)
|
||||
self.device_manager.connector.send_client_info(
|
||||
f"Current microstep position {self._omny_get_microstep_position(axis_Id):.0f}",
|
||||
scope="drive axis to limit",
|
||||
show_asap=True,
|
||||
)
|
||||
time.sleep(0.5)
|
||||
|
||||
# check if we actually hit the limit
|
||||
@@ -201,13 +204,7 @@ class GalilController(Controller):
|
||||
else:
|
||||
print("Limit reached.")
|
||||
|
||||
def get_device_manager(self):
|
||||
for axis in self._axis:
|
||||
if hasattr(axis, "device_manager") and axis.device_manager:
|
||||
return axis.device_manager
|
||||
raise BECConfigError("Could not access the device_manager")
|
||||
|
||||
def find_reference(self, axis_Id_numeric: int, verbose=0, raise_error = 1) -> None:
|
||||
def find_reference(self, axis_Id_numeric: int, verbose=0, raise_error=1) -> None:
|
||||
"""
|
||||
Find the reference of an axis.
|
||||
|
||||
@@ -224,7 +221,11 @@ class GalilController(Controller):
|
||||
while self.is_axis_moving(None, axis_Id_numeric):
|
||||
time.sleep(0.1)
|
||||
if verbose:
|
||||
self.get_device_manager().connector.send_client_info(f"Current microstep position {self._omny_get_microstep_position(axis_Id):.0f} reference is before {self._omny_get_reference_limit(axis_Id)}", scope="find axis reference", show_asap=True)
|
||||
self.device_manager.connector.send_client_info(
|
||||
f"Current microstep position {self._omny_get_microstep_position(axis_Id):.0f} reference is before {self._omny_get_reference_limit(axis_Id)}",
|
||||
scope="find axis reference",
|
||||
show_asap=True,
|
||||
)
|
||||
time.sleep(0.5)
|
||||
|
||||
if not self.axis_is_referenced(axis_Id_numeric):
|
||||
@@ -236,7 +237,6 @@ class GalilController(Controller):
|
||||
logger.info(f"Successfully found reference of axis {axis_Id_numeric}.")
|
||||
print(f"Successfully found reference of axis {axis_Id_numeric}.")
|
||||
|
||||
|
||||
def show_running_threads(self) -> None:
|
||||
t = PrettyTable()
|
||||
t.title = f"Threads on {self.sock.host}:{self.sock.port}"
|
||||
@@ -251,7 +251,7 @@ class GalilController(Controller):
|
||||
|
||||
def is_motor_on(self, axis_Id) -> bool:
|
||||
return not bool(float(self.socket_put_and_receive(f"MG _MO{axis_Id}").strip()))
|
||||
|
||||
|
||||
def get_motor_limit_switch(self, axis_Id) -> list:
|
||||
"""
|
||||
Get the status of the motor limit switches.
|
||||
@@ -269,14 +269,7 @@ class GalilController(Controller):
|
||||
def describe(self) -> None:
|
||||
t = PrettyTable()
|
||||
t.title = f"{self.__class__.__name__} on {self.sock.host}:{self.sock.port}"
|
||||
field_names = [
|
||||
"Axis",
|
||||
"Name",
|
||||
"Referenced",
|
||||
"Motor On",
|
||||
"Limits",
|
||||
"Position",
|
||||
]
|
||||
field_names = ["Axis", "Name", "Referenced", "Motor On", "Limits", "Position"]
|
||||
# in case of OMNY
|
||||
if self.sock.host == "mpc3217.psi.ch":
|
||||
field_names.append("Temperature")
|
||||
@@ -286,7 +279,7 @@ class GalilController(Controller):
|
||||
axis = self._axis[ax]
|
||||
if axis is not None:
|
||||
if self.sock.host == "mpc3217.psi.ch":
|
||||
#case of omny. possibly consider moving to ogalil
|
||||
# case of omny. possibly consider moving to ogalil
|
||||
motor_on = self.is_motor_on(axis.axis_Id)
|
||||
if motor_on == True:
|
||||
motor_on = self.WARNING + "ON" + self.ENDC
|
||||
@@ -299,7 +292,7 @@ class GalilController(Controller):
|
||||
else:
|
||||
folerr_status = "False"
|
||||
position = axis.readback.read().get(axis.name).get("value")
|
||||
position = f'{position:.3f}'
|
||||
position = f"{position:.3f}"
|
||||
t.add_row(
|
||||
[
|
||||
f"{axis.axis_Id_numeric}/{axis.axis_Id}",
|
||||
@@ -330,8 +323,6 @@ class GalilController(Controller):
|
||||
self.show_running_threads()
|
||||
self.show_status_other()
|
||||
|
||||
|
||||
|
||||
def show_status_other(self) -> None:
|
||||
"""
|
||||
Show additional device-specific status information.
|
||||
@@ -420,7 +411,7 @@ class GalilSetpointSignal(GalilSignalBase):
|
||||
while self.controller.is_thread_active(0):
|
||||
time.sleep(0.1)
|
||||
|
||||
#in the case of lamni, consider moving to lgalil
|
||||
# in the case of lamni, consider moving to lgalil
|
||||
if self.parent.axis_Id_numeric == 2 and self.controller.sock.host == "mpc2680.psi.ch":
|
||||
try:
|
||||
rt = self.parent.device_manager.devices[self.parent.rt]
|
||||
|
||||
@@ -170,7 +170,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -530,7 +530,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_lib import bec_logger, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, DeviceStatus, PositionerBase, Signal
|
||||
from ophyd import Device, 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
|
||||
@@ -25,9 +23,6 @@ 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
|
||||
|
||||
|
||||
@@ -133,15 +128,15 @@ class RtFlomniController(Controller):
|
||||
while not self.slew_rate_limiters_on_target() or np.abs(self.pid_y()) > 0.1:
|
||||
time.sleep(0.05)
|
||||
|
||||
self.get_device_manager().devices.rty.update_user_parameter({"tomo_additional_offsety": 0})
|
||||
self.device_manager.devices.rty.update_user_parameter({"tomo_additional_offsety": 0})
|
||||
self.clear_trajectory_generator()
|
||||
|
||||
self.laser_tracker_on()
|
||||
|
||||
# move to 0. FUPR will set the rotation angle during readout
|
||||
self.get_device_manager().devices.fsamroy.obj.move(0, wait=True)
|
||||
self.device_manager.devices.fsamroy.obj.move(0, wait=True)
|
||||
|
||||
fsamx = self.get_device_manager().devices.fsamx
|
||||
fsamx = self.device_manager.devices.fsamx
|
||||
|
||||
fsamx.obj.pid_x_correction = 0
|
||||
fsamx.obj.controller.socket_put_confirmed("axspeed[4]=0.1*stppermm[4]")
|
||||
@@ -171,7 +166,7 @@ class RtFlomniController(Controller):
|
||||
self.show_cyclic_error_compensation()
|
||||
|
||||
self.rt_pid_voltage = self.get_pid_x()
|
||||
rtx = self.get_device_manager().devices.rtx
|
||||
rtx = self.device_manager.devices.rtx
|
||||
rtx.update_user_parameter({"rt_pid_voltage": self.rt_pid_voltage})
|
||||
|
||||
self.set_device_read_write("fsamx", False)
|
||||
@@ -182,7 +177,7 @@ class RtFlomniController(Controller):
|
||||
def move_samx_to_scan_region(self, fovx: float, cenx: float):
|
||||
time.sleep(0.05)
|
||||
if self.rt_pid_voltage is None:
|
||||
rtx = self.get_device_manager().devices.rtx
|
||||
rtx = self.device_manager.devices.rtx
|
||||
self.rt_pid_voltage = rtx.user_parameter.get("rt_pid_voltage")
|
||||
if self.rt_pid_voltage is None:
|
||||
raise RtError(
|
||||
@@ -199,7 +194,7 @@ class RtFlomniController(Controller):
|
||||
break
|
||||
wait_on_exit = True
|
||||
self.socket_put("v0")
|
||||
fsamx = self.get_device_manager().devices.fsamx
|
||||
fsamx = self.device_manager.devices.fsamx
|
||||
fsamx.read_only = False
|
||||
fsamx.obj.controller.socket_put_confirmed("axspeed[4]=0.1*stppermm[4]")
|
||||
fsamx.obj.pid_x_correction -= (self.get_pid_x() - expected_voltage) * 0.007
|
||||
@@ -245,7 +240,7 @@ class RtFlomniController(Controller):
|
||||
self.set_device_read_write("foptx", True)
|
||||
self.set_device_read_write("fopty", True)
|
||||
|
||||
fsamx = self.get_device_manager().devices.fsamx
|
||||
fsamx = self.device_manager.devices.fsamx
|
||||
fsamx.obj.controller.socket_put_confirmed("axspeed[4]=025*stppermm[4]")
|
||||
print("rt feedback is now disalbed.")
|
||||
|
||||
@@ -275,7 +270,8 @@ class RtFlomniController(Controller):
|
||||
self.laser_update_tracker_info()
|
||||
if self.tracker_info["enabled_z"] and self.tracker_info["enabled_y"]:
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def laser_tracker_on(self):
|
||||
if not self.laser_tracker_check_enabled():
|
||||
@@ -295,12 +291,8 @@ class RtFlomniController(Controller):
|
||||
self.socket_put("T1")
|
||||
time.sleep(0.5)
|
||||
|
||||
self.get_device_manager().devices.ftrackz.obj.controller.socket_put_confirmed(
|
||||
"trackyct=0"
|
||||
)
|
||||
self.get_device_manager().devices.ftrackz.obj.controller.socket_put_confirmed(
|
||||
"trackzct=0"
|
||||
)
|
||||
self.device_manager.devices.ftrackz.obj.controller.socket_put_confirmed("trackyct=0")
|
||||
self.device_manager.devices.ftrackz.obj.controller.socket_put_confirmed("trackzct=0")
|
||||
|
||||
self.laser_tracker_wait_on_target()
|
||||
logger.info("Laser tracker running!")
|
||||
@@ -347,7 +339,7 @@ class RtFlomniController(Controller):
|
||||
}
|
||||
|
||||
def laser_tracker_galil_enable(self):
|
||||
ftrackz_con = self.get_device_manager().devices.ftrackz.obj.controller
|
||||
ftrackz_con = self.device_manager.devices.ftrackz.obj.controller
|
||||
ftrackz_con.socket_put_confirmed("tracken=1")
|
||||
ftrackz_con.socket_put_confirmed("trackyct=0")
|
||||
ftrackz_con.socket_put_confirmed("trackzct=0")
|
||||
@@ -387,61 +379,66 @@ class RtFlomniController(Controller):
|
||||
val = float(self.socket_put_and_receive(f"j{axis_number}").strip())
|
||||
return val
|
||||
|
||||
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.
|
||||
"""
|
||||
def laser_tracker_check_signalstrength(self):
|
||||
if not self.laser_tracker_check_enabled():
|
||||
return "disabled"
|
||||
returnval = "disabled"
|
||||
else:
|
||||
returnval = "ok"
|
||||
self.laser_tracker_wait_on_target()
|
||||
|
||||
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)
|
||||
signal = self.read_ssi_interferometer(1)
|
||||
rtx = self.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:
|
||||
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 minimum required signal of {min_signal}. Readjustment requred!\x1b[0m"
|
||||
f"\x1b[91mThe signal of the tracker {signal} is below the warning limit of {low_signal}. Readjustment recommended!\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"
|
||||
)
|
||||
returnval = "low"
|
||||
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 = "Interferometer signal strength"
|
||||
t.title = f"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"
|
||||
@@ -452,23 +449,18 @@ 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) -> 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.
|
||||
"""
|
||||
def get_scan_status(self):
|
||||
return_table = (self.socket_put_and_receive("sr")).split(",")
|
||||
if len(return_table) != 3:
|
||||
raise RtCommunicationError(
|
||||
@@ -484,14 +476,87 @@ 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) -> 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: # 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.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.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.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):
|
||||
@@ -613,7 +678,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
@@ -728,6 +793,9 @@ 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"""
|
||||
@@ -739,178 +807,6 @@ 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, device_manager=None
|
||||
|
||||
@@ -152,8 +152,8 @@ 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.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.device_manager.devices.rtx.obj.user_setpoint.set_with_feedback_disabled(x_curr)
|
||||
self.device_manager.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_read_write("lsamx", False)
|
||||
@@ -286,7 +286,7 @@ class RtLamniController(Controller):
|
||||
|
||||
# if not (mode==2 or mode==3):
|
||||
# error
|
||||
self.get_device_manager().connector.set(
|
||||
self.device_manager.connector.set(
|
||||
MessageEndpoints.device_status("rt_scan"),
|
||||
messages.DeviceStatusMessage(
|
||||
device="rt_scan", status=1, metadata=self.readout_metadata
|
||||
@@ -321,7 +321,7 @@ class RtLamniController(Controller):
|
||||
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(
|
||||
self.device_manager.connector.set(
|
||||
MessageEndpoints.device_status("rt_scan"),
|
||||
messages.DeviceStatusMessage(
|
||||
device="rt_scan", status=0, metadata=self.readout_metadata
|
||||
@@ -333,7 +333,7 @@ class RtLamniController(Controller):
|
||||
)
|
||||
|
||||
def publish_device_data(self, signals, point_id):
|
||||
self.get_device_manager().connector.set_and_publish(
|
||||
self.device_manager.connector.set_and_publish(
|
||||
MessageEndpoints.device_read("rt_lamni"),
|
||||
messages.DeviceMessage(
|
||||
signals=signals, metadata={"point_id": point_id, **self.readout_metadata}
|
||||
@@ -368,10 +368,10 @@ class RtLamniController(Controller):
|
||||
) # we set all three outputs of the traj. gen. although in LamNI case only 0,1 are used
|
||||
self.clear_trajectory_generator()
|
||||
|
||||
self.get_device_manager().devices.lsamrot.obj.move(0, wait=True)
|
||||
self.device_manager.devices.lsamrot.obj.move(0, wait=True)
|
||||
|
||||
galil_controller_rt_status = (
|
||||
self.get_device_manager().devices.lsamx.obj.controller.lgalil_is_air_off_and_orchestra_enabled()
|
||||
self.device_manager.devices.lsamx.obj.controller.lgalil_is_air_off_and_orchestra_enabled()
|
||||
)
|
||||
|
||||
if galil_controller_rt_status == 0:
|
||||
@@ -384,16 +384,16 @@ class RtLamniController(Controller):
|
||||
|
||||
time.sleep(0.03)
|
||||
|
||||
lsamx_user_params = self.get_device_manager().devices.lsamx.user_parameter
|
||||
lsamx_user_params = self.device_manager.devices.lsamx.user_parameter
|
||||
if lsamx_user_params is None or lsamx_user_params.get("center") is None:
|
||||
raise RuntimeError("lsamx center is not defined")
|
||||
lsamy_user_params = self.get_device_manager().devices.lsamy.user_parameter
|
||||
lsamy_user_params = self.device_manager.devices.lsamy.user_parameter
|
||||
if lsamy_user_params is None or lsamy_user_params.get("center") is None:
|
||||
raise RuntimeError("lsamy center is not defined")
|
||||
lsamx_center = lsamx_user_params.get("center")
|
||||
lsamy_center = lsamy_user_params.get("center")
|
||||
self.get_device_manager().devices.lsamx.obj.move(lsamx_center, wait=True)
|
||||
self.get_device_manager().devices.lsamy.obj.move(lsamy_center, wait=True)
|
||||
self.device_manager.devices.lsamx.obj.move(lsamx_center, wait=True)
|
||||
self.device_manager.devices.lsamy.obj.move(lsamy_center, wait=True)
|
||||
self.socket_put("J1")
|
||||
|
||||
_waitforfeedbackctr = 0
|
||||
@@ -588,7 +588,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ class RtOMNYController(Controller):
|
||||
return self.mirror_parameters[channel]
|
||||
|
||||
def laser_tracker_check_and_wait_for_signalstrength(self):
|
||||
self.get_device_manager().connector.send_client_info(
|
||||
self.device_manager.connector.send_client_info(
|
||||
"Checking laser tracker...", scope="", show_asap=True
|
||||
)
|
||||
if not self.laser_tracker_check_enabled():
|
||||
@@ -282,12 +282,12 @@ class RtOMNYController(Controller):
|
||||
self.laser_tracker_wait_on_target()
|
||||
# when on target, check interferometer signal
|
||||
signal = self._omny_interferometer_get_signalsample("ssi_4", 0.1)
|
||||
rtx = self.get_device_manager().devices.rtx
|
||||
rtx = self.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(
|
||||
self.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,
|
||||
@@ -298,14 +298,14 @@ class RtOMNYController(Controller):
|
||||
signal = self._omny_interferometer_get_signalsample("ssi_4", 0.1)
|
||||
|
||||
if signal < low_signal:
|
||||
self.get_device_manager().connector.send_client_info(
|
||||
self.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(
|
||||
self.device_manager.connector.send_client_info(
|
||||
"Checking laser tracker completed.", scope="", show_asap=True
|
||||
)
|
||||
|
||||
@@ -364,7 +364,7 @@ class RtOMNYController(Controller):
|
||||
elif amplitude < 10:
|
||||
amplitude = 10
|
||||
|
||||
oshield = self.get_device_manager().devices.oshield
|
||||
oshield = self.device_manager.devices.oshield
|
||||
|
||||
oshield.obj.controller.move_open_loop_steps(
|
||||
channel, steps, amplitude=amplitude, frequency=500
|
||||
@@ -444,7 +444,7 @@ class RtOMNYController(Controller):
|
||||
|
||||
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(
|
||||
self.device_manager.connector.send_client_info(
|
||||
message, scope="_omny_interferometer_optimize", show_asap=True
|
||||
)
|
||||
|
||||
@@ -504,7 +504,7 @@ class RtOMNYController(Controller):
|
||||
return True
|
||||
|
||||
def feedback_enable_with_reset(self):
|
||||
self.get_device_manager().connector.send_client_info(
|
||||
self.device_manager.connector.send_client_info(
|
||||
f"Enabling the feedback...", scope="", show_asap=True
|
||||
)
|
||||
|
||||
@@ -525,16 +525,16 @@ class RtOMNYController(Controller):
|
||||
self.laser_tracker_on()
|
||||
time.sleep(0.01)
|
||||
|
||||
osamroy = self.get_device_manager().devices.osamroy
|
||||
osamroy = self.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(
|
||||
self.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
|
||||
osamx = self.device_manager.devices.osamx
|
||||
|
||||
osamx_in = osamx.user_parameter.get("in")
|
||||
if not np.isclose(osamx.obj.readback.get(), osamx_in, atol=0.01):
|
||||
@@ -622,7 +622,7 @@ class RtOMNYController(Controller):
|
||||
# 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(
|
||||
self.device_manager.connector.send_client_info(
|
||||
f"Enabling the laser tracker. Please wait...", scope="", show_asap=True
|
||||
)
|
||||
|
||||
@@ -639,12 +639,8 @@ class RtOMNYController(Controller):
|
||||
self.socket_put("T1")
|
||||
time.sleep(0.5)
|
||||
|
||||
self.get_device_manager().devices.otracky.obj.controller.socket_put_confirmed(
|
||||
"trackyct=0"
|
||||
)
|
||||
self.get_device_manager().devices.otracky.obj.controller.socket_put_confirmed(
|
||||
"trackzct=0"
|
||||
)
|
||||
self.device_manager.devices.otracky.obj.controller.socket_put_confirmed("trackyct=0")
|
||||
self.device_manager.devices.otracky.obj.controller.socket_put_confirmed("trackzct=0")
|
||||
|
||||
self.laser_tracker_wait_on_target()
|
||||
logger.info("Laser tracker running!")
|
||||
@@ -718,17 +714,17 @@ class RtOMNYController(Controller):
|
||||
self.laser_tracker_galil_status()
|
||||
|
||||
def laser_tracker_galil_enable(self):
|
||||
otracky_con = self.get_device_manager().devices.otracky.obj.controller
|
||||
otracky_con = self.device_manager.devices.otracky.obj.controller
|
||||
otracky_con.socket_put_confirmed("tracken=1")
|
||||
otracky_con.socket_put_confirmed("trackyct=0")
|
||||
otracky_con.socket_put_confirmed("trackzct=0")
|
||||
|
||||
def laser_tracker_galil_disable(self):
|
||||
otracky_con = self.get_device_manager().devices.otracky.obj.controller
|
||||
otracky_con = self.device_manager.devices.otracky.obj.controller
|
||||
otracky_con.socket_put_confirmed("tracken=0")
|
||||
|
||||
def laser_tracker_galil_status(self):
|
||||
otracky_con = self.get_device_manager().devices.otracky.obj.controller
|
||||
otracky_con = self.device_manager.devices.otracky.obj.controller
|
||||
if bool(float(otracky_con.socket_put_and_receive("MGtracken").strip())) == 0:
|
||||
print(self.red + "Tracking in the Galil Controller is disabled." + self.white)
|
||||
print("Use dev.rtx.controller.laser_tracker_galil_enable to enable.\n")
|
||||
@@ -914,12 +910,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:
|
||||
return axis.device_manager
|
||||
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
|
||||
@@ -933,7 +923,7 @@ class RtOMNYController(Controller):
|
||||
|
||||
# if not (mode==2 or mode==3):
|
||||
# error
|
||||
self.get_device_manager().connector.set(
|
||||
self.device_manager.connector.set(
|
||||
MessageEndpoints.device_status("rt_scan"),
|
||||
messages.DeviceStatusMessage(
|
||||
device="rt_scan", status=1, metadata=self.readout_metadata
|
||||
@@ -968,7 +958,7 @@ class RtOMNYController(Controller):
|
||||
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(
|
||||
self.device_manager.connector.set(
|
||||
MessageEndpoints.device_status("rt_scan"),
|
||||
messages.DeviceStatusMessage(
|
||||
device="rt_scan", status=0, metadata=self.readout_metadata
|
||||
@@ -981,7 +971,7 @@ class RtOMNYController(Controller):
|
||||
f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}."
|
||||
)
|
||||
|
||||
self.get_device_manager().connector.send_client_info(
|
||||
self.device_manager.connector.send_client_info(
|
||||
"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}.",
|
||||
@@ -990,7 +980,7 @@ class RtOMNYController(Controller):
|
||||
)
|
||||
|
||||
def publish_device_data(self, signals, point_id):
|
||||
self.get_device_manager().connector.set_and_publish(
|
||||
self.device_manager.connector.set_and_publish(
|
||||
MessageEndpoints.device_read("rt_omny"),
|
||||
messages.DeviceMessage(
|
||||
signals=signals, metadata={"point_id": point_id, **self.readout_metadata}
|
||||
@@ -1129,7 +1119,7 @@ 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:
|
||||
def wait_for_connection(self, timeout: int = 30, **kwargs) -> None:
|
||||
"""Wait for the device to be connected."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
|
||||
@@ -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 AsyncFlyScanBase
|
||||
from bec_server.scan_server.scans import SyncFlyScanBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class FlomniFermatScan(AsyncFlyScanBase):
|
||||
class FlomniFermatScan(SyncFlyScanBase):
|
||||
scan_name = "flomni_fermat_scan"
|
||||
scan_type = "fly"
|
||||
required_kwargs = ["fovx", "fovy", "exp_time", "step", "angle"]
|
||||
@@ -97,7 +97,6 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
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 = []
|
||||
@@ -108,6 +107,10 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
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
|
||||
@@ -132,13 +135,13 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
if flip_axes:
|
||||
self.positions = np.flipud(self.positions)
|
||||
|
||||
self.flyer_num_pos = len(self.positions)
|
||||
self.num_pos = len(self.positions)
|
||||
self._check_min_positions()
|
||||
|
||||
def _check_min_positions(self):
|
||||
if self.flyer_num_pos < 20:
|
||||
if self.num_pos < 20:
|
||||
raise ScanAbortion(
|
||||
f"The number of positions must exceed 20. Currently: {self.flyer_num_pos}."
|
||||
f"The number of positions must exceed 20. Currently: {self.num_pos}."
|
||||
)
|
||||
|
||||
def _prepare_setup(self):
|
||||
@@ -147,8 +150,6 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
|
||||
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()
|
||||
@@ -168,16 +169,12 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
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!",
|
||||
)
|
||||
@@ -279,13 +276,26 @@ class FlomniFermatScan(AsyncFlyScanBase):
|
||||
return np.array(positions)
|
||||
|
||||
def scan_core(self):
|
||||
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)
|
||||
# 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')}"
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
self.point_id += 1
|
||||
logger.debug("reading monitors")
|
||||
self.num_pos = self.point_id
|
||||
# yield from self.device_rpc("rtx", "controller.kickoff")
|
||||
|
||||
def move_to_start(self):
|
||||
"""return to the start position"""
|
||||
|
||||
@@ -17,24 +17,23 @@ def rt_flomni():
|
||||
socket_port=8081,
|
||||
device_manager=mock.MagicMock(),
|
||||
)
|
||||
with mock.patch.object(rt_flomni, "get_device_manager"):
|
||||
with mock.patch.object(rt_flomni, "sock"):
|
||||
rtx = mock.MagicMock(spec=RtFlomniMotor)
|
||||
rtx.name = "rtx"
|
||||
rtx.axis_Id = "A"
|
||||
rtx.axis_Id_numeric = 0
|
||||
rty = mock.MagicMock(spec=RtFlomniMotor)
|
||||
rty.name = "rty"
|
||||
rty.axis_Id = "B"
|
||||
rty.axis_Id_numeric = 1
|
||||
rtz = mock.MagicMock(spec=RtFlomniMotor)
|
||||
rtz.name = "rtz"
|
||||
rtz.axis_Id = "C"
|
||||
rtz.axis_Id_numeric = 2
|
||||
rt_flomni.set_axis(axis=rtx, axis_nr=0)
|
||||
rt_flomni.set_axis(axis=rty, axis_nr=1)
|
||||
rt_flomni.set_axis(axis=rtz, axis_nr=2)
|
||||
yield rt_flomni
|
||||
with mock.patch.object(rt_flomni, "sock"):
|
||||
rtx = mock.MagicMock(spec=RtFlomniMotor)
|
||||
rtx.name = "rtx"
|
||||
rtx.axis_Id = "A"
|
||||
rtx.axis_Id_numeric = 0
|
||||
rty = mock.MagicMock(spec=RtFlomniMotor)
|
||||
rty.name = "rty"
|
||||
rty.axis_Id = "B"
|
||||
rty.axis_Id_numeric = 1
|
||||
rtz = mock.MagicMock(spec=RtFlomniMotor)
|
||||
rtz.name = "rtz"
|
||||
rtz.axis_Id = "C"
|
||||
rtz.axis_Id_numeric = 2
|
||||
rt_flomni.set_axis(axis=rtx, axis_nr=0)
|
||||
rt_flomni.set_axis(axis=rty, axis_nr=1)
|
||||
rt_flomni.set_axis(axis=rtz, axis_nr=2)
|
||||
yield rt_flomni
|
||||
RtFlomniController._reset_controller()
|
||||
|
||||
|
||||
@@ -56,7 +55,7 @@ def test_rt_flomni_feedback_is_running(rt_flomni, return_value, is_running):
|
||||
|
||||
def test_feedback_enable_with_reset(rt_flomni):
|
||||
|
||||
device_manager = rt_flomni.get_device_manager()
|
||||
device_manager = rt_flomni.device_manager
|
||||
device_manager.devices.fsamx.user_parameter.get.return_value = 0.05
|
||||
device_manager.devices.fsamx.obj.readback.get.return_value = 0.05
|
||||
|
||||
@@ -72,7 +71,7 @@ def test_feedback_enable_with_reset(rt_flomni):
|
||||
|
||||
|
||||
def test_move_samx_to_scan_region(rt_flomni):
|
||||
device_manager = rt_flomni.get_device_manager()
|
||||
device_manager = rt_flomni.device_manager
|
||||
device_manager.devices.rtx.user_parameter.get.return_value = 1
|
||||
rt_flomni.move_samx_to_scan_region(20, 2)
|
||||
assert mock.call(b"v0\n") not in rt_flomni.sock.put.mock_calls
|
||||
|
||||
Reference in New Issue
Block a user