Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(api): Port AddressableAreaStore to StateUpdate #17027

Merged
merged 17 commits into from
Dec 10, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Port moveLabware.
  • Loading branch information
SyntaxColoring committed Dec 4, 2024
commit cdfab24d7261bacda830e15d9327d8fb36bbda74
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_engine/commands/move_labware.py
Original file line number Diff line number Diff line change
@@ -156,6 +156,7 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
area_name
)
state_update.set_addressable_area_used(addressable_area_name=area_name)

if fixture_validation.is_gripper_waste_chute(area_name):
# When dropping off labware in the waste chute, some bigger pieces
@@ -201,6 +202,9 @@ async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C
self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration(
params.newLocation.slotName.id
)
state_update.set_addressable_area_used(
addressable_area_name=params.newLocation.slotName.id
)

available_new_location = self._state_view.geometry.ensure_location_not_occupied(
location=params.newLocation
16 changes: 1 addition & 15 deletions api/src/opentrons/protocol_engine/state/addressable_areas.py
Original file line number Diff line number Diff line change
@@ -12,10 +12,6 @@

from opentrons.types import Point, DeckSlotName

from ..commands import (
Command,
MoveLabwareResult,
)
from ..errors import (
IncompatibleAddressableAreaError,
AreaNotInDeckConfigurationError,
@@ -35,7 +31,6 @@
from ..actions.get_state_update import get_state_updates
from ..actions import (
Action,
SucceedCommandAction,
SetDeckConfigurationAction,
AddAddressableAreaAction,
)
@@ -199,9 +194,7 @@ def handle_action(self, action: Action) -> None:
state_update.addressable_area_used.addressable_area_name
)

if isinstance(action, SucceedCommandAction):
self._handle_command(action.command)
elif isinstance(action, AddAddressableAreaAction):
if isinstance(action, AddAddressableAreaAction):
self._add_addressable_area(action.addressable_area_name)
elif isinstance(action, SetDeckConfigurationAction):
current_state = self._state
@@ -217,13 +210,6 @@ def handle_action(self, action: Action) -> None:
)
)

def _handle_command(self, command: Command) -> None:
"""Modify state in reaction to a command."""
if isinstance(command.result, MoveLabwareResult):
location = command.params.newLocation
if isinstance(location, (DeckSlotLocation, AddressableAreaLocation)):
self._add_addressable_area(location)

