Skip to content

Commit 8b911e5

Browse files
Wait for smargon enable in robot load and unload (#1791)
* Wait for smargon enable rather than pin mounted in robot load * Fix tests * Small fixes * PR comments * Fix lint --------- Co-authored-by: Dominic Oram <dominic.oram@diamond.ac.uk>
1 parent a03a288 commit 8b911e5

File tree

2 files changed

+52
-31
lines changed

2 files changed

+52
-31
lines changed

src/dodal/devices/robot.py

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
from asyncio import FIRST_COMPLETED, CancelledError, Task, wait_for
33
from dataclasses import dataclass
4+
from enum import IntEnum
45

56
from bluesky.protocols import Movable
67
from ophyd_async.core import (
@@ -21,8 +22,8 @@
2122

2223
from dodal.log import LOGGER
2324

24-
WAIT_FOR_OLD_PIN_MSG = "Waiting on old pin unloaded"
25-
WAIT_FOR_NEW_PIN_MSG = "Waiting on new pin loaded"
25+
WAIT_FOR_BEAMLINE_DISABLE_MSG = "Waiting on beamline disable"
26+
WAIT_FOR_BEAMLINE_ENABLE_MSG = "Waiting on beamline enable"
2627

2728

2829
class RobotLoadError(Exception):
@@ -51,6 +52,11 @@ class PinMounted(StrictEnum):
5152
PIN_MOUNTED = "Pin Mounted"
5253

5354

55+
class BeamlineStatus(IntEnum):
56+
ENABLED = 0
57+
DISABLED = 1
58+
59+
5460
class ErrorStatus(Device):
5561
def __init__(self, prefix: str) -> None:
5662
self.str = epics_signal_r(str, prefix + "_ERR_MSG")
@@ -89,6 +95,8 @@ def __init__(self, prefix: str, name: str = "") -> None:
8995
self.current_puck = epics_signal_r(float, prefix + "CURRENT_PUCK_RBV")
9096
self.current_pin = epics_signal_r(float, prefix + "CURRENT_PIN_RBV")
9197

98+
self.beamline_disabled = epics_signal_r(int, prefix + "ROBOT_OP_16_BITS.B8")
99+
92100
self.next_pin = epics_signal_rw_rbv(float, prefix + "NEXT_PIN")
93101
self.next_puck = epics_signal_rw_rbv(float, prefix + "NEXT_PUCK")
94102

@@ -119,8 +127,8 @@ def __init__(self, prefix: str, name: str = "") -> None:
119127
)
120128
super().__init__(name=name)
121129

122-
async def pin_state_or_error(self, expected_state=PinMounted.PIN_MOUNTED):
123-
"""This co-routine will finish when either the pin sensor reaches the specified
130+
async def beamline_status_or_error(self, expected_state: BeamlineStatus):
131+
"""This co-routine will finish when either the beamline reaches the specified
124132
state or the robot gives an error (whichever happens first). In the case where
125133
there is an error a RobotLoadError error is raised.
126134
"""
@@ -133,12 +141,12 @@ async def raise_if_error():
133141
error_msg = await self.prog_error.str.get_value()
134142
raise RobotLoadError(error_code, error_msg)
135143

136-
async def wfv():
137-
await wait_for_value(self.gonio_pin_sensor, expected_state, None)
144+
async def wait_for_expected_state():
145+
await wait_for_value(self.beamline_disabled, expected_state.value, None)
138146

139147
tasks = [
140148
(Task(raise_if_error())),
141-
(Task(wfv())),
149+
(Task(wait_for_expected_state())),
142150
]
143151
try:
144152
finished, unfinished = await asyncio.wait(
@@ -174,12 +182,15 @@ async def _load_pin_and_puck(self, sample_location: SampleLocation):
174182
set_and_wait_for_value(self.next_pin, sample_location.pin),
175183
)
176184
await self.load.trigger()
177-
if await self.gonio_pin_sensor.get_value() == PinMounted.PIN_MOUNTED:
178-
LOGGER.info(WAIT_FOR_OLD_PIN_MSG)
179-
await self.pin_state_or_error(PinMounted.NO_PIN_MOUNTED)
180-
LOGGER.info(WAIT_FOR_NEW_PIN_MSG)
185+
await self._wait_for_beamline_enabled_after_load_or_unload()
186+
187+
async def _wait_for_beamline_enabled_after_load_or_unload(self):
188+
if await self.beamline_disabled.get_value() == BeamlineStatus.ENABLED.value:
189+
LOGGER.info(WAIT_FOR_BEAMLINE_DISABLE_MSG)
190+
await self.beamline_status_or_error(BeamlineStatus.DISABLED)
181191

182-
await self.pin_state_or_error()
192+
LOGGER.info(WAIT_FOR_BEAMLINE_ENABLE_MSG)
193+
await self.beamline_status_or_error(BeamlineStatus.ENABLED)
183194

184195
@AsyncStatus.wrap
185196
async def set(self, value: SampleLocation):
@@ -198,7 +209,10 @@ async def set(self, value: SampleLocation):
198209
)
199210
else:
200211
await self.unload.trigger(timeout=self.LOAD_TIMEOUT)
201-
await wait_for_value(self.program_running, False, self.NOT_BUSY_TIMEOUT)
212+
await wait_for(
213+
self._wait_for_beamline_enabled_after_load_or_unload(),
214+
timeout=self.LOAD_TIMEOUT + self.NOT_BUSY_TIMEOUT,
215+
)
202216
except TimeoutError as e:
203217
await self.prog_error.raise_if_error(e)
204218
await self.controller_error.raise_if_error(e)

tests/devices/test_bart_robot.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515

1616
from dodal.devices.robot import (
1717
SAMPLE_LOCATION_EMPTY,
18-
WAIT_FOR_NEW_PIN_MSG,
19-
WAIT_FOR_OLD_PIN_MSG,
18+
WAIT_FOR_BEAMLINE_DISABLE_MSG,
19+
WAIT_FOR_BEAMLINE_ENABLE_MSG,
2020
BartRobot,
21+
BeamlineStatus,
2122
PinMounted,
2223
RobotLoadError,
2324
SampleLocation,
@@ -36,14 +37,18 @@ async def robot_for_unload():
3637

3738
async def finish_later():
3839
await drying_complete.wait()
40+
await asyncio.sleep(0.1)
3941
set_mock_value(device.program_running, False)
42+
set_mock_value(device.beamline_disabled, BeamlineStatus.ENABLED.value)
4043

4144
async def fake_unload(*args, **kwargs):
4245
set_mock_value(device.program_running, True)
46+
set_mock_value(device.beamline_disabled, BeamlineStatus.DISABLED.value)
4347
await trigger_complete.wait()
4448
asyncio.create_task(finish_later())
4549

4650
get_mock_put(device.unload).side_effect = fake_unload
51+
# device.unload.trigger = AsyncMock(side_effect=fake_unload)
4752
return device, trigger_complete, drying_complete
4853

4954

@@ -52,6 +57,7 @@ async def _get_bart_robot() -> BartRobot:
5257
device.LOAD_TIMEOUT = 1 # type: ignore
5358
device.NOT_BUSY_TIMEOUT = 1 # type: ignore
5459
await device.connect(mock=True)
60+
# set_mock_value(device.beamline_disabled, BeamlineStatus.DISABLED.value)
5561
return device
5662

5763

@@ -109,7 +115,7 @@ async def test_given_program_not_running_but_pin_not_unmounting_when_load_pin_th
109115
await device.set(SampleLocation(15, 10))
110116
device.load.trigger.assert_called_once() # type:ignore
111117
last_log = patch_logger.mock_calls[1].args[0]
112-
assert "Waiting on old pin unloaded" in last_log
118+
assert WAIT_FOR_BEAMLINE_DISABLE_MSG in last_log
113119

114120

115121
@patch("dodal.devices.robot.LOGGER")
@@ -127,34 +133,34 @@ async def test_given_program_not_running_and_pin_unmounting_but_new_pin_not_moun
127133
try:
128134
device.load.trigger.assert_called_once() # type:ignore
129135
last_log = patch_logger.mock_calls[1].args[0]
130-
assert "Waiting on new pin loaded" in last_log
136+
assert WAIT_FOR_BEAMLINE_DISABLE_MSG in last_log
131137
except AssertionError:
132138
traceback.print_exception(exc_info.value)
133139
raise
134140

135141

136-
def _set_pin_sensor_on_log_messages(device: BartRobot, msg: str):
137-
if msg == WAIT_FOR_OLD_PIN_MSG:
138-
set_mock_value(device.gonio_pin_sensor, PinMounted.NO_PIN_MOUNTED)
139-
elif msg == WAIT_FOR_NEW_PIN_MSG:
140-
set_mock_value(device.gonio_pin_sensor, PinMounted.PIN_MOUNTED)
142+
def _set_beamline_enabled_on_log_messages(device: BartRobot, msg: str):
143+
if msg == WAIT_FOR_BEAMLINE_DISABLE_MSG:
144+
set_mock_value(device.beamline_disabled, BeamlineStatus.DISABLED.value)
145+
elif msg == WAIT_FOR_BEAMLINE_ENABLE_MSG:
146+
set_mock_value(device.beamline_disabled, BeamlineStatus.ENABLED.value)
141147

142148

143149
def _error_on_unload_log_messages(device: BartRobot, msg: str):
144-
if msg == WAIT_FOR_OLD_PIN_MSG:
150+
if msg == WAIT_FOR_BEAMLINE_DISABLE_MSG:
145151
set_mock_value(device.prog_error.code, 40)
146152
set_mock_value(device.prog_error.str, "Test error")
147153

148154

149-
# Use log info messages to determine when to set the gonio_pin_sensor, so we don't have to use any sleeps during testing
155+
# Use log info messages to determine when to set the beamline enable, so we don't have to use any sleeps during testing
150156
async def set_with_happy_path(
151157
device: BartRobot, mock_log_info: MagicMock
152158
) -> AsyncStatus:
153159
"""Mocks the logic that the robot would do on a successful load"""
154160

155-
mock_log_info.side_effect = partial(_set_pin_sensor_on_log_messages, device)
161+
mock_log_info.side_effect = partial(_set_beamline_enabled_on_log_messages, device)
156162
set_mock_value(device.program_running, False)
157-
set_mock_value(device.gonio_pin_sensor, PinMounted.PIN_MOUNTED)
163+
set_mock_value(device.beamline_disabled, BeamlineStatus.ENABLED.value)
158164
status = device.set(SampleLocation(15, 10))
159165
return status
160166

@@ -166,7 +172,7 @@ async def set_with_unhappy_path(
166172

167173
mock_log_info.side_effect = partial(_error_on_unload_log_messages, device)
168174
set_mock_value(device.program_running, False)
169-
set_mock_value(device.gonio_pin_sensor, PinMounted.PIN_MOUNTED)
175+
set_mock_value(device.beamline_disabled, BeamlineStatus.ENABLED.value)
170176
status = device.set(SampleLocation(15, 10))
171177
return status
172178

@@ -187,15 +193,16 @@ async def test_given_program_not_running_and_pin_unmounts_then_mounts_when_load_
187193
async def test_given_waiting_for_pin_to_mount_when_no_pin_mounted_then_error_raised():
188194
device = await _get_bart_robot()
189195
set_mock_value(device.prog_error.code, 25)
190-
status = device.pin_state_or_error()
196+
set_mock_value(device.beamline_disabled, BeamlineStatus.DISABLED.value)
197+
status = device.beamline_status_or_error(BeamlineStatus.ENABLED)
191198
with pytest.raises(RobotLoadError):
192199
await status
193200

194201

195-
async def test_given_waiting_for_pin_to_mount_when_pin_mounted_then_no_error_raised():
202+
async def test_given_waiting_for_beamline_to_enable_when_beamline_enabled_then_no_error_raised():
196203
device = await _get_bart_robot()
197-
status = create_task(device.pin_state_or_error())
198-
set_mock_value(device.gonio_pin_sensor, PinMounted.PIN_MOUNTED)
204+
status = create_task(device.beamline_status_or_error(BeamlineStatus.ENABLED))
205+
set_mock_value(device.beamline_disabled, BeamlineStatus.ENABLED.value)
199206
await status
200207

201208

0 commit comments

Comments
 (0)