Skip to content

Commit 36663c3

Browse files
authored
feat(robot-server): Support labware offset notifications (#19051)
1 parent 2bc46f4 commit 36663c3

File tree

2 files changed

+70
-16
lines changed

2 files changed

+70
-16
lines changed

robot-server/robot_server/service/notifications/publishers/runs_publisher.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class _EngineStateSlice:
3131
current_command: Optional[CommandPointer] = None
3232
recovery_target_command: Optional[CommandPointer] = None
3333
state_summary_status: Optional[EngineStatus] = None
34+
state_summary_labware_offset_count: Optional[int] = None
3435

3536

3637
class RunsPublisher:
@@ -49,7 +50,7 @@ def __init__(
4950
[
5051
self._handle_current_command_change,
5152
self._handle_recovery_target_command_change,
52-
self._handle_engine_status_change,
53+
self._handle_relevant_engine_change,
5354
]
5455
)
5556

@@ -145,20 +146,30 @@ async def _handle_recovery_target_command_change(self) -> None:
145146
new_recovery_target_command
146147
)
147148

148-
async def _handle_engine_status_change(self) -> None:
149-
"""Publish a refetch flag if the engine status has changed."""
149+
async def _handle_relevant_engine_change(self) -> None:
150+
"""Publish a refetch flag if relevant engine changes occur."""
150151
if self._run_hooks is not None and self._engine_state_slice is not None:
151152
new_state_summary = self._run_hooks.get_state_summary(
152153
self._run_hooks.run_id
153154
)
154155

155-
if (
156-
new_state_summary is not None
157-
and self._engine_state_slice.state_summary_status
158-
!= new_state_summary.status
159-
):
160-
self.publish_runs_advise_refetch(run_id=self._run_hooks.run_id)
161-
self._engine_state_slice.state_summary_status = new_state_summary.status
156+
if new_state_summary is not None:
157+
if (
158+
self._engine_state_slice.state_summary_status
159+
!= new_state_summary.status
160+
):
161+
self.publish_runs_advise_refetch(run_id=self._run_hooks.run_id)
162+
self._engine_state_slice.state_summary_status = (
163+
new_state_summary.status
164+
)
165+
166+
elif self._engine_state_slice.state_summary_labware_offset_count != len(
167+
new_state_summary.labwareOffsets
168+
):
169+
self.publish_runs_advise_refetch(run_id=self._run_hooks.run_id)
170+
self._engine_state_slice.state_summary_labware_offset_count = len(
171+
new_state_summary.labwareOffsets
172+
)
162173

163174

164175
_runs_publisher_accessor: AppStateAccessor[RunsPublisher] = AppStateAccessor[

robot-server/tests/service/notifications/publishers/test_runs_publisher.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ async def test_initialize(
6969
assert runs_publisher._engine_state_slice.current_command is None
7070
assert runs_publisher._engine_state_slice.recovery_target_command is None
7171
assert runs_publisher._engine_state_slice.state_summary_status is None
72+
assert runs_publisher._engine_state_slice.state_summary_labware_offset_count is None
7273

7374
notification_client.publish_advise_refetch.assert_any_call(topic=topics.RUNS)
7475
notification_client.publish_advise_refetch.assert_any_call(
@@ -171,10 +172,10 @@ async def test_handle_recovery_target_command_change(
171172
)
172173

173174

174-
async def test_handle_engine_status_change(
175+
async def test_handle_relevant_engine_change(
175176
runs_publisher: RunsPublisher, notification_client: Mock
176177
) -> None:
177-
"""It should handle engine status changes appropriately."""
178+
"""It should handle relevant engine changes appropriately."""
178179
runs_publisher.start_publishing_for_run(
179180
run_id="1234",
180181
get_current_command=lambda _: make_command_pointer("command1"),
@@ -189,26 +190,68 @@ async def test_handle_engine_status_change(
189190

190191
runs_publisher._run_hooks.run_id = "1234"
191192
runs_publisher._run_hooks.get_state_summary = MagicMock(
192-
return_value=MagicMock(status=EngineStatus.IDLE)
193+
return_value=MagicMock(status=EngineStatus.IDLE, labwareOffsets=[])
193194
)
194195
runs_publisher._engine_state_slice.state_summary_status = EngineStatus.IDLE
196+
runs_publisher._engine_state_slice.state_summary_labware_offset_count = 0
195197

196-
await runs_publisher._handle_engine_status_change()
198+
await runs_publisher._handle_relevant_engine_change()
197199

198200
assert notification_client.publish_advise_refetch.call_count == 2
199201

200202
runs_publisher._run_hooks.get_state_summary.return_value = MagicMock(
201-
status=EngineStatus.RUNNING
203+
status=EngineStatus.RUNNING, labwareOffsets=[]
202204
)
203205

204-
await runs_publisher._handle_engine_status_change()
206+
await runs_publisher._handle_relevant_engine_change()
205207

206208
notification_client.publish_advise_refetch.assert_any_call(topic=topics.RUNS)
207209
notification_client.publish_advise_refetch.assert_any_call(
208210
topic=f"{topics.RUNS}/1234"
209211
)
210212

211213

214+
async def test_handle_labware_offset_count_change(
215+
runs_publisher: RunsPublisher, notification_client: Mock
216+
) -> None:
217+
"""It should handle labware offset count changes appropriately."""
218+
runs_publisher.start_publishing_for_run(
219+
run_id="1234",
220+
get_current_command=lambda _: make_command_pointer("command1"),
221+
get_recovery_target_command=AsyncMock(),
222+
get_state_summary=AsyncMock(),
223+
)
224+
225+
# todo(mm, 2024-05-21): We should test through the public interface of the subject,
226+
# not through its private attributes.
227+
assert runs_publisher._run_hooks
228+
assert runs_publisher._engine_state_slice
229+
230+
initial_offsets = ["offset1", "offset2"]
231+
runs_publisher._run_hooks.run_id = "1234"
232+
runs_publisher._run_hooks.get_state_summary = MagicMock(
233+
return_value=MagicMock(status=EngineStatus.IDLE, labwareOffsets=initial_offsets)
234+
)
235+
runs_publisher._engine_state_slice.state_summary_status = EngineStatus.IDLE
236+
runs_publisher._engine_state_slice.state_summary_labware_offset_count = 2
237+
238+
await runs_publisher._handle_relevant_engine_change()
239+
assert notification_client.publish_advise_refetch.call_count == 2
240+
241+
new_offsets = ["offset1", "offset2", "offset3"]
242+
runs_publisher._run_hooks.get_state_summary.return_value = MagicMock(
243+
status=EngineStatus.IDLE, labwareOffsets=new_offsets
244+
)
245+
246+
await runs_publisher._handle_relevant_engine_change()
247+
248+
notification_client.publish_advise_refetch.assert_any_call(topic=topics.RUNS)
249+
notification_client.publish_advise_refetch.assert_any_call(
250+
topic=f"{topics.RUNS}/1234"
251+
)
252+
assert runs_publisher._engine_state_slice.state_summary_labware_offset_count == 3
253+
254+
212255
async def test_publish_pre_serialized_commannds_notif(
213256
runs_publisher: RunsPublisher, notification_client: Mock
214257
) -> None:

0 commit comments

Comments
 (0)