@staticmethod
def _get_addressable_areas_from_deck_configuration(
deck_config: DeckConfigurationType, deck_definition: DeckDefinitionV5
58 changes: 34 additions & 24 deletions api/tests/opentrons/protocol_engine/commands/test_move_labware.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Test the ``moveLabware`` command."""
from datetime import datetime
import inspect
from unittest.mock import sentinel

import pytest
from decoy import Decoy, matchers

@@ -90,9 +92,10 @@ async def test_manual_move_labware_implementation(
times_pause_called: int,
) -> None:
"""It should execute a pause and return the new offset."""
new_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_4)
data = MoveLabwareParams(
labwareId="my-cool-labware-id",
newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_4),
newLocation=new_location,
strategy=strategy,
)

@@ -131,7 +134,10 @@ async def test_manual_move_labware_implementation(
labware_id="my-cool-labware-id",
offset_id="wowzers-a-new-offset-id",
new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_5),
)
),
addressable_area_used=update_types.AddressableAreaUsedUpdate(
addressable_area_name=new_location.slotName.id
),
),
)

@@ -211,20 +217,19 @@ async def test_gripper_move_labware_implementation(
"""It should delegate to the equipment handler and return the new offset."""
from_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_1)
new_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_5)
pick_up_offset = LabwareOffsetVector(x=1, y=2, z=3)

data = MoveLabwareParams(
labwareId="my-cool-labware-id",
newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_4),
newLocation=new_location,
strategy=LabwareMovementStrategy.USING_GRIPPER,
pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3),
pickUpOffset=pick_up_offset,
dropOffset=None,
)

decoy.when(
state_view.labware.get_definition(labware_id="my-cool-labware-id")
).then_return(
LabwareDefinition.construct(namespace="my-cool-namespace") # type: ignore[call-arg]
)
).then_return(sentinel.labware_definition)
decoy.when(state_view.labware.get(labware_id="my-cool-labware-id")).then_return(
LoadedLabware(
id="my-cool-labware-id",
@@ -235,40 +240,36 @@ async def test_gripper_move_labware_implementation(
)
)
decoy.when(
state_view.geometry.ensure_location_not_occupied(
location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4),
)
).then_return(DeckSlotLocation(slotName=DeckSlotName.SLOT_5))
state_view.geometry.ensure_location_not_occupied(location=new_location)
).then_return(sentinel.new_location_validated_unoccupied)
decoy.when(
equipment.find_applicable_labware_offset_id(
labware_definition_uri="opentrons-test/load-name/1",
labware_location=new_location,
labware_location=sentinel.new_location_validated_unoccupied,
)
).then_return("wowzers-a-new-offset-id")

validated_from_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_6)
validated_new_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_7)
decoy.when(
state_view.geometry.ensure_valid_gripper_location(from_location)
).then_return(validated_from_location)
decoy.when(
state_view.geometry.ensure_valid_gripper_location(new_location)
).then_return(validated_new_location)
).then_return(sentinel.from_location_validated_for_gripper)
decoy.when(
labware_validation.validate_gripper_compatible(
LabwareDefinition.construct(namespace="my-cool-namespace") # type: ignore[call-arg]
state_view.geometry.ensure_valid_gripper_location(
sentinel.new_location_validated_unoccupied
)
).then_return(sentinel.new_location_validated_for_gripper)
decoy.when(
labware_validation.validate_gripper_compatible(sentinel.labware_definition)
).then_return(True)

result = await subject.execute(data)
decoy.verify(
state_view.labware.raise_if_labware_has_labware_on_top("my-cool-labware-id"),
await labware_movement.move_labware_with_gripper(
labware_id="my-cool-labware-id",
current_location=validated_from_location,
new_location=validated_new_location,
current_location=sentinel.from_location_validated_for_gripper,
new_location=sentinel.new_location_validated_for_gripper,
user_offset_data=LabwareMovementOffsetData(
pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3),
pickUpOffset=pick_up_offset,
dropOffset=LabwareOffsetVector(x=0, y=0, z=0),
),
post_drop_slide_offset=None,
@@ -282,9 +283,12 @@ async def test_gripper_move_labware_implementation(
pipette_location=update_types.CLEAR,
labware_location=update_types.LabwareLocationUpdate(
labware_id="my-cool-labware-id",
new_location=new_location,
new_location=sentinel.new_location_validated_unoccupied,
offset_id="wowzers-a-new-offset-id",
),
addressable_area_used=update_types.AddressableAreaUsedUpdate(
addressable_area_name=new_location.slotName.id
),
),
)

@@ -380,6 +384,9 @@ async def test_gripper_error(
state_update=update_types.StateUpdate(
labware_location=update_types.NO_CHANGE,
pipette_location=update_types.CLEAR,
addressable_area_used=update_types.AddressableAreaUsedUpdate(
addressable_area_name=new_location.slotName.id
),
),
)

@@ -520,6 +527,9 @@ async def test_gripper_move_to_waste_chute_implementation(
new_location=new_location,
offset_id="wowzers-a-new-offset-id",
),
addressable_area_used=update_types.AddressableAreaUsedUpdate(
addressable_area_name=new_location.addressableAreaName
),
),
)

Original file line number Diff line number Diff line change
@@ -9,8 +9,6 @@

from opentrons_shared_data.deck.types import DeckDefinitionV5

from opentrons.types import DeckSlotName

from opentrons.protocol_engine.commands import Command, Comment
from opentrons.protocol_engine.actions import (
SucceedCommandAction,
@@ -25,13 +23,6 @@
from opentrons.protocol_engine.types import (
DeckType,
DeckConfigurationType,
LabwareMovementStrategy,
DeckSlotLocation,
AddressableAreaLocation,
)

from .command_fixtures import (
create_move_labware_command,
)


@@ -168,37 +159,6 @@ def test_initial_state(
assert len(subject.state.loaded_addressable_areas_by_name) == 16


# todo(mm, 2024-12-02): Delete in favor of test_addressable_area_usage_in_simulation()
# when all of these commands have been ported to StateUpdate.
@pytest.mark.parametrize(
("command", "expected_area"),
(
(
create_move_labware_command(
new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1),
strategy=LabwareMovementStrategy.USING_GRIPPER,
),
"A1",
),
(
create_move_labware_command(
new_location=AddressableAreaLocation(addressableAreaName="A4"),
strategy=LabwareMovementStrategy.USING_GRIPPER,
),
"A4",
),
),
)
def test_addressable_area_referencing_commands_load_on_simulated_deck(
command: Command,
expected_area: str,
simulated_subject: AddressableAreaStore,
) -> None:
"""It should check and store the addressable area when referenced in a command."""
simulated_subject.handle_action(SucceedCommandAction(command=command))
assert expected_area in simulated_subject.state.loaded_addressable_areas_by_name


@pytest.mark.parametrize("addressable_area_name", ["A1", "A4", "gripperWasteChute"])
def test_addressable_area_usage_in_simulation(
simulated_subject: AddressableAreaStore,
@@ -225,37 +185,6 @@ def test_addressable_area_usage_in_simulation(
)


# todo(mm, 2024-12-02): Delete in favor of test_addressable_area_usage()
# when all of these commands have been ported to StateUpdate.
@pytest.mark.parametrize(
("command", "expected_area"),
(
(
create_move_labware_command(
new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1),
strategy=LabwareMovementStrategy.USING_GRIPPER,
),
"A1",
),
(
create_move_labware_command(
new_location=AddressableAreaLocation(addressableAreaName="C4"),
strategy=LabwareMovementStrategy.USING_GRIPPER,
),
"C4",
),
),
)
def test_addressable_area_referencing_commands_load(
command: Command,
expected_area: str,
subject: AddressableAreaStore,
) -> None:
"""It should check that the addressable area is in the deck config."""
subject.handle_action(SucceedCommandAction(command=command))
assert expected_area in subject.state.loaded_addressable_areas_by_name


@pytest.mark.parametrize("addressable_area_name", ["A1", "C4"])
def test_addressable_area_usage(
subject: AddressableAreaStore,