Skip to content

Commit

Permalink
fix(hardware): cancel moves after stop condition (#13058)
Browse files Browse the repository at this point in the history
When we command move groups with a stall stop condition, if the stop
condition occurs in the first or second move the microcontroller will
cancel the remaining moves in the group. The move group runner needs to
not wait for those moves since they'll never occur.

Closes RQA-1043
  • Loading branch information
sfoster1 authored Jul 7, 2023
1 parent bbffb4a commit 2816a75
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 7 deletions.
27 changes: 20 additions & 7 deletions hardware/opentrons_hardware/hardware_control/move_group_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,18 +412,31 @@ def _handle_move_completed(
try:
stop_cond = self._stop_condition[group_id][seq_id]
if (
stop_cond
in [
MoveStopCondition.limit_switch,
MoveStopCondition.limit_switch_backoff,
]
and ack_id != MoveAckId.stopped_by_condition
):
(
stop_cond.value
& (
MoveStopCondition.limit_switch.value
| MoveStopCondition.limit_switch_backoff.value
)
)
!= 0
) and ack_id != MoveAckId.stopped_by_condition:
log.error(
f"Homing move from node {node_id} completed without meeting condition {stop_cond}"
)
self._should_stop = True
self._event.set()
if (
stop_cond.value & MoveStopCondition.stall.value
) and ack_id == MoveAckId.stopped_by_condition:
# When an axis has a stop-on-stall move and stalls, it will clear the rest of its executing moves.
# If we wait for those moves, we'll time out.
remaining = [elem for elem in self._moves[group_id]]
for move_node, move_seq in remaining:
if node_id == move_node:
self._moves[group_id].remove((move_node, move_seq))
if not self._moves[group_id]:
self._event.set()
except IndexError:
# If we have two move group runners running at once, they each
# pick up groups they don't care about, and need to not fail.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
ErrorCode,
ErrorSeverity,
PipetteTipActionType,
MoveAckId,
)
from opentrons_hardware.drivers.can_bus.can_messenger import (
MessageListenerCallback,
Expand Down Expand Up @@ -476,12 +477,14 @@ def __init__(
listener: MessageListenerCallback,
start_at_index: int = 0,
ack_id: int = 1,
ignore_seq_ids: List[int] = [],
) -> None:
"""Constructor."""
self._move_groups = move_groups
self._listener = listener
self._start_at_index = start_at_index
self._ack_id = ack_id
self._ignore_seq_ids = ignore_seq_ids

@property
def groups(self) -> MoveGroups:
Expand All @@ -506,6 +509,8 @@ async def mock_send(
for seq_id, moves in enumerate(
self._move_groups[message.payload.group_id.value - self._start_at_index]
):
if seq_id in self._ignore_seq_ids:
continue
for node, move in moves.items():
if isinstance(move, MoveGroupSingleAxisStep):
payload = MoveCompletedPayload(
Expand Down Expand Up @@ -1251,3 +1256,47 @@ async def test_multiple_move_error(
with pytest.raises(RuntimeError):
await subject.run(can_messenger=mock_can_messenger)
assert mock_sender.call_count == 2


@pytest.fixture
def move_group_with_stall() -> MoveGroups:
"""Move group with a stop-on-stall."""
return [
# Group 0
[
{
NodeId.head_l: MoveGroupSingleAxisStep(
distance_mm=float64(229),
velocity_mm_sec=float64(235),
duration_sec=float64(2142),
acceleration_mm_sec_sq=float64(1000),
stop_condition=MoveStopCondition.stall,
),
},
{
NodeId.head_l: MoveGroupSingleAxisStep(
distance_mm=float64(229),
velocity_mm_sec=float64(235),
duration_sec=float64(2142),
acceleration_mm_sec_sq=float64(1000),
stop_condition=MoveStopCondition.stall,
),
},
],
]


async def test_moves_removed_on_stall_detected(
mock_can_messenger: AsyncMock, move_group_with_stall: MoveGroups
) -> None:
"""Check that remaining moves for a node are removed when it stops on stall."""
subject = MoveScheduler(move_groups=move_group_with_stall)
mock_sender = MockSendMoveCompleter(
move_group_with_stall,
subject,
ack_id=MoveAckId.stopped_by_condition,
ignore_seq_ids=[1],
)
mock_can_messenger.ensure_send.side_effect = mock_sender.mock_ensure_send
mock_can_messenger.send.side_effect = mock_sender.mock_send
await subject.run(can_messenger=mock_can_messenger)

0 comments on commit 2816a75

Please sign in to comment.