diff --git a/pyproject.toml b/pyproject.toml index c5cfb4b748..186ce78bba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ description = "Ophyd devices and other utils that could be used across DLS beaml dependencies = [ "click", "ophyd", - "ophyd-async==0.8.0a3", + "ophyd-async==0.8.0a4", "bluesky", "pyepics", "dataclasses-json", diff --git a/src/dodal/devices/apple2_undulator.py b/src/dodal/devices/apple2_undulator.py index 6e78f05f33..eee422f432 100644 --- a/src/dodal/devices/apple2_undulator.py +++ b/src/dodal/devices/apple2_undulator.py @@ -7,6 +7,7 @@ from bluesky.protocols import Movable from ophyd_async.core import ( AsyncStatus, + Reference, StandardReadable, StandardReadableFormat, StrictEnum, @@ -387,9 +388,9 @@ def __init__( # Attributes are set after super call so they are not renamed to # -undulator, etc. - with self.add_children_as_readables(): - self.gap = id_gap - self.phase = id_phase + self.gap = Reference(id_gap) + self.phase = Reference(id_phase) + with self.add_children_as_readables(StandardReadableFormat.HINTED_SIGNAL): # Store the polarisation for readback. self.polarisation, self._polarisation_set = soft_signal_r_and_setter( @@ -436,16 +437,16 @@ async def _set(self, value: Apple2Val, energy: float) -> None: """ # Only need to check gap as the phase motors share both fault and gate with gap. - await self.gap.check_id_status() + await self.gap().check_id_status() await asyncio.gather( - self.phase.top_outer.user_setpoint.set(value=value.top_outer), - self.phase.top_inner.user_setpoint.set(value=value.top_inner), - self.phase.btm_inner.user_setpoint.set(value=value.btm_inner), - self.phase.btm_outer.user_setpoint.set(value=value.btm_outer), - self.gap.user_setpoint.set(value=value.gap), + self.phase().top_outer.user_setpoint.set(value=value.top_outer), + self.phase().top_inner.user_setpoint.set(value=value.top_inner), + self.phase().btm_inner.user_setpoint.set(value=value.btm_inner), + self.phase().btm_outer.user_setpoint.set(value=value.btm_outer), + self.gap().user_setpoint.set(value=value.gap), ) timeout = np.max( - await asyncio.gather(self.gap.get_timeout(), self.phase.get_timeout()) + await asyncio.gather(self.gap().get_timeout(), self.phase().get_timeout()) ) LOGGER.info( f"Moving f{self.name} energy and polorisation to {energy}, {self.pol}" @@ -453,10 +454,12 @@ async def _set(self, value: Apple2Val, energy: float) -> None: ) await asyncio.gather( - self.gap.set_move.set(value=1, timeout=timeout), - self.phase.set_move.set(value=1, timeout=timeout), + self.gap().set_move.set(value=1, timeout=timeout), + self.phase().set_move.set(value=1, timeout=timeout), + ) + await wait_for_value( + self.gap().gate, UndulatorGateStatus.close, timeout=timeout ) - await wait_for_value(self.gap.gate, UndulatorGateStatus.close, timeout=timeout) self._energy_set(energy) # Update energy for after move for readback. def _get_id_gap_phase(self, energy: float) -> tuple[float, float]: @@ -521,12 +524,11 @@ async def determinePhaseFromHardware(self) -> tuple[str | None, float]: (May be for future one can use the inverse poly to work out the energy and try to match it with the current energy to workout the polarisation but during my test the inverse poly is too unstable for general use.) """ - cur_loc = await self.read() - top_outer = cur_loc[self.phase.top_outer.user_setpoint_readback.name]["value"] - top_inner = cur_loc[self.phase.top_inner.user_setpoint_readback.name]["value"] - btm_inner = cur_loc[self.phase.btm_inner.user_setpoint_readback.name]["value"] - btm_outer = cur_loc[self.phase.btm_outer.user_setpoint_readback.name]["value"] - gap = cur_loc[self.gap.user_readback.name]["value"] + top_outer = await self.phase().top_outer.user_setpoint_readback.get_value() + top_inner = await self.phase().top_inner.user_setpoint_readback.get_value() + btm_inner = await self.phase().btm_inner.user_setpoint_readback.get_value() + btm_outer = await self.phase().btm_outer.user_setpoint_readback.get_value() + gap = await self.gap().user_readback.get_value() if gap > MAXIMUM_GAP_MOTOR_POSITION: raise RuntimeError( f"{self.name} is not in use, close gap or set polarisation to use this ID" diff --git a/src/dodal/devices/i10/i10_apple2.py b/src/dodal/devices/i10/i10_apple2.py index 89e459b8ed..88b8010ca8 100644 --- a/src/dodal/devices/i10/i10_apple2.py +++ b/src/dodal/devices/i10/i10_apple2.py @@ -119,7 +119,7 @@ def __init__( name=name, ) with self.add_children_as_readables(): - self.id_jaw_phase = id_jaw_phase + self.id_jaw_phase = Reference(id_jaw_phase) @AsyncStatus.wrap async def set(self, value: SupportsFloat) -> None: @@ -148,8 +148,8 @@ async def set(self, value: SupportsFloat) -> None: LOGGER.info(f"Setting polarisation to {self.pol}, with {id_set_val}") await self._set(value=id_set_val, energy=value) if self.pol != "la": - await self.id_jaw_phase.set(0) - await self.id_jaw_phase.set_move.set(1) + await self.id_jaw_phase().set(0) + await self.id_jaw_phase().set_move.set(1) def update_lookuptable(self): """ @@ -299,7 +299,7 @@ async def set(self, value: SupportsFloat) -> None: f"jaw_phase position for angle ({value}) is outside permitted range" f" [-{self.jaw_phase_limit}, {self.jaw_phase_limit}]" ) - await self.id_ref().id_jaw_phase.set(jaw_phase) + await self.id_ref().id_jaw_phase().set(jaw_phase) self._angle_set(value) diff --git a/src/dodal/devices/oav/oav_detector.py b/src/dodal/devices/oav/oav_detector.py index 8f0531cc43..5b63412de9 100644 --- a/src/dodal/devices/oav/oav_detector.py +++ b/src/dodal/devices/oav/oav_detector.py @@ -1,6 +1,6 @@ from enum import IntEnum -from ophyd_async.core import DEFAULT_TIMEOUT, AsyncStatus, StandardReadable +from ophyd_async.core import DEFAULT_TIMEOUT, AsyncStatus, LazyMock, StandardReadable from ophyd_async.epics.core import epics_signal_rw from dodal.common.signal_utils import create_hardware_backed_soft_signal @@ -118,7 +118,7 @@ async def _get_beam_position(self, coord: int) -> int: async def connect( self, - mock: bool = False, + mock: bool | LazyMock = False, timeout: float = DEFAULT_TIMEOUT, force_reconnect: bool = False, ): diff --git a/tests/devices/i10/test_i10Apple2.py b/tests/devices/i10/test_i10Apple2.py index 2ce256b95a..3d406c7707 100644 --- a/tests/devices/i10/test_i10Apple2.py +++ b/tests/devices/i10/test_i10Apple2.py @@ -164,10 +164,10 @@ async def test_I10Apple2_determine_pol( btm_inner_phase: float, btm_outer_phase: float, ): - set_mock_value(mock_id.phase.top_inner.user_setpoint_readback, top_inner_phase) - set_mock_value(mock_id.phase.top_outer.user_setpoint_readback, top_outer_phase) - set_mock_value(mock_id.phase.btm_inner.user_setpoint_readback, btm_inner_phase) - set_mock_value(mock_id.phase.btm_outer.user_setpoint_readback, btm_outer_phase) + set_mock_value(mock_id.phase().top_inner.user_setpoint_readback, top_inner_phase) + set_mock_value(mock_id.phase().top_outer.user_setpoint_readback, top_outer_phase) + set_mock_value(mock_id.phase().btm_inner.user_setpoint_readback, btm_inner_phase) + set_mock_value(mock_id.phase().btm_outer.user_setpoint_readback, btm_outer_phase) if pol is None: with pytest.raises(ValueError): @@ -234,7 +234,7 @@ async def test_fail_I10Apple2_set_lookup_gap_pol(mock_id: I10Apple2): async def test_fail_I10Apple2_set_undefined_pol(mock_id: I10Apple2): - set_mock_value(mock_id.gap.user_readback, 101) + set_mock_value(mock_id.gap().user_readback, 101) with pytest.raises(RuntimeError) as e: await mock_id.set(600) assert ( @@ -244,15 +244,15 @@ async def test_fail_I10Apple2_set_undefined_pol(mock_id: I10Apple2): async def test_fail_I10Apple2_set_id_not_ready(mock_id: I10Apple2): - set_mock_value(mock_id.gap.fault, 1) + set_mock_value(mock_id.gap().fault, 1) with pytest.raises(RuntimeError) as e: await mock_id.set(600) - assert str(e.value) == mock_id.gap.name + " is in fault state" - set_mock_value(mock_id.gap.fault, 0) - set_mock_value(mock_id.gap.gate, UndulatorGateStatus.open) + assert str(e.value) == mock_id.gap().name + " is in fault state" + set_mock_value(mock_id.gap().fault, 0) + set_mock_value(mock_id.gap().gate, UndulatorGateStatus.open) with pytest.raises(RuntimeError) as e: await mock_id.set(600) - assert str(e.value) == mock_id.gap.name + " is already in motion." + assert str(e.value) == mock_id.gap().name + " is already in motion." async def test_I10Apple2_RE_scan(mock_id: I10Apple2, RE: RunEngine): @@ -341,23 +341,23 @@ async def test_I10Apple2_pol_set( else: await mock_id_pol.set(pol) assert mock_id_pol.id.pol == pol - top_inner = get_mock_put(mock_id_pol.id.phase.top_inner.user_setpoint) + top_inner = get_mock_put(mock_id_pol.id.phase().top_inner.user_setpoint) top_inner.assert_called_once() assert float(top_inner.call_args[0][0]) == pytest.approx(expect_top_inner, 0.01) - top_outer = get_mock_put(mock_id_pol.id.phase.top_outer.user_setpoint) + top_outer = get_mock_put(mock_id_pol.id.phase().top_outer.user_setpoint) top_outer.assert_called_once() assert float(top_outer.call_args[0][0]) == pytest.approx(expect_top_outer, 0.01) - btm_inner = get_mock_put(mock_id_pol.id.phase.btm_inner.user_setpoint) + btm_inner = get_mock_put(mock_id_pol.id.phase().btm_inner.user_setpoint) btm_inner.assert_called_once() assert float(btm_inner.call_args[0][0]) == pytest.approx(expect_btm_inner, 0.01) - btm_outer = get_mock_put(mock_id_pol.id.phase.btm_outer.user_setpoint) + btm_outer = get_mock_put(mock_id_pol.id.phase().btm_outer.user_setpoint) btm_outer.assert_called_once() assert float(btm_outer.call_args[0][0]) == pytest.approx(expect_btm_outer, 0.01) - gap = get_mock_put(mock_id_pol.id.gap.user_setpoint) + gap = get_mock_put(mock_id_pol.id.gap().user_setpoint) gap.assert_called_once() assert float(gap.call_args[0][0]) == pytest.approx(expect_gap, 0.05) @@ -428,7 +428,7 @@ def capture_emitted(name, doc): assert_emitted(docs, start=1, descriptor=1, event=num_point, stop=1) jaw_phase = get_mock_put( - mock_linear_arbitrary_angle.id_ref().id_jaw_phase.jaw_phase.user_setpoint + mock_linear_arbitrary_angle.id_ref().id_jaw_phase().jaw_phase.user_setpoint ) poly = poly1d(