diff --git a/debye_bec/devices/mo1_bragg.py b/debye_bec/devices/mo1_bragg.py new file mode 100644 index 0000000..a807678 --- /dev/null +++ b/debye_bec/devices/mo1_bragg.py @@ -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 \ No newline at end of file