diff --git a/src/dodal/beamlines/i05.py b/src/dodal/beamlines/i05.py index 2ca7d00d063..2443922899f 100644 --- a/src/dodal/beamlines/i05.py +++ b/src/dodal/beamlines/i05.py @@ -2,6 +2,8 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager from dodal.devices.beamlines.i05 import I05Goniometer +from dodal.devices.beamlines.i05.enums import M4M5Mirror +from dodal.devices.common_mirror import XYZSwitchingMirror from dodal.devices.temperture_controller import Lakeshore336 from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name @@ -15,6 +17,15 @@ devices.include(i05_shared_devices) +# will connect after https://jira.diamond.ac.uk/browse/I05-731 +@devices.factory(skip=True) +def m4m5() -> XYZSwitchingMirror: + return XYZSwitchingMirror( + prefix=f"{PREFIX.beamline_prefix}-OP-RFM-01:", + mirrors=M4M5Mirror, + ) + + @devices.factory() def sample_temperature_controller() -> Lakeshore336: return Lakeshore336(prefix=f"{PREFIX.beamline_prefix}-EA-TCTRL-02:") diff --git a/src/dodal/beamlines/i05_1.py b/src/dodal/beamlines/i05_1.py index 93842aaa5f0..496552d5f75 100644 --- a/src/dodal/beamlines/i05_1.py +++ b/src/dodal/beamlines/i05_1.py @@ -1,7 +1,9 @@ from dodal.beamlines.i05_shared import devices as i05_shared_devices from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager +from dodal.devices.beamlines.i05.enums import Mj7j8Mirror from dodal.devices.beamlines.i05_1 import XYZPolarAzimuthDefocusStage +from dodal.devices.common_mirror import XYZPiezoSwitchingMirror from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name @@ -14,6 +16,15 @@ devices.include(i05_shared_devices) +# will connect after https://jira.diamond.ac.uk/browse/I05-731 +@devices.factory(skip=True) +def mj7j8() -> XYZPiezoSwitchingMirror: + return XYZPiezoSwitchingMirror( + prefix=f"{PREFIX.beamline_prefix}-OP-RFM-01:", + mirrors=Mj7j8Mirror, + ) + + @devices.factory def sm() -> XYZPolarAzimuthDefocusStage: """Sample Manipulator.""" diff --git a/src/dodal/beamlines/i05_shared.py b/src/dodal/beamlines/i05_shared.py index 807c1140485..88d495526e3 100644 --- a/src/dodal/beamlines/i05_shared.py +++ b/src/dodal/beamlines/i05_shared.py @@ -1,10 +1,12 @@ from dodal.device_manager import DeviceManager -from dodal.devices.beamlines.i05.enums import Grating +from dodal.devices.beamlines.i05.enums import Grating, M3MJ6Mirror +from dodal.devices.common_mirror import XYZPiezoSwitchingMirror from dodal.devices.insertion_device import ( Apple2, UndulatorGap, UndulatorLockedPhaseAxes, ) +from dodal.devices.motors import XYZPitchYawRollStage from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.synchrotron import Synchrotron from dodal.utils import BeamlinePrefix, get_beamline_name @@ -49,3 +51,17 @@ def id( ) -> Apple2[UndulatorLockedPhaseAxes]: """i05 insertion device.""" return Apple2[UndulatorLockedPhaseAxes](id_gap=id_gap, id_phase=id_phase) + + +@devices.factory() +def m1_collimating_mirror() -> XYZPitchYawRollStage: + return XYZPitchYawRollStage(prefix=f"{PREFIX.beamline_prefix}-OP-COL-01:") + + +# will connect after https://jira.diamond.ac.uk/browse/I05-731 +@devices.factory(skip=True) +def m3mj6_switching_mirror() -> XYZPiezoSwitchingMirror: + return XYZPiezoSwitchingMirror( + prefix=f"{PREFIX.beamline_prefix}-OP-SWTCH-01:", + mirrors=M3MJ6Mirror, + ) diff --git a/src/dodal/beamlines/i10.py b/src/dodal/beamlines/i10.py index 7f88e1520a6..1459b9e55c2 100644 --- a/src/dodal/beamlines/i10.py +++ b/src/dodal/beamlines/i10.py @@ -6,7 +6,6 @@ I10Diagnostic5ADet, I10Slits, I10SlitsDrainCurrent, - PiezoMirror, ) from dodal.devices.beamlines.i10.diagnostics import I10Diagnostic, I10Diagnostic5ADet from dodal.devices.beamlines.i10.rasor.rasor_current_amp import RasorFemto, RasorSR570 @@ -16,6 +15,7 @@ PaStage, ) from dodal.devices.beamlines.i10.rasor.rasor_scaler_cards import RasorScalerCard1 +from dodal.devices.common_mirror import XYZPiezoCollimatingMirror from dodal.devices.current_amplifiers import CurrentAmpDet from dodal.devices.motors import XYStage, XYZStage from dodal.devices.temperture_controller import ( @@ -34,8 +34,8 @@ @devices.factory() -def focusing_mirror() -> PiezoMirror: - return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCS-01:") +def focusing_mirror() -> XYZPiezoCollimatingMirror: + return XYZPiezoCollimatingMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCS-01:") """Optic slits""" diff --git a/src/dodal/beamlines/i10_1.py b/src/dodal/beamlines/i10_1.py index 92fc9f4685e..84ad4c75c86 100644 --- a/src/dodal/beamlines/i10_1.py +++ b/src/dodal/beamlines/i10_1.py @@ -1,12 +1,13 @@ from dodal.beamlines.i10_shared import devices as i10_shared_devices from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager -from dodal.devices.beamlines.i10 import I10JDiagnostic, I10JSlits, PiezoMirror +from dodal.devices.beamlines.i10 import I10JDiagnostic, I10JSlits from dodal.devices.beamlines.i10_1 import ( ElectromagnetMagnetField, ElectromagnetStage, I10JScalerCard, ) +from dodal.devices.common_mirror import XYZPiezoCollimatingMirror from dodal.devices.current_amplifiers import SR570, CurrentAmpDet from dodal.devices.temperture_controller.lakeshore.lakeshore import Lakeshore336 from dodal.log import set_beamline as set_log_beamline @@ -40,8 +41,8 @@ def diagnostic() -> I10JDiagnostic: @devices.factory() -def focusing_mirror() -> PiezoMirror: - return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCA-01:") +def focusing_mirror() -> XYZPiezoCollimatingMirror: + return XYZPiezoCollimatingMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCA-01:") """I10J Electromagnet Devices""" diff --git a/src/dodal/beamlines/i10_shared.py b/src/dodal/beamlines/i10_shared.py index ac7a27da06c..0cdfdfb8993 100644 --- a/src/dodal/beamlines/i10_shared.py +++ b/src/dodal/beamlines/i10_shared.py @@ -15,7 +15,6 @@ I10SharedDiagnostic, I10SharedSlits, I10SharedSlitsDrainCurrent, - PiezoMirror, ) from dodal.devices.beamlines.i10.i10_apple2 import ( I10Apple2, @@ -25,6 +24,7 @@ # Imports taken from i10 while we work out how to deal with split end stations from dodal.devices.beamlines.i10.i10_setting_data import I10Grating +from dodal.devices.common_mirror import XYZPiezoCollimatingMirror from dodal.devices.insertion_device import ( BeamEnergy, InsertionDeviceEnergy, @@ -63,8 +63,8 @@ def synchrotron() -> Synchrotron: @devices.factory() -def first_mirror() -> PiezoMirror: - return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-COL-01:") +def first_mirror() -> XYZPiezoCollimatingMirror: + return XYZPiezoCollimatingMirror(prefix=f"{PREFIX.beamline_prefix}-OP-COL-01:") @devices.factory() @@ -80,8 +80,8 @@ def pgm() -> PlaneGratingMonochromator: @devices.factory() -def switching_mirror() -> PiezoMirror: - return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-SWTCH-01:") +def switching_mirror() -> XYZPiezoCollimatingMirror: + return XYZPiezoCollimatingMirror(prefix=f"{PREFIX.beamline_prefix}-OP-SWTCH-01:") """ID""" diff --git a/src/dodal/devices/beamlines/i05/__init__.py b/src/dodal/devices/beamlines/i05/__init__.py index 484665f1152..788346eba5a 100644 --- a/src/dodal/devices/beamlines/i05/__init__.py +++ b/src/dodal/devices/beamlines/i05/__init__.py @@ -1,5 +1,18 @@ +from dodal.devices.beamlines.i05.enums import ( + Grating, + M3MJ6Mirror, + M4M5Mirror, + Mj7j8Mirror, +) + from .compound_motors import PolynomCompoundMotors -from .enums import Grating from .i05_motors import I05Goniometer -__all__ = ["Grating", "PolynomCompoundMotors", "I05Goniometer"] +__all__ = [ + "Grating", + "I05Goniometer", + "M3MJ6Mirror", + "M4M5Mirror", + "Mj7j8Mirror", + "PolynomCompoundMotors", +] diff --git a/src/dodal/devices/beamlines/i05/enums.py b/src/dodal/devices/beamlines/i05/enums.py index e49cbed77b2..dd97882c22d 100644 --- a/src/dodal/devices/beamlines/i05/enums.py +++ b/src/dodal/devices/beamlines/i05/enums.py @@ -6,3 +6,24 @@ class Grating(StrictEnum): C_1600 = "C 1600 lines/mm" RH_1600 = "Rh 1600 lines/mm" PT_800 = "B 800 lines/mm" + + +class M3MJ6Mirror(StrictEnum): + UNKNOWN = "Unknown" + MJ6 = "MJ6" + M3 = "M3" + REFERENCE = "Reference" + + +class M4M5Mirror(StrictEnum): + UNKNOWN = "Unknown" + M4 = "M4" + M5 = "M5" + REFERENCE = "Reference" + + +class Mj7j8Mirror(StrictEnum): + UNKNOWN = "Unknown" + MJ8 = "MJ8" + MJ7 = "MJ7" + REFERENCE = "Reference" diff --git a/src/dodal/devices/beamlines/i10/__init__.py b/src/dodal/devices/beamlines/i10/__init__.py index cd42a773492..fe5dc102f92 100644 --- a/src/dodal/devices/beamlines/i10/__init__.py +++ b/src/dodal/devices/beamlines/i10/__init__.py @@ -5,7 +5,6 @@ I10PneumaticStage, I10SharedDiagnostic, ) -from .mirrors import PiezoMirror from .slits import ( I10JSlits, I10SharedSlits, @@ -18,7 +17,6 @@ "I10Diagnostic", "I10Diagnostic5ADet", "I10PneumaticStage", - "PiezoMirror", "I10Slits", "I10SlitsDrainCurrent", "I10SharedDiagnostic", diff --git a/src/dodal/devices/beamlines/i10/mirrors.py b/src/dodal/devices/beamlines/i10/mirrors.py deleted file mode 100644 index da1cd247f7c..00000000000 --- a/src/dodal/devices/beamlines/i10/mirrors.py +++ /dev/null @@ -1,18 +0,0 @@ -from ophyd_async.epics.core import epics_signal_rw - -from dodal.devices.motors import XYZPitchYawRollStage - - -class PiezoMirror(XYZPitchYawRollStage): - def __init__( - self, - prefix: str, - name: str = "", - ): - with self.add_children_as_readables(): - self.fine_pitch = epics_signal_rw( - float, - read_pv=prefix + "FPITCH:RBV:AI", - write_pv=prefix + "FPITCH:DMD:AO", - ) - super().__init__(prefix, name) diff --git a/src/dodal/devices/common_mirror.py b/src/dodal/devices/common_mirror.py new file mode 100644 index 00000000000..f9a7fd259b2 --- /dev/null +++ b/src/dodal/devices/common_mirror.py @@ -0,0 +1,146 @@ +from typing import Generic, TypeVar + +from bluesky.protocols import Locatable, Location, Stoppable +from ophyd_async.core import AsyncStatus, StrictEnum +from ophyd_async.epics.core import epics_signal_rw, epics_signal_x + +from dodal.devices.motors import XYZPitchYawRollStage + +DEFAULT_MIRROR_READ_SUFFIX = "MIRCTRL:RBV:MIRROR" +DEFAULT_MIRROR_WRITE_SUFFIX = "MIRCTRL:DMD:MIRROR" +DEFAULT_MIRROR_CHANGE_SUFFIX = "MIRCTRL:SEQ:CHNG:MIRROR.PROC" +DEFAULT_MIRROR_ABORT_SUFFIX = "MIRCTRL:DMD:ABORT.PROC" +DEFAULT_FPITCH_READ_SUFFIX = "FPITCH:RBV" +DEFAULT_FPITCH_WRITE_SUFFIX = "FPITCH:DMD" + + +TMirror = TypeVar("TMirror", bound=StrictEnum) + + +class XYZSwitchingMirror( + XYZPitchYawRollStage, Generic[TMirror], Locatable[TMirror], Stoppable +): + """Set of mirrors on hexapod stage. + + This device controls set of mirrors on a hexapod stage with standard x,y,z and yaw, + pitch, roll motors. + + Args: + prefix (str): EPICS PV prefix for the mirror. + mirrors (StrictEnum): enum representing set of mirrors + fpitch_read_suffix (str, optional): suffix for the fine pitch readback PV + fpitch_write_suffix (str, optional): suffix for the fine pitch setpoint PV + mirror_read_suffix (str, optional): suffix for mirror readback PV + mirror_write_suffix (str, optional): suffix for mirror demand PV + mirror_abort_suffix (str, optional): suffix for mirror abort PV + name (str, optional): name of the device. + """ + + def __init__( + self, + prefix: str, + mirrors: type[TMirror], + mirror_read_suffix: str = DEFAULT_MIRROR_READ_SUFFIX, + mirror_write_suffix: str = DEFAULT_MIRROR_WRITE_SUFFIX, + mirror_change_suffix: str = DEFAULT_MIRROR_CHANGE_SUFFIX, + mirror_abort_suffix: str = DEFAULT_MIRROR_ABORT_SUFFIX, + name: str = "", + ): + with self.add_children_as_readables(): + self.mirror = epics_signal_rw( + mirrors, + read_pv=prefix + mirror_read_suffix, + write_pv=prefix + mirror_write_suffix, + ) + + self.mirror_change = epics_signal_x(write_pv=prefix + mirror_change_suffix) + self.mirror_abort = epics_signal_x(write_pv=prefix + mirror_abort_suffix) + + super().__init__(prefix=prefix, name=name) + + @AsyncStatus.wrap + async def set(self, new_position: TMirror): + await self.mirror.set(new_position) + await self.mirror_change.trigger() + + async def locate(self) -> Location[TMirror]: + location = await self.mirror.locate() + return location + + async def stop(self, success=True) -> None: + await self.mirror_abort.trigger() + + +class XYZPiezoCollimatingMirror(XYZPitchYawRollStage): + """Collimating mirror on a hexapod stage. + + This device controls mirror on a hexapod stage which includes standard x, y, z, yaw, + pitch and roll motors, as well as fine pitch piezo motor. + + Args: + prefix (str): EPICS PV prefix for the mirror. + fpitch_read_suffix (str, optional): The suffix for the fine pitch readback PV. + fpitch_write_suffix (str, optional): The suffix for the fine pitch setpoint PV. + name (str, optional): The name of the device. + """ + + def __init__( + self, + prefix: str, + fpitch_read_suffix: str = "FPITCH:RBV:AI", + fpitch_write_suffix: str = "FPITCH:DMD:AO", + name: str = "", + ): + with self.add_children_as_readables(): + self.fine_pitch = epics_signal_rw( + float, + read_pv=prefix + fpitch_read_suffix, + write_pv=prefix + fpitch_write_suffix, + ) + super().__init__(prefix=prefix, name=name) + + +class XYZPiezoSwitchingMirror(XYZSwitchingMirror[TMirror], Generic[TMirror]): + """Set of mirrors on a hexapod stage with piezo fine pitch motor. + + This device controls set of mirrors on a hexapod stage which includes standard x, + y, z, yaw, pitch and roll motors, as well as fine pitch piezo motor. + + Args: + prefix (str): EPICS PV prefix for the mirror. + mirrors (StrictEnum): enum representing set of mirrors + fpitch_read_suffix (str, optional): suffix for the fine pitch readback PV + fpitch_write_suffix (str, optional): suffix for the fine pitch setpoint PV + mirror_read_suffix (str, optional): suffix for mirror readback PV + mirror_write_suffix (str, optional): suffix for mirror demand PV + mirror_abort_suffix (str, optional): suffix for mirror abort PV + name (str, optional): name of the device. + """ + + def __init__( + self, + prefix: str, + mirrors: type[TMirror], + fpitch_read_suffix: str = DEFAULT_FPITCH_READ_SUFFIX, + fpitch_write_suffix: str = DEFAULT_FPITCH_WRITE_SUFFIX, + mirror_read_suffix: str = DEFAULT_MIRROR_READ_SUFFIX, + mirror_write_suffix: str = DEFAULT_MIRROR_WRITE_SUFFIX, + mirror_change_suffix: str = DEFAULT_MIRROR_CHANGE_SUFFIX, + mirror_abort_suffix: str = DEFAULT_MIRROR_ABORT_SUFFIX, + name: str = "", + ): + with self.add_children_as_readables(): + self.fine_pitch = epics_signal_rw( + float, + read_pv=prefix + fpitch_read_suffix, + write_pv=prefix + fpitch_write_suffix, + ) + super().__init__( + prefix=prefix, + mirrors=mirrors, + mirror_read_suffix=mirror_read_suffix, + mirror_write_suffix=mirror_write_suffix, + mirror_change_suffix=mirror_change_suffix, + mirror_abort_suffix=mirror_abort_suffix, + name=name, + ) diff --git a/tests/devices/test_common_mirrors.py b/tests/devices/test_common_mirrors.py new file mode 100644 index 00000000000..73e200ad476 --- /dev/null +++ b/tests/devices/test_common_mirrors.py @@ -0,0 +1,233 @@ +from unittest.mock import AsyncMock + +import pytest +from bluesky.plan_stubs import mv, stop +from bluesky.protocols import Location +from bluesky.run_engine import RunEngine +from ophyd_async.core import StrictEnum, init_devices, set_mock_value +from ophyd_async.testing import assert_reading, partial_reading + +from dodal.devices.common_mirror import ( + XYZPiezoCollimatingMirror, + XYZPiezoSwitchingMirror, + XYZSwitchingMirror, +) + + +class MirrorEnum(StrictEnum): + UNKNOWN = "Unknown" + MJ6 = "MJ6" + M3 = "M3" + REFERENCE = "Reference" + + +@pytest.fixture +async def xyz_piezo_coll_mirror() -> XYZPiezoCollimatingMirror: + async with init_devices(mock=True): + xyz_piezo_coll_mirror = XYZPiezoCollimatingMirror("") + return xyz_piezo_coll_mirror + + +@pytest.fixture +async def xyz_switching_mirror() -> XYZSwitchingMirror[MirrorEnum]: + async with init_devices(mock=True): + xyz_switching_mirror = XYZSwitchingMirror("", MirrorEnum) + return xyz_switching_mirror + + +@pytest.fixture +async def xyz_piezo_switching_mirror() -> XYZPiezoSwitchingMirror[MirrorEnum]: + async with init_devices(mock=True): + xyz_piezo_switching_mirror = XYZPiezoSwitchingMirror("", MirrorEnum) + return xyz_piezo_switching_mirror + + +@pytest.mark.parametrize( + "x, y, z, pitch, yaw, roll, fpitch", + [ + (0, 0, 0, 0, 0, 0, 0), + (1.23, 2.40, 0.0, 0.0, 0.0, 0.0, 0.0), + (1.23, 2.40, 3.51, 24.06, 12.02, 3.56, 13.23), + ], +) +async def test_setting_xyz_piezo_coll_mirror_positions( + xyz_piezo_coll_mirror: XYZPiezoCollimatingMirror, + x: float, + y: float, + z: float, + pitch: float, + yaw: float, + roll: float, + fpitch: float, +): + """Test setting positions on the Table using the ophyd_async mock tools.""" + # Call set to update the position + set_mock_value(xyz_piezo_coll_mirror.x.user_readback, x) + set_mock_value(xyz_piezo_coll_mirror.y.user_readback, y) + set_mock_value(xyz_piezo_coll_mirror.z.user_readback, z) + set_mock_value(xyz_piezo_coll_mirror.pitch.user_readback, pitch) + set_mock_value(xyz_piezo_coll_mirror.yaw.user_readback, yaw) + set_mock_value(xyz_piezo_coll_mirror.roll.user_readback, roll) + set_mock_value(xyz_piezo_coll_mirror.fine_pitch, fpitch) + + await assert_reading( + xyz_piezo_coll_mirror, + { + "xyz_piezo_coll_mirror-x": partial_reading(x), + "xyz_piezo_coll_mirror-y": partial_reading(y), + "xyz_piezo_coll_mirror-z": partial_reading(z), + "xyz_piezo_coll_mirror-pitch": partial_reading(pitch), + "xyz_piezo_coll_mirror-yaw": partial_reading(yaw), + "xyz_piezo_coll_mirror-roll": partial_reading(roll), + "xyz_piezo_coll_mirror-fine_pitch": partial_reading(fpitch), + }, + ) + + +@pytest.mark.parametrize( + "x, y, z, pitch, yaw, roll, mirror", + [ + (0, 0, 0, 0, 0, 0, MirrorEnum.UNKNOWN), + (1.23, 2.40, 0.0, 0.0, 0.0, 0.0, MirrorEnum.M3), + (1.23, 2.40, 3.51, 24.06, 12.02, 3.56, MirrorEnum.REFERENCE), + ], +) +async def test_setting_xyz_switching_mirror_position_table( + xyz_switching_mirror: XYZSwitchingMirror, + x: float, + y: float, + z: float, + pitch: float, + yaw: float, + roll: float, + mirror: MirrorEnum, +): + """Test setting positions on the Table using the ophyd_async mock tools.""" + # Call set to update the position + set_mock_value(xyz_switching_mirror.x.user_readback, x) + set_mock_value(xyz_switching_mirror.y.user_readback, y) + set_mock_value(xyz_switching_mirror.z.user_readback, z) + set_mock_value(xyz_switching_mirror.pitch.user_readback, pitch) + set_mock_value(xyz_switching_mirror.yaw.user_readback, yaw) + set_mock_value(xyz_switching_mirror.roll.user_readback, roll) + set_mock_value(xyz_switching_mirror.mirror, mirror) + + await assert_reading( + xyz_switching_mirror, + { + "xyz_switching_mirror-x": partial_reading(x), + "xyz_switching_mirror-y": partial_reading(y), + "xyz_switching_mirror-z": partial_reading(z), + "xyz_switching_mirror-pitch": partial_reading(pitch), + "xyz_switching_mirror-yaw": partial_reading(yaw), + "xyz_switching_mirror-roll": partial_reading(roll), + "xyz_switching_mirror-mirror": partial_reading(mirror), + }, + ) + + +async def test_move_xyz_switching_mirror( + xyz_switching_mirror: XYZSwitchingMirror, + run_engine: RunEngine, +): + assert await xyz_switching_mirror.mirror.get_value() == MirrorEnum.UNKNOWN + run_engine(mv(xyz_switching_mirror, MirrorEnum.M3)) + assert await xyz_switching_mirror.mirror.get_value() == MirrorEnum.M3 + + +async def test_locate_xyz_switching_mirror( + xyz_switching_mirror: XYZSwitchingMirror, + run_engine: RunEngine, +): + assert await xyz_switching_mirror.locate() == Location( + setpoint=MirrorEnum.UNKNOWN, readback=MirrorEnum.UNKNOWN + ) + run_engine(mv(xyz_switching_mirror, MirrorEnum.M3)) + assert await xyz_switching_mirror.locate() == Location( + setpoint=MirrorEnum.M3, readback=MirrorEnum.M3 + ) + + +async def test_stop_xyz_switching_mirror( + xyz_switching_mirror: XYZSwitchingMirror, + run_engine: RunEngine, +): + xyz_switching_mirror.mirror_abort.trigger = AsyncMock() + run_engine(stop(xyz_switching_mirror)) + xyz_switching_mirror.mirror_abort.trigger.assert_awaited_once() + + +@pytest.mark.parametrize( + "x, y, z, pitch, yaw, roll,fpitch, mirror", + [ + (0, 0, 0, 0, 0, 0, 0, MirrorEnum.UNKNOWN), + (1.23, 2.40, 0.21, 0.0, 0.0, 0.0, 0.0, MirrorEnum.M3), + (1.23, 2.40, 3.51, 24.06, 12.02, 3.56, 1.81, MirrorEnum.REFERENCE), + ], +) +async def test_setting_xyz_piezo_switching_mirror_positions( + xyz_piezo_switching_mirror: XYZPiezoSwitchingMirror, + x: float, + y: float, + z: float, + pitch: float, + yaw: float, + roll: float, + fpitch: float, + mirror: MirrorEnum, +): + """Test setting positions on the Table using the ophyd_async mock tools.""" + # Call set to update the position + set_mock_value(xyz_piezo_switching_mirror.x.user_readback, x) + set_mock_value(xyz_piezo_switching_mirror.y.user_readback, y) + set_mock_value(xyz_piezo_switching_mirror.z.user_readback, z) + set_mock_value(xyz_piezo_switching_mirror.pitch.user_readback, pitch) + set_mock_value(xyz_piezo_switching_mirror.yaw.user_readback, yaw) + set_mock_value(xyz_piezo_switching_mirror.roll.user_readback, roll) + set_mock_value(xyz_piezo_switching_mirror.fine_pitch, fpitch) + set_mock_value(xyz_piezo_switching_mirror.mirror, mirror) + + await assert_reading( + xyz_piezo_switching_mirror, + { + "xyz_piezo_switching_mirror-x": partial_reading(x), + "xyz_piezo_switching_mirror-y": partial_reading(y), + "xyz_piezo_switching_mirror-z": partial_reading(z), + "xyz_piezo_switching_mirror-pitch": partial_reading(pitch), + "xyz_piezo_switching_mirror-yaw": partial_reading(yaw), + "xyz_piezo_switching_mirror-roll": partial_reading(roll), + "xyz_piezo_switching_mirror-fine_pitch": partial_reading(fpitch), + "xyz_piezo_switching_mirror-mirror": partial_reading(mirror), + }, + ) + + +async def test_move_xyz_piezo_switching_mirror( + xyz_piezo_switching_mirror: XYZPiezoSwitchingMirror, + run_engine: RunEngine, +): + assert await xyz_piezo_switching_mirror.mirror.get_value() == MirrorEnum.UNKNOWN + run_engine(mv(xyz_piezo_switching_mirror, MirrorEnum.M3)) + assert await xyz_piezo_switching_mirror.mirror.get_value() == MirrorEnum.M3 + + +async def test_locate_xyz_piezo_switching_mirror( + xyz_piezo_switching_mirror: XYZPiezoSwitchingMirror, + run_engine: RunEngine, +): + assert await xyz_piezo_switching_mirror.locate() == Location( + setpoint=MirrorEnum.UNKNOWN, readback=MirrorEnum.UNKNOWN + ) + run_engine(mv(xyz_piezo_switching_mirror, MirrorEnum.M3)) + assert await xyz_piezo_switching_mirror.locate() == Location( + setpoint=MirrorEnum.M3, readback=MirrorEnum.M3 + ) + + +async def test_stop_xyz_piezo_switching_mirror( + xyz_piezo_switching_mirror: XYZPiezoSwitchingMirror, + run_engine: RunEngine, +): + xyz_piezo_switching_mirror.mirror_abort.trigger = AsyncMock() + run_engine(stop(xyz_piezo_switching_mirror)) + xyz_piezo_switching_mirror.mirror_abort.trigger.assert_awaited_once()