online changes; added bec logger; added limits to custom devices

This commit is contained in:
e20216 2022-07-25 13:58:59 +02:00
parent 3b971336ee
commit 6e541cc0d0
3 changed files with 206 additions and 62 deletions

View File

@ -6,9 +6,9 @@ from typing import List
import numpy as np
from ophyd import Component as Cpt
from ophyd import Device, PositionerBase
from ophyd import Device, PositionerBase, Signal
from ophyd.status import wait as status_wait
from ophyd.utils import ReadOnlyError
from ophyd.utils import ReadOnlyError, LimitError
from ophyd_devices.utils.controller import Controller, threadlocked
from ophyd_devices.utils.socket import SocketIO, SocketSignal, raise_if_disconnected
from prettytable import PrettyTable
@ -29,7 +29,7 @@ class BECConfigError(Exception):
def retry_once(fcn):
"""Decorator to rerun a function in case a SmaractCommunicationError was raised. This may happen if the buffer was not empty."""
"""Decorator to rerun a function in case a Galil communication error was raised. This may happen if the buffer was not empty."""
@functools.wraps(fcn)
def wrapper(self, *args, **kwargs):
@ -49,6 +49,7 @@ class GalilController(Controller):
"galil_show_all",
"socket_put_and_receive",
"socket_put_confirmed",
"lgalil_is_air_off_and_orchestra_enabled"
]
def __init__(
@ -347,6 +348,8 @@ class GalilMotor(Device, PositionerBase):
motor_resolution = Cpt(GalilMotorResolution, signal_name="resolution", kind="config")
motor_is_moving = Cpt(GalilMotorIsMoving, signal_name="motor_is_moving", kind="normal")
all_axes_referenced = Cpt(GalilAxesReferenced, signal_name="all_axes_referenced", kind="config")
high_limit_travel = Cpt(Signal, value=0, kind="omitted")
low_limit_travel = Cpt(Signal, value=0, kind="omitted")
SUB_READBACK = "readback"
SUB_CONNECTION_CHANGE = "connection_change"
@ -364,6 +367,7 @@ class GalilMotor(Device, PositionerBase):
parent=None,
host="mpc2680.psi.ch",
port=8081,
limits=None,
sign=1,
socket_cls=SocketIO,
device_manager=None,
@ -399,6 +403,30 @@ class GalilMotor(Device, PositionerBase):
self._update_connection_state()
# self.readback.subscribe(self._forward_readback, event_type=self.readback.SUB_VALUE)
if limits is not None:
assert len(limits) == 2
self.low_limit_travel.put(limits[0])
self.high_limit_travel.put(limits[1])
@property
def limits(self):
return (self.low_limit_travel.get(), self.high_limit_travel.get())
@property
def low_limit(self):
return self.limits[0]
@property
def high_limit(self):
return self.limits[1]
def check_value(self, pos):
"""Check that the position is within the soft limits"""
low_limit, high_limit = self.limits
if low_limit < high_limit and not (low_limit <= pos <= high_limit):
raise LimitError(f"position={pos} not within limits {self.limits}")
def _update_connection_state(self, **kwargs):
for walk in self.walk_signals():
walk.item._metadata["connected"] = self.controller.connected
@ -438,7 +466,7 @@ class GalilMotor(Device, PositionerBase):
If motion fails other than timing out
"""
self._started_moving = False
timeout = kwargs.pop("timeout", 4)
timeout = kwargs.pop("timeout", 100)
status = super().move(position, timeout=timeout, **kwargs)
self.user_setpoint.put(position, wait=False)

View File

@ -12,8 +12,10 @@ from ophyd_devices.utils.socket import (
SocketSignal,
)
from bec_utils import bec_logger
from ophyd.utils import ReadOnlyError
from ophyd.utils import ReadOnlyError,LimitError
from ophyd.status import wait as status_wait
from ophyd import Signal
logger = bec_logger.logger
@ -60,7 +62,11 @@ class RtLamniController(Controller):
"feedback_disable_and_even_reset_lamni_angle_interferometer",
"feedback_enable_with_reset",
"add_pos_to_scan",
"clear_trajectory_generator"
"clear_trajectory_generator",
"_set_axis_velocity",
"_set_axis_velocity_maximum_speed",
"_position_sampling_single_read",
"_position_sampling_single_reset_and_start_sampling"
]
def __init__(
self,
@ -75,6 +81,7 @@ class RtLamniController(Controller):
if not hasattr(self, "_initialized") or not self._initialized:
self._rtlamni_axis_per_controller = 3
self._axis = [None for axis_num in range(self._rtlamni_axis_per_controller)]
self._min_scan_buffer_reached = False
super().__init__(
name=name,
socket=socket,
@ -83,14 +90,19 @@ class RtLamniController(Controller):
labels=labels,
kind=kind,
)
def on(self, controller_num=0) -> None:
"""Open a new socket connection to the controller"""
if not self.connected:
self.sock.open()
#discuss - after disconnect takes a while for the server to be ready again
welcome_message = self.sock.receive()
self.connected = True
try:
self.sock.open()
#discuss - after disconnect takes a while for the server to be ready again
welcome_message = self.sock.receive()
self.connected = True
except ConnectionRefusedError as conn_error:
logger.error("Failed to open a connection to RTLamNI.")
else:
logger.info("The connection has already been established.")
# warnings.warn(f"The connection has already been established.", stacklevel=2)
@ -146,8 +158,8 @@ class RtLamniController(Controller):
def set_rotation_angle(self, val:float):
self.socket_put(f"a{(val-300+30.538)/180*np.pi}")
def stop_all_axes(self) -> str:
return 0 #self.socket_put_and_receive(f"XQ#STOP,1")
def stop_all_axes(self):
self.socket_put("sc")
def feedback_disable(self):
self.socket_put("J0")
@ -158,6 +170,25 @@ class RtLamniController(Controller):
#motor_par("lopty","disable",0)
#motor_par("loptz","disable",0)
def _set_axis_velocity(self, um_per_s):
self.socket_put(f"V{um_per_s}")
def _set_axis_velocity_maximum_speed(self):
self.socket_put(f"V0")
#for developement of soft continuous scanning
def _position_sampling_single_reset_and_start_sampling(self):
self.socket_put(f"Ss")
def _position_sampling_single_read(self):
(number_of_samples,sum0,sum0_2,sum1,sum1_2,sum2,sum2_2) = self.socket_put_and_receive(f"Sr").split(",")
avg_x = float(sum1)/int(number_of_samples)
avg_y = float(sum0)/int(number_of_samples)
stdev_x = np.sqrt(float(sum1_2)/int(number_of_samples)-np.power(float(sum1)/int(number_of_samples),2))
stdev_y = np.sqrt(float(sum0_2)/int(number_of_samples)-np.power(float(sum0)/int(number_of_samples),2))
return(avg_x,avg_y,stdev_x,stdev_y)
def feedback_enable_without_reset(self):
#read current interferometer position
return_table = (self.socket_put_and_receive(f"J4")).split(",")
@ -201,11 +232,21 @@ class RtLamniController(Controller):
self.socket_put("sc")
logger.info("LamNI scan stopped and deleted, moving to start position")
def add_pos_to_scan(self, posx, posy) -> int:
self.socket_put_and_receive(f"s{posx},{posy},0")
def add_pos_to_scan(self, positions) -> None:
def send_positions(parent, positions):
parent._min_scan_buffer_reached = False
for pos_index, pos in enumerate(positions):
parent.socket_put_and_receive(f"s{pos[0]},{pos[1]},0")
if pos_index > 100:
parent._min_scan_buffer_reached = True
parent._min_scan_buffer_reached = True
threading.Thread(target=send_positions, args=(self, positions), daemon=True).start()
@retry_once
def get_scan_status(self):
return_table = (self.socket_put_and_receive(f"sr")).split(",")
if len(return_table)!=3:
raise RtLamniCommunicationError(f"Expected to receive 3 return values. Instead received {return_table}")
mode=int(return_table[0])
#mode 0: direct positioning
#mode 1: running internal timer (not tested/used anymore)
@ -219,12 +260,14 @@ class RtLamniController(Controller):
def start_scan(self):
interferometer_feedback_not_running = int((self.socket_put_and_receive("J2")).split(",")[0])
if interferometer_feedback_not_running == 1:
logger.info("Cannot start scan because feedback loop is not running or there is an interferometer error.")
logger.error("Cannot start scan because feedback loop is not running or there is an interferometer error.")
raise RtLamniError("Cannot start scan because feedback loop is not running or there is an interferometer error.")
#here exception
(mode,number_of_positions_planned,current_position_in_scan)=self.get_scan_status()
if number_of_positions_planned == 0:
logger.info("Cannot start scan because no target positions are planned.")
logger.error("Cannot start scan because no target positions are planned.")
raise RtLamniError("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")
@ -235,28 +278,59 @@ class RtLamniController(Controller):
def kickoff(self):
time.sleep(0.3)
while not self._min_scan_buffer_reached:
time.sleep(0.001)
self.start_scan()
while self.get_scan_status()[0]!=0:
time.sleep(0.1)
time.sleep(0.1)
self.start_readout()
def read_positions_from_sampler(self):
number_of_samples_to_read = self.get_scan_status()[1] #number of valid samples, will be updated upon first data read
#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
previous_point_in_scan = 0
average_stdeviations_x_st_fzp = 0
average_stdeviations_y_st_fzp = 0
average_lamni_angle = 0
while number_of_samples_to_read > read_counter:
#fprintf(dname,"DataPoint TotalPoints Target_x Average_x_st_fzp Stdev_x_st_fzp Target_y Average_y_st_fzp Stdev_y_st_fzp Average_cap1 Stdev_cap1 Average_cap2 Stdev_cap2 Average_cap3 Stdev_cap3 Average_cap4 Stdev_cap4 Average_cap5 Stdev_cap5 Average_angle_interf_ST Stdev_angle_interf_ST\n")
# 0 1 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
mode,number_of_positions_planned,current_position_in_scan = self.get_scan_status()
#if not (mode==2 or mode==3):
# error
#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(.01)
if (current_position_in_scan>5):
while current_position_in_scan > read_counter:
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
average_stdeviations_x_st_fzp=average_stdeviations_x_st_fzp+float(return_table[5])
average_stdeviations_y_st_fzp=average_stdeviations_y_st_fzp+float(return_table[8])
average_lamni_angle = average_lamni_angle+float(return_table[19])
time.sleep(.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"{return_table}")
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
#logger.info(f"{return_table}")
read_counter = read_counter+1
average_stdeviations_x_st_fzp=average_stdeviations_x_st_fzp+float(return_table[5])
average_stdeviations_y_st_fzp=average_stdeviations_y_st_fzp+float(return_table[8])
average_lamni_angle = average_lamni_angle+float(return_table[19])
read_counter=read_counter+1
logger.info(f"LamNI statistics: Average of all standard deviations: x {average_stdeviations_x_st_fzp/number_of_samples_to_read}, y {average_stdeviations_y_st_fzp/number_of_samples_to_read}, angle {average_lamni_angle/number_of_samples_to_read}.")
def feedback_status_angle_lamni(self) -> bool:
@ -283,48 +357,39 @@ class RtLamniController(Controller):
self.get_device_manager().devices.lsamrot.obj.move(0, wait=True)
#!lgalil_is_air_off_and_orchestra_enabled:
# try:
### self.parent.device_manager.devices[
# self.parent.galil
# ].obj.controller.set_rotation_angle(val[self.parent.name]["value"])
# except KeyError:
# logger.warning("Failed to set RT value during readback.")
# lgalil_is_air_off_and_orchestra_enabled
#Cannot enable feedback. The small rotation air is on and/or orchestra disabled by the motor controller.
#exit with failure
galil_controller_rt_status = self.get_device_manager().devices.lsamx.obj.controller.lgalil_is_air_off_and_orchestra_enabled()
if galil_controller_rt_status == 0:
logger.error("Cannot enable feedback. The small rotation air is on and/or orchestra disabled by the motor controller.")
raise RtLamniError("Cannot enable feedback. The small rotation air is on and/or orchestra disabled by the motor controller.")
time.sleep(0.03)
#global _lsamx_center
#global _lsamy_center
#umv lsamx _lsamx_center
#umv lsamy _lsamy_center
#needs to be changed to configurable center psoition
self.get_device_manager().devices.lsamx.obj.move(8.866, wait=True)
self.get_device_manager().devices.lsamy.obj.move(10.18, wait=True)
self.socket_put("J1")
_waitforfeedbackctr=0
#this is implemented as class and not function. why? RtLamniFeedbackRunning
interferometer_feedback_not_running = int((self.socket_put_and_receive("J2")).split(",")[0])
# while self.RtLamniFeedbackRunning _rt_status_feedback(self) == 1 && _waitforfeedbackctr<100)
# {
# sleep(0.01)
# _waitforfeedbackctr++
# #p _waitforfeedbackctr
# }
# motor_par("lsamx","disable",1)
# motor_par("lsamy","disable",1)
# motor_par("loptx","disable",1)
# motor_par("lopty","disable",1)
# motor_par("loptz","disable",1)
# rt_feedback_status
while interferometer_feedback_not_running == 1 and _waitforfeedbackctr<100:
time.sleep(0.01)
_waitforfeedbackctr = _waitforfeedbackctr + 1
interferometer_feedback_not_running = int((self.socket_put_and_receive("J2")).split(",")[0])
# motor_par("lsamx","disable",1)
# motor_par("lsamy","disable",1)
# motor_par("loptx","disable",1)
# motor_par("lopty","disable",1)
# motor_par("loptz","disable",1)
if interferometer_feedback_not_running == 1:
logger.error("Cannot start scan because feedback loop is not running or there is an interferometer error.")
raise RtLamniError("Cannot start scan because feedback loop is not running or there is an interferometer error.")
time.sleep(0.01)
#ptychography_alignment_done = 0
#ptychography_alignment_done = 0
@ -440,6 +505,8 @@ class RtLamniMotor(Device, PositionerBase):
motor_is_moving = Cpt(
RtLamniMotorIsMoving, signal_name="motor_is_moving", kind="normal"
)
high_limit_travel = Cpt(Signal, value=0, kind="omitted")
low_limit_travel = Cpt(Signal, value=0, kind="omitted")
SUB_READBACK = "readback"
SUB_CONNECTION_CHANGE = "connection_change"
@ -460,6 +527,7 @@ class RtLamniMotor(Device, PositionerBase):
sign=1,
socket_cls=SocketIO,
device_manager=None,
limits = None,
**kwargs,
):
self.axis_Id = axis_Id
@ -483,8 +551,31 @@ class RtLamniMotor(Device, PositionerBase):
self._update_connection_state, event_type=self.SUB_CONNECTION_CHANGE
)
self._update_connection_state()
# self.readback.subscribe(self._forward_readback, event_type=self.readback.SUB_VALUE)
# self.readback.subscribe(self._forward_readback, event_type=self.readback.SUB_VALUE)
if limits is not None:
assert len(limits) == 2
self.low_limit_travel.put(limits[0])
self.high_limit_travel.put(limits[1])
@property
def limits(self):
return (self.low_limit_travel.get(), self.high_limit_travel.get())
@property
def low_limit(self):
return self.limits[0]
@property
def high_limit(self):
return self.limits[1]
def check_value(self, pos):
"""Check that the position is within the soft limits"""
low_limit, high_limit = self.limits
if low_limit < high_limit and not (low_limit <= pos <= high_limit):
raise LimitError(f"position={pos} not within limits {self.limits}")
def _update_connection_state(self, **kwargs):
for walk in self.walk_signals():
@ -527,7 +618,7 @@ class RtLamniMotor(Device, PositionerBase):
If motion fails other than timing out
"""
self._started_moving = False
timeout = kwargs.pop("timeout", 4)
timeout = kwargs.pop("timeout", 100)
status = super().move(position, timeout=timeout, **kwargs)
self.user_setpoint.put(position, wait=False)

View File

@ -5,9 +5,9 @@ from typing import List
import numpy as np
from ophyd import Component as Cpt
from ophyd import Device, PositionerBase
from ophyd import Device, PositionerBase, Signal
from ophyd.status import wait as status_wait
from ophyd.utils import ReadOnlyError
from ophyd.utils import ReadOnlyError, LimitError
from ophyd_devices.smaract.smaract_controller import SmaractController
from ophyd_devices.smaract.smaract_errors import SmaractCommunicationError, SmaractError
from ophyd_devices.utils.socket import SocketIO, SocketSignal, raise_if_disconnected
@ -93,7 +93,8 @@ class SmaractMotor(Device, PositionerBase):
# )
motor_is_moving = Cpt(SmaractMotorIsMoving, signal_name="motor_is_moving", kind="normal")
high_limit_travel = Cpt(Signal, value=0, kind="omitted")
low_limit_travel = Cpt(Signal, value=0, kind="omitted")
# all_axes_referenced = Cpt(
# SmaractAxesReferenced, signal_name="all_axes_referenced", kind="config"
# )
@ -114,6 +115,7 @@ class SmaractMotor(Device, PositionerBase):
parent=None,
host="mpc2680.psi.ch",
port=8085,
limits=None,
sign=1,
socket_cls=SocketIO,
**kwargs,
@ -138,6 +140,29 @@ class SmaractMotor(Device, PositionerBase):
self._update_connection_state, event_type=self.SUB_CONNECTION_CHANGE
)
self._update_connection_state()
if limits is not None:
assert len(limits) == 2
self.low_limit_travel.put(limits[0])
self.high_limit_travel.put(limits[1])
@property
def limits(self):
return (self.low_limit_travel.get(), self.high_limit_travel.get())
@property
def low_limit(self):
return self.limits[0]
@property
def high_limit(self):
return self.limits[1]
def check_value(self, pos):
"""Check that the position is within the soft limits"""
low_limit, high_limit = self.limits
if low_limit < high_limit and not (low_limit <= pos <= high_limit):
raise LimitError(f"position={pos} not within limits {self.limits}")
def _update_connection_state(self, **kwargs):
for walk in self.walk_signals():