Skip to content

Commit 5ab67ae

Browse files
authored
Make beamstop selected_pos attribute settable (#1227)
* Add beamstop position setter to device * Raise ValueError if set to UNKNOWN * Move beamstop tests to mx_phase1 directory
1 parent e6776dc commit 5ab67ae

File tree

3 files changed

+60
-7
lines changed

3 files changed

+60
-7
lines changed

src/dodal/devices/mx_phase1/beamstop.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import asyncio
12
from math import isclose
23

3-
from ophyd_async.core import StandardReadable, StrictEnum, derived_signal_r
4+
from ophyd_async.core import (
5+
StandardReadable,
6+
StrictEnum,
7+
derived_signal_rw,
8+
)
49
from ophyd_async.epics.motor import Motor
510

611
from dodal.common.beamlines.beamline_parameters import GDABeamlineParameters
@@ -37,8 +42,7 @@ class Beamstop(StandardReadable):
3742
x: beamstop x position in mm
3843
y: beamstop y position in mm
3944
z: beamstop z position in mm
40-
selected_pos: Get the current position of the beamstop as an enum. Currently this
41-
is read-only.
45+
selected_pos: Get or set the current position of the beamstop as an enum.
4246
"""
4347

4448
def __init__(
@@ -51,10 +55,13 @@ def __init__(
5155
self.x_mm = Motor(prefix + "X")
5256
self.y_mm = Motor(prefix + "Y")
5357
self.z_mm = Motor(prefix + "Z")
54-
self.selected_pos = derived_signal_r(
55-
self._get_selected_position, x=self.x_mm, y=self.y_mm, z=self.z_mm
58+
self.selected_pos = derived_signal_rw(
59+
self._get_selected_position,
60+
self._set_selected_position,
61+
x=self.x_mm,
62+
y=self.y_mm,
63+
z=self.z_mm,
5664
)
57-
5865
self._in_beam_xyz_mm = [
5966
float(beamline_parameters[f"in_beam_{axis}_STANDARD"])
6067
for axis in ("x", "y", "z")
@@ -77,3 +84,13 @@ def _get_selected_position(self, x: float, y: float, z: float) -> BeamstopPositi
7784
return BeamstopPositions.DATA_COLLECTION
7885
else:
7986
return BeamstopPositions.UNKNOWN
87+
88+
async def _set_selected_position(self, position: BeamstopPositions) -> None:
89+
if position == BeamstopPositions.DATA_COLLECTION:
90+
await asyncio.gather(
91+
self.x_mm.set(self._in_beam_xyz_mm[0]),
92+
self.y_mm.set(self._in_beam_xyz_mm[1]),
93+
self.z_mm.set(self._in_beam_xyz_mm[2]),
94+
)
95+
elif position == BeamstopPositions.UNKNOWN:
96+
raise ValueError(f"Cannot set beamstop to position {position}")

tests/devices/mx_phase1/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from itertools import dropwhile
2-
from unittest.mock import Mock
2+
from unittest.mock import AsyncMock, Mock
33

44
import pytest
5+
from bluesky import FailedStatus
56
from bluesky import plan_stubs as bps
67
from bluesky.preprocessors import run_decorator
78
from bluesky.run_engine import RunEngine
@@ -64,3 +65,38 @@ def check_in_beam():
6465
assert data["beamstop-y_mm"] == y
6566
assert data["beamstop-z_mm"] == z
6667
assert data["beamstop-selected_pos"] == expected_pos
68+
69+
70+
async def test_set_beamstop_position_to_data_collection_moves_beamstop_into_beam(
71+
beamline_parameters: GDABeamlineParameters, RE: RunEngine
72+
):
73+
beamstop = Beamstop("-MO-BS-01:", beamline_parameters, name="beamstop")
74+
await beamstop.connect(mock=True)
75+
76+
beamstop.x_mm.set = AsyncMock()
77+
beamstop.y_mm.set = AsyncMock()
78+
beamstop.z_mm.set = AsyncMock()
79+
set_mock_value(beamstop.x_mm.user_readback, 0)
80+
set_mock_value(beamstop.y_mm.user_readback, 0)
81+
set_mock_value(beamstop.z_mm.user_readback, 0)
82+
83+
RE(bps.abs_set(beamstop.selected_pos, BeamstopPositions.DATA_COLLECTION))
84+
85+
assert beamstop.x_mm.set.call_count == 1
86+
assert beamstop.x_mm.set.call_args[0][0] == 1.52
87+
88+
assert beamstop.y_mm.set.call_count == 1
89+
assert beamstop.y_mm.set.call_args[0][0] == 44.78
90+
91+
assert beamstop.z_mm.set.call_count == 1
92+
assert beamstop.z_mm.set.call_args[0][0] == 30.0
93+
94+
95+
async def test_set_beamstop_position_to_unknown_raises_error(
96+
beamline_parameters: GDABeamlineParameters, RE: RunEngine
97+
):
98+
beamstop = Beamstop("-MO-BS-01:", beamline_parameters, name="beamstop")
99+
await beamstop.connect(mock=True)
100+
with pytest.raises(FailedStatus) as e:
101+
RE(bps.abs_set(beamstop.selected_pos, BeamstopPositions.UNKNOWN, wait=True))
102+
assert isinstance(e.value.args[0].exception(), ValueError)

0 commit comments

Comments
 (0)