feat: add first draft for mo1bragg ophyd positioner

This commit is contained in:
gac-x01da (Resp. Clark Adam Hugh)
2024-07-11 15:43:04 +02:00
parent abc5271a02
commit ad0db78e45
+149
View File
@@ -0,0 +1,149 @@
from ophyd import Device, PositionerBase, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, Kind, Signal, DeviceStatus
from ophyd import Component as Cpt
from ophyd.utils import LimitError
from typing import Literal
import enum
class Mo1BraggError(Exception):
"""Mo1Bragg specific exception"""
class MoveType(str, enum.Enum):
ENERGY = 'energy'
ANGLE = 'angle'
class MoveTypeSignal(Signal):
""" Custom Signal to set the move type of the Bragg positioner"""
def set(self, value:str | MoveType) -> None:
""" Returns currently active move method
Args:
value (str | MoveType) : Can be either 'energy' or 'angle'
"""
if value not in ['energy', 'angle']:
raise ValueError(f"Invalid input for MoveTypeSignal {value}, can be either 'energy' or 'angle'")
self._readback = value
class Mo1Bragg(Device, PositionerBase):
USER_ACCESS = ["set_xtal"]
offset_si111 = Cpt(EpicsSignalWithRBV, suffix='offset_si111', kind=Kind.config, auto_monitor=True)
offset_si311 = Cpt(EpicsSignalWithRBV, suffix='offset_si311', kind=Kind.config, auto_monitor=True)
# For the moment commented until the PV suffix in EPICS changes (RBV was on xtal_RBV_ENUM -> xtal_ENUM_RBV)
xtal_enum = Cpt(EpicsSignal, suffix='xtal_ENUM', kind=Kind.config, auto_monitor=True)
d_spacing_si111 = Cpt(EpicsSignalWithRBV, suffix='d_spacing_si111', kind=Kind.config, auto_monitor=True)
d_spacing_si311 = Cpt(EpicsSignalWithRBV, suffix='d_spacing_si311', kind=Kind.config, auto_monitor=True)
set_offset = Cpt(EpicsSignal, suffix ='set_offset', kind=Kind.config)
current_xtal = Cpt(EpicsSignalRO, suffix='current_xtal_RBV_ENUM', kind=Kind.normal, auto_monitor=True)
move_type = Cpt(MoveTypeSignal, value='energy', kind=Kind.normal)
feedback_pos_energy = Cpt(EpicsSignalRO, suffix="feedback_pos_energy_RBV", kind=Kind.hinted, auto_monitor=True)
feedback_pos_angle = Cpt(EpicsSignalRO, suffix="feedback_pos_angle_RBV", kind=Kind.hinted, auto_monitor=True)
low_limit_angle = Cpt(EpicsSignalRO, suffix="lo_lim_pos_RBV", kind=Kind.config, auto_monitor=True)
high_limit_angle = Cpt(EpicsSignalRO, suffix="hi_lim_pos_RBV", kind=Kind.config, auto_monitor=True)
move_velocity = Cpt(EpicsSignalWithRBV, suffix="move_velocity", kind=Kind.config, auto_monitor=True)
setpoint_abs_angle = Cpt(EpicsSignalWithRBV, suffix="set_abs_pos_angle", kind=Kind.normal, auto_monitor=True)
setpoint_abs_energy = Cpt(EpicsSignalWithRBV, suffix="set_abs_pos_energy", kind=Kind.normal, auto_monitor=True)
setpoint_rel_angle = Cpt(EpicsSignalWithRBV, suffix="set_rel_pos_angle", kind=Kind.normal, auto_monitor=True)
setpoint_rel_energy = Cpt(EpicsSignalWithRBV, suffix="set_rel_pos_energy", kind=Kind.normal, auto_monitor=True)
move_abs = Cpt(EpicsSignal, suffix="move_abs", kind=Kind.config)
move_abs_done = Cpt(EpicsSignalRO, suffix="move_abs_done_RBV", kind=Kind.normal, auto_monitor=True)
SUB_READBACK = "readback"
_default_sub = SUB_READBACK
@property
def limits(self) -> tuple:
"""Return limits of the Bragg positioner"""
if self.move_type.get() == 'energy':
raise NotImplementedError("PVs need to still be exposed to the softIOC")
# return ()
return (self.low_limit_angle.get(), self.high_limit_angle.get())
@property
def low_limit(self) -> float:
""" Return low limit of axis"""
return self.limits[0]
@property
def high_limit(self) -> float:
""" Return high limit of axis"""
return self.limits[1]
#pylint: disable:arguments-differ
def check_value(self, value:float) -> None:
""" Check if value is within limits
Args:
value (float) : value to move axis to.
"""
low_limit, high_limit = self.limits
if low_limit < high_limit and not low_limit <= value <= high_limit:
raise LimitError(f"position={value} not within limits {self.limits}")
# def move(self, value:float, move_type:str='angle', **kwargs) -> DeviceStatus:
# self._stopped = False
# status = super().move(value)
# # Start motion
# def thread_function(status):
# while self.move_abs_done == 0
# self._run_subs(sub_type=self.SUB_READBACK, value = self.feedback_pos_angle.get())
# if self._stopped ==True:
# status.set_exception()
# return
# status.set_finished()
# def stop()->None:
# self._stopped=True
# def _move_and_finished(self, start_pos, stop_pos, st):
# success = True
def set_xtal(self,
xtal_enum:Literal['111', '311'],
offset_si111:float=None,
offset_si311:float=None,
d_spacing_si111:float=None,
d_spacing_si311:float=None,
)->None:
""" Method to set the crystal
Args:
"""
if offset_si111 is not None:
self.offset_si111.set(offset_si111)
if offset_si311 is not None:
self.offset_si311.set(offset_si311)
if d_spacing_si111 is not None:
self.d_spacing_si111.set(d_spacing_si111)
if d_spacing_si311 is not None:
self.d_spacing_si311.set(d_spacing_si311)
if xtal_enum == '111':
crystal_set = 0
elif xtal_enum == '311':
crystal_set = 1
else:
raise ValueError(f"Invalid argument for xtal_enum : {xtal_enum}, choose from '111' or '311'")
self.xtal_enum.set(crystal_set)
self.set_offset.set(1)
if __name__ == "__main__":
#bragg = Mo1Bragg(name='bragg', prefix="X01DA-OP-MO1:BRAGG:")
pass