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

feat(shared-data,api,robot-server): Use new OT-3 deck slot naming style #12571

Merged
merged 74 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
fdcd747
Update OT-3 deck definition IDs to be OT-3-style.
SyntaxColoring Apr 17, 2023
5962e86
Update module definitions to refer to new OT-3 deck IDs.
SyntaxColoring Apr 17, 2023
378b8ce
Remove internal stringification from legacy resolve_module_location().
SyntaxColoring Apr 21, 2023
8298053
Remove meddlesome assertion.
SyntaxColoring Apr 18, 2023
1d468d3
Hack together some canonicalization for load_labware.
SyntaxColoring Apr 21, 2023
1542195
Update command schema.
SyntaxColoring Apr 21, 2023
8fa9f6e
Fix module offset test.
SyntaxColoring Apr 24, 2023
57722e0
Canonicalize loadModule commands.
SyntaxColoring Apr 24, 2023
7832120
canonicalize->normalize
SyntaxColoring May 4, 2023
ae467cb
Add integration test for normalization over HTTP.
SyntaxColoring May 4, 2023
9e80b44
Fix bug in loadModule command normalization.
SyntaxColoring May 4, 2023
7d6c060
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 4, 2023
3f29948
Fix command turning itself into a non-setup command.
SyntaxColoring May 5, 2023
96ec51c
Normalize labware offset requests.
SyntaxColoring May 5, 2023
943960e
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 8, 2023
0a76173
use words more good
SyntaxColoring May 8, 2023
91c7891
by the robot type -> for the robot type
SyntaxColoring May 8, 2023
cf36fc1
Reconcile Jeremy's DeckSlotName changes with mine.
SyntaxColoring May 8, 2023
2c1af00
Add some more internal DeckSlotName documentation.
SyntaxColoring May 8, 2023
e9f5241
Leave todos on Deck methods that look private.
SyntaxColoring May 8, 2023
9117287
Normalize input slot in Deck.get_slot_definition().
jbleon95 May 8, 2023
ebb064c
Move labware offset standardization to a mockable function.
SyntaxColoring May 8, 2023
a3e3492
refactor: Delete a bunch of unused fixtures in test_protocol_engine.py.
SyntaxColoring May 9, 2023
63c2754
Test that ProtocolEngine.add_command() standardizes its input.
SyntaxColoring May 9, 2023
290e326
Start on some command standardization tests.
SyntaxColoring May 9, 2023
affb4fb
Also test ModuleLocations.
SyntaxColoring May 10, 2023
b6f927c
Boundless intelligence.
SyntaxColoring May 10, 2023
6b964e3
Test standardization of loadModule commands.
SyntaxColoring May 10, 2023
dce11e8
Also test off-deck locations.
SyntaxColoring May 10, 2023
db2a1de
Add failing tests for moveLabware standardization.
SyntaxColoring May 10, 2023
301407e
Add todo comment for Thermocycler dodging.
SyntaxColoring May 10, 2023
d5b7ca0
Fix get_edge_path_type() not accounting for OT-3 slots.
SyntaxColoring May 10, 2023
eed2450
Fix fixed trash slot.
SyntaxColoring May 10, 2023
7f16a61
Scaffold command standardization functions.
SyntaxColoring May 10, 2023
3aab9c9
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 18, 2023
cbb9e75
Lint/typecheck fixups.
SyntaxColoring May 18, 2023
05791c4
Update create_protocol_engine() test.
SyntaxColoring May 18, 2023
b5b3520
Work around test_geometry.py depending on legacy Deck class.
SyntaxColoring May 18, 2023
1b46a38
Add docstrings to test_slot_standardization.py.
SyntaxColoring May 18, 2023
ca87322
Extract into free-standing functions; implement for moveLabware.
SyntaxColoring May 18, 2023
248f8df
Docstrings and comments.
SyntaxColoring May 19, 2023
0829f2f
Update DeckDataProvider test.
SyntaxColoring May 19, 2023
165fa95
Revert "Remove internal stringification from legacy resolve_module_lo…
SyntaxColoring May 22, 2023
bf8ea77
Remove vestigial TypeVar from commands.py.
SyntaxColoring May 22, 2023
5e084e4
Undo unnecessary formatting changes.
SyntaxColoring May 22, 2023
d02e385
Fix comment typos.
SyntaxColoring May 22, 2023
a077f54
get_slot_definition(): Be explicit about id stringification.
SyntaxColoring May 22, 2023
b7a2be4
Document module calibration storing slots as an int.
SyntaxColoring May 22, 2023
3dabc07
Remove problematic assert from hardware_testing, and document `slot: …
SyntaxColoring May 22, 2023
37c437e
Add tests and improve documentation for DeckSlotName.
SyntaxColoring May 22, 2023
e675c08
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 22, 2023
a7f2a3f
Only guarantee nice errors if the caller obeys type-checking.
SyntaxColoring May 22, 2023
7631940
Document auto-conversion behavior at the HTTP level.
SyntaxColoring May 23, 2023
d064f1d
Add missing comma.
SyntaxColoring May 23, 2023
fb77c84
Add tests for get_edge_path_type().
SyntaxColoring May 23, 2023
b691461
Add test for get_fixed_trash_slot().
SyntaxColoring May 23, 2023
2410f19
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 25, 2023
f6d8c9d
Explicitly name right-side OT-3 slots.
SyntaxColoring May 25, 2023
177f459
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 26, 2023
15823b3
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 30, 2023
7efb245
Port Tavern test to Python so we can parametrize it over OT-2/OT-3 se…
SyntaxColoring May 30, 2023
f043491
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring May 31, 2023
4da0510
Leave a bunch of todo comments for hard-coded slots in PE.
SyntaxColoring May 30, 2023
f7a4a2c
Remove todo comment for loading Thermocycler in slot 7.
SyntaxColoring May 31, 2023
a04fcc7
Resolve todo about hard-coded _INSTRUMENT_ATTACH_SLOT.
SyntaxColoring May 31, 2023
f944ff6
Resolve todo for fetching the deck-appropriate middle slot for TC dod…
SyntaxColoring May 31, 2023
746aab4
Update slot pairs for Thermocycler dodging. Close RLAB-339.
SyntaxColoring May 31, 2023
6b9f4f1
Remove todo in LegacyCommandMapper.
SyntaxColoring May 31, 2023
4fc063b
Release dem notes.
SyntaxColoring Jun 1, 2023
14b5ff3
Merge branch 'edge' into flex_deck_slot_names
SyntaxColoring Jun 2, 2023
13560a5
Update expected slots in ReturnTip.test.tsx.
SyntaxColoring Jun 2, 2023
3ac7c46
Update expected slots in PickUpTip.test.tsx.
SyntaxColoring Jun 2, 2023
c9b7f78
Update expected slots in CheckItem.test.tsx.
SyntaxColoring Jun 2, 2023
0c52b1e
Hurl my computer and all peripherals into the ocean
SyntaxColoring Jun 2, 2023
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
15 changes: 14 additions & 1 deletion api/release-notes-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,21 @@ This is internal release 0.11.0 for the Opentrons Flex robot software, involving

Some things are known not to work, and are listed below. Specific compatibility notes about peripheral hardware are also listed.

## Smaller Fun Features
## Update Notes

- ⚠️ After upgrading your robot to 0.11.0, you'll need to factory-reset its run history before you can use it.

1. From the robot's 3-dot menu (⋮), go to **Robot settings.**
2. Under **Advanced > Factory reset**, select **Choose reset settings.**
3. Choose **Clear protocol run history,** and then select **Clear data and restart robot.**

Note that this will remove all of your saved labware offsets.

You will need to follow these steps if you subsequently downgrade back to a prior release, too.

## New Stuff In This Release

- When interacting with an OT-3, the app will use the newer names for the deck slots, like "C2", instead of the names from the OT-2, like "5".
- The `requirements` dict in Python protocols can now have `"robotType": "Flex"` instead of `"robotType": "OT-3"`. `"OT-3"` will still work, but it's discouraged because it's not the customer-facing name.

# Internal Release 0.9.0
Expand Down
9 changes: 8 additions & 1 deletion api/src/opentrons/calibration_storage/ot3/models/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,14 @@ class Config:
class ModuleOffsetModel(BaseModel):
offset: Point = Field(..., description="Module offset found from calibration.")
mount: OT3Mount = Field(..., description="The mount used to calibrate this module.")
slot: int = Field(..., description="The slot this module was calibrated in.")
slot: int = Field(
...,
description=(
"The slot this module was calibrated in."
" For historical reasons, this is specified as an OT-2-style integer like `5`,"
" not an OT-3-style string like `'C2'`."
),
)
module: ModuleType = Field(..., description="The module type of this module.")
module_id: str = Field(..., description="The unique id of this module.")
instrument_id: str = Field(
Expand Down
16 changes: 14 additions & 2 deletions api/src/opentrons/protocol_api/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,47 +82,59 @@ def __len__(self) -> int:
"""Get the number of slots on the deck."""
return len(self._slot_definitions_by_name)

# todo(mm, 2023-05-08): This may be internal and removable from this public class. Jira RSS-236.
def right_of(self, slot: DeckLocation) -> Optional[DeckItem]:
"""Get the item directly to the right of the given slot, if any."""
slot_name = _get_slot_name(slot)
east_slot = adjacent_slots_getters.get_east_slot(slot_name.as_int())

return self[east_slot] if east_slot is not None else None

# todo(mm, 2023-05-08): This may be internal and removable from this public class. Jira RSS-236.
def left_of(self, slot: DeckLocation) -> Optional[DeckItem]:
"""Get the item directly to the left of the given slot, if any."""
slot_name = _get_slot_name(slot)
west_slot = adjacent_slots_getters.get_west_slot(slot_name.as_int())

return self[west_slot] if west_slot is not None else None

# todo(mm, 2023-05-08): This is undocumented in the public PAPI, but is used in some protocols
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
# written by Applications Engineering. Either officially document this, or decide it's internal
# and remove it from this class. Jira RSS-236.
def position_for(self, slot: DeckLocation) -> Location:
"""Get the absolute location of a deck slot's front-left corner."""
slot_definition = self.get_slot_definition(slot)
x, y, z = slot_definition["position"]

return Location(point=Point(x, y, z), labware=slot_definition["id"])

# todo(mm, 2023-05-08): This may be internal and removable from this public class. Jira RSS-236.
def get_slot_definition(self, slot: DeckLocation) -> SlotDefV3:
"""Get the geometric definition data of a slot."""
slot_name = _get_slot_name(slot)
return self._slot_definitions_by_name[slot_name.id]
slot_name = validation.ensure_deck_slot_string(
_get_slot_name(slot), self._protocol_core.robot_type
)
return self._slot_definitions_by_name[slot_name]

# todo(mm, 2023-05-08): This may be internal and removable from this public class. Jira RSS-236.
def get_slot_center(self, slot: DeckLocation) -> Point:
"""Get the absolute coordinates of a slot's center."""
slot_name = _get_slot_name(slot)
return self._protocol_core.get_slot_center(slot_name)

# todo(mm, 2023-05-08): This may be internal and removable from this public class. Jira RSS-236.
@property
def highest_z(self) -> float:
"""Get the height of the tallest known point on the deck."""
return self._protocol_core.get_highest_z()

# todo(mm, 2023-05-08): This appears internal. Remove it from this public class. Jira RSS-236.
@property
def slots(self) -> List[SlotDefV3]:
"""Get a list of all slot definitions."""
return list(self._slot_definitions_by_name.values())

# todo(mm, 2023-05-08): This appears internal. Remove it from this public class. Jira RSS-236.
@property
def calibration_positions(self) -> List[CalibrationPosition]:
"""Get a list of all calibration positions on the deck."""
Expand Down
5 changes: 1 addition & 4 deletions api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ def ensure_deck_slot(deck_slot: Union[int, str]) -> DeckSlotName:


def ensure_deck_slot_string(slot_name: DeckSlotName, robot_type: RobotType) -> str:
if robot_type == "OT-2 Standard":
return str(slot_name)
else:
return slot_name.as_coordinate()
return slot_name.to_equivalent_for_robot_type(robot_type).id


def ensure_lowercase_name(name: str) -> str:
Expand Down
10 changes: 9 additions & 1 deletion api/src/opentrons/protocol_engine/protocol_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from opentrons.hardware_control.modules import AbstractModule as HardwareModuleAPI
from opentrons.hardware_control.types import PauseType as HardwarePauseType

from . import commands
from . import commands, slot_standardization
from .resources import ModelUtils, ModuleDataProvider
from .types import (
LabwareOffset,
Expand Down Expand Up @@ -148,6 +148,10 @@ def add_command(self, request: commands.CommandCreate) -> commands.Command:
RunStoppedError: the run has been stopped, so no new commands
may be added.
"""
request = slot_standardization.standardize_command(
request, self.state_view.config.robot_type
)

command_id = self._model_utils.generate_id()
request_hash = commands.hash_command_params(
create=request,
Expand Down Expand Up @@ -281,6 +285,10 @@ def add_labware_offset(self, request: LabwareOffsetCreate) -> LabwareOffset:

To retrieve offsets later, see `.state_view.labware`.
"""
request = slot_standardization.standardize_labware_offset(
request, self.state_view.config.robot_type
)

labware_offset_id = self._model_utils.generate_id()
created_at = self._model_utils.get_timestamp()
self._action_dispatcher.dispatch(
Expand Down
133 changes: 133 additions & 0 deletions api/src/opentrons/protocol_engine/slot_standardization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Convert deck slots into the preferred style for a robot.

The deck slots on an OT-2 are labeled like "1", "2", ..., and on an OT-3 they're labeled like
"D1," "D2", ....

When Protocol Engine takes a deck slot as input, we generally want it to accept either style
of label. This helps make protocols more portable across robot types.

But, Protocol Engine should then immediately convert it to the "correct" style for the robot that
it's controlling or simulating. This makes it simpler to consume the robot's HTTP API,
and it makes it easier for us to reason about Protocol Engine's internal state.

This module does that conversion, for any Protocol Engine input that contains a reference to a
deck slot.
"""
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved


from typing import Any, Callable, Dict, Type

from opentrons_shared_data.robot.dev_types import RobotType

from . import commands
from .types import (
OFF_DECK_LOCATION,
DeckSlotLocation,
LabwareLocation,
LabwareOffsetCreate,
ModuleLocation,
)


def standardize_labware_offset(
original: LabwareOffsetCreate, robot_type: RobotType
) -> LabwareOffsetCreate:
"""Convert the deck slot in the given `LabwareOffsetCreate` to match the given robot type."""
return original.copy(
update={
"location": original.location.copy(
update={
"slotName": original.location.slotName.to_equivalent_for_robot_type(
robot_type
)
}
)
}
)


def standardize_command(
original: commands.CommandCreate, robot_type: RobotType
) -> commands.CommandCreate:
"""Convert any deck slots in the given `CommandCreate` to match the given robot type."""
try:
standardize = _standardize_command_functions[type(original)]
except KeyError:
return original
else:
return standardize(original, robot_type)


# Command-specific standardization:
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
#
# Our use of .copy(update=...) in these implementations, instead of .construct(...), is a tradeoff.
# .construct() would give us better type-checking for the fields that we set,
# but .copy(update=...) avoids the hazard of forgetting to set fields that have defaults.


def _standardize_load_labware(
original: commands.LoadLabwareCreate, robot_type: RobotType
) -> commands.LoadLabwareCreate:
params = original.params.copy(
update={
"location": _standardize_labware_location(
original.params.location, robot_type
)
}
)
return original.copy(update={"params": params})


def _standardize_load_module(
original: commands.LoadModuleCreate, robot_type: RobotType
) -> commands.LoadModuleCreate:
params = original.params.copy(
update={
"location": _standardize_deck_slot_location(
original.params.location, robot_type
)
}
)
return original.copy(update={"params": params})


def _standardize_move_labware(
original: commands.MoveLabwareCreate, robot_type: RobotType
) -> commands.MoveLabwareCreate:
params = original.params.copy(
update={
"newLocation": _standardize_labware_location(
original.params.newLocation, robot_type
)
}
)
return original.copy(update={"params": params})


_standardize_command_functions: Dict[
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
Type[commands.CommandCreate], Callable[[Any, RobotType], commands.CommandCreate]
] = {
commands.LoadLabwareCreate: _standardize_load_labware,
commands.LoadModuleCreate: _standardize_load_module,
commands.MoveLabwareCreate: _standardize_move_labware,
}


# Helpers:


def _standardize_labware_location(
original: LabwareLocation, robot_type: RobotType
) -> LabwareLocation:
if isinstance(original, DeckSlotLocation):
return _standardize_deck_slot_location(original, robot_type)
elif isinstance(original, ModuleLocation) or original == OFF_DECK_LOCATION:
return original


def _standardize_deck_slot_location(
original: DeckSlotLocation, robot_type: RobotType
) -> DeckSlotLocation:
return original.copy(
update={"slotName": original.slotName.to_equivalent_for_robot_type(robot_type)}
)
12 changes: 9 additions & 3 deletions api/src/opentrons/protocol_engine/state/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
CurrentWell,
TipGeometry,
)
from .config import Config
from .labware import LabwareView
from .modules import ModuleView
from .pipettes import PipetteView
Expand All @@ -34,11 +35,13 @@ class GeometryView:

def __init__(
self,
config: Config,
labware_view: LabwareView,
module_view: ModuleView,
pipette_view: PipetteView,
) -> None:
"""Initialize a GeometryView instance."""
self._config = config
self._labware = labware_view
self._modules = module_view
self._pipettes = pipette_view
Expand Down Expand Up @@ -374,10 +377,13 @@ def get_extra_waypoints(
from_slot=self.get_ancestor_slot_name(location.labware_id),
to_slot=self.get_ancestor_slot_name(labware_id),
):
slot_5_center = self._labware.get_slot_center_position(
slot=DeckSlotName.SLOT_5
middle_slot = DeckSlotName.SLOT_5.to_equivalent_for_robot_type(
self._config.robot_type
)
return [(slot_5_center.x, slot_5_center.y)]
middle_slot_center = self._labware.get_slot_center_position(
slot=middle_slot,
)
return [(middle_slot_center.x, middle_slot_center.y)]
return []

# TODO(mc, 2022-12-09): enforce data integrity (e.g. one module per slot)
Expand Down
38 changes: 24 additions & 14 deletions api/src/opentrons/protocol_engine/state/labware.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,20 @@
"opentrons/usascientific_96_wellplate_2.4ml_deep/1",
}

_INSTRUMENT_ATTACH_SLOT = DeckSlotName.SLOT_1
_OT3_INSTRUMENT_ATTACH_SLOT = DeckSlotName.SLOT_D1

_RIGHT_SIDE_SLOTS = {
# OT-2:
DeckSlotName.FIXED_TRASH,
DeckSlotName.SLOT_9,
DeckSlotName.SLOT_6,
DeckSlotName.SLOT_3,
# OT-3:
DeckSlotName.SLOT_A3,
DeckSlotName.SLOT_B3,
DeckSlotName.SLOT_C3,
DeckSlotName.SLOT_D3,
}


class LabwareLoadParams(NamedTuple):
Expand Down Expand Up @@ -269,11 +282,11 @@ def get_slot_definition(self, slot: DeckSlotName) -> SlotDefV3:
deck_def = self.get_deck_definition()

for slot_def in deck_def["locations"]["orderedSlots"]:
if slot_def["id"] == str(slot):
if slot_def["id"] == slot.id:
return slot_def

raise errors.SlotDoesNotExistError(
f"Slot ID {slot} does not exist in deck {deck_def['otId']}"
f"Slot ID {slot.id} does not exist in deck {deck_def['otId']}"
)

def get_slot_position(self, slot: DeckSlotName) -> Point:
Expand Down Expand Up @@ -408,12 +421,7 @@ def get_edge_path_type(

left_path_criteria = mount is MountType.RIGHT and well_name in left_column
right_path_criteria = mount is MountType.LEFT and well_name in right_column
labware_right_side = labware_slot in [
DeckSlotName.SLOT_3,
DeckSlotName.SLOT_6,
DeckSlotName.SLOT_9,
DeckSlotName.FIXED_TRASH,
]
labware_right_side = labware_slot in _RIGHT_SIDE_SLOTS

if left_path_criteria and (next_to_module or labware_right_side):
return EdgePathType.LEFT
Expand Down Expand Up @@ -580,10 +588,12 @@ def get_fixed_trash_id(self) -> str:
that is currently in use for the protocol run.
"""
for labware in self._state.labware_by_id.values():
if (
isinstance(labware.location, DeckSlotLocation)
and labware.location.slotName == DeckSlotName.FIXED_TRASH
):
if isinstance(
labware.location, DeckSlotLocation
) and labware.location.slotName in {
DeckSlotName.FIXED_TRASH,
DeckSlotName.SLOT_A3,
}:
return labware.id

raise errors.LabwareNotLoadedError(
Expand All @@ -606,7 +616,7 @@ def raise_if_labware_in_location(

def get_calibration_coordinates(self, offset: Point) -> Point:
"""Get calibration critical point and target position."""
target_center = self.get_slot_center_position(_INSTRUMENT_ATTACH_SLOT)
target_center = self.get_slot_center_position(_OT3_INSTRUMENT_ATTACH_SLOT)
# TODO (tz, 11-30-22): These coordinates wont work for OT-2. We will need to apply offsets after
# https://opentrons.atlassian.net/browse/RCORE-382

Expand Down
Loading