Skip to content

Commit

Permalink
fix(api,shared-data): various FlexStacker fixes and adjustments
Browse files Browse the repository at this point in the history
- increased flex stacker z current from 1.5mA to 1.8mA to account for full tipracks
- increased the offset used when moving the z axis for storing tall labware like tipracks
- added await get_motion_params call when initializing the move_params in the FlexStacker module.
- added lid height if lid is used in the retrieve.py and store.py commands.
- exclude lids on top of tipracks in the Flex Stacker when getting the highest z.
- add flexStackerModuleV1D4 addressable area to the flex stacker + wate chute fixture
- adjust the stackingOffsetWithLabware z from 8.193 to 14 for `opentrons_flex_tiprack_lid`
- decrease the `gripHeightFromLabwareTop from 8 to 4 for `opentrons_flex_tiprack_lid``
  • Loading branch information
vegano1 committed Jan 27, 2025
1 parent 35686bc commit a44a108
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 27 deletions.
4 changes: 2 additions & 2 deletions api/src/opentrons/drivers/flex_stacker/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@
max_speed=10.0,
acceleration=100.0,
max_speed_discont=40,
current=1.5,
current=1.8,
),
"move": MoveParams(
StackerAxis.Z,
max_speed=200.0,
acceleration=500.0,
max_speed_discont=40,
current=1.5,
current=1.8,
),
},
StackerAxis.L: {
Expand Down
5 changes: 3 additions & 2 deletions api/src/opentrons/hardware_control/modules/flex_stacker.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ async def store_labware(self, labware_height: float) -> bool:
await self._prepare_for_action()

# Move X then Z axis
distance = MAX_TRAVEL[StackerAxis.Z] - (labware_height / 2) - OFFSET_MD
offset = OFFSET_MD if labware_height < 20 else OFFSET_LG * 2
distance = MAX_TRAVEL[StackerAxis.Z] - (labware_height / 2) - offset
await self._move_and_home_axis(StackerAxis.X, Direction.RETRACT, OFFSET_SM)
await self.move_axis(StackerAxis.Z, Direction.EXTENT, distance)

Expand Down Expand Up @@ -410,7 +411,7 @@ async def get_limit_switch_status(self) -> None:
async def get_motion_parameters(self) -> None:
"""Get the motion parameters used by the axis motors."""
self.move_params = {
axis: self._driver.get_motion_params(axis) for axis in StackerAxis
axis: await self._driver.get_motion_params(axis) for axis in StackerAxis
}

async def get_platform_sensor_state(self) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,16 @@ async def execute(self, params: RetrieveParams) -> SuccessData[RetrieveResult]:
# Get the labware dimensions for the labware being retrieved,
# which is the first one in the hopper labware id list
lw_id = stacker_state.hopper_labware_ids[0]
lw_dim = self._state_view.labware.get_dimensions(labware_id=lw_id)

if stacker_hw is not None:
# Dispense the labware from the Flex Stacker using the labware height
await stacker_hw.dispense_labware(labware_height=lw_dim.z)
labware = self._state_view.labware.get(lw_id)
labware_height = self._state_view.labware.get_dimensions(labware_id=lw_id).z
if labware.lid_id is not None:
lid_def = self._state_view.labware.get_definition(labware.lid_id)
offset = self._state_view.labware.get_labware_overlap_offsets(
lid_def, labware.loadName
).z
labware_height = labware_height + lid_def.dimensions.zDimension - offset
await stacker_hw.dispense_labware(labware_height=labware_height)

# update the state to reflect the labware is now in the flex stacker slot
state_update.set_labware_location(
Expand Down
11 changes: 9 additions & 2 deletions api/src/opentrons/protocol_engine/commands/flex_stacker/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,19 @@ async def execute(self, params: StoreParams) -> SuccessData[StoreResult]:
"Cannot store labware if Flex Stacker carriage is empty"
)

lw_dim = self._state_view.labware.get_dimensions(labware_id=lw_id)
# TODO: check the type of the labware should match that already in the stack
state_update = update_types.StateUpdate()

if stacker_hw is not None:
await stacker_hw.store_labware(labware_height=lw_dim.z)
labware = self._state_view.labware.get(lw_id)
labware_height = self._state_view.labware.get_dimensions(labware_id=lw_id).z
if labware.lid_id is not None:
lid_def = self._state_view.labware.get_definition(labware.lid_id)
offset = self._state_view.labware.get_labware_overlap_offsets(
lid_def, labware.loadName
).z
labware_height = labware_height + lid_def.dimensions.zDimension - offset
await stacker_hw.store_labware(labware_height=labware_height)

# update the state to reflect the labware is store in the stack
state_update.set_labware_location(
Expand Down
1 change: 1 addition & 0 deletions api/src/opentrons/protocol_engine/state/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def get_all_obstacle_highest_z(self) -> float:
self._get_highest_z_from_labware_data(lw_data)
for lw_data in self._labware.get_all()
if lw_data.location != OFF_DECK_LOCATION
and not self._labware.get_labware_by_lid_id(lw_data.id)
),
default=0.0,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Test Flex Stacker retrieve command implementation."""
from decoy import Decoy
import pytest
from decoy import Decoy
from contextlib import nullcontext as does_not_raise
from typing import ContextManager, Any

from opentrons.calibration_storage.helpers import uri_from_details
from opentrons.hardware_control.modules import FlexStacker

from opentrons.protocol_engine.state.state import StateView
Expand All @@ -21,8 +22,17 @@
from opentrons.protocol_engine.commands import flex_stacker
from opentrons.protocol_engine.commands.command import SuccessData
from opentrons.protocol_engine.commands.flex_stacker.retrieve import RetrieveImpl
from opentrons.protocol_engine.types import Dimensions, ModuleLocation
from opentrons.protocol_engine.types import (
DeckSlotLocation,
Dimensions,
ModuleLocation,
LoadedLabware,
OverlapOffset,
)
from opentrons.protocol_engine.errors import CannotPerformModuleAction
from opentrons.types import DeckSlotName

from opentrons_shared_data.labware.labware_definition import LabwareDefinition


@pytest.mark.parametrize(
Expand All @@ -44,6 +54,7 @@ async def test_retrieve(
equipment: EquipmentHandler,
in_static_mode: bool,
expectation: ContextManager[Any],
tiprack_lid_def: LabwareDefinition,
) -> None:
"""It should be able to retrieve a labware."""
subject = RetrieveImpl(state_view=state_view, equipment=equipment)
Expand All @@ -59,11 +70,28 @@ async def test_retrieve(
decoy.when(
state_view.modules.get_flex_stacker_substate(module_id="flex-stacker-id")
).then_return(fs_module_substate)
decoy.when(state_view.labware.get("labware-id")).then_return(
LoadedLabware(
id="labware-id",
loadName="tiprack",
definitionUri=uri_from_details(namespace="a", load_name="b", version=1),
location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3),
offsetId=None,
lid_id="lid-id",
displayName="Labware",
)
)

decoy.when(state_view.labware.get_dimensions(labware_id="labware-id")).then_return(
Dimensions(x=1, y=1, z=1)
)

decoy.when(state_view.labware.get_definition("lid-id")).then_return(tiprack_lid_def)

decoy.when(
state_view.labware.get_labware_overlap_offsets(tiprack_lid_def, "tiprack")
).then_return(OverlapOffset(x=0, y=0, z=14))

decoy.when(
equipment.get_module_hardware_api(FlexStackerId("flex-stacker-id"))
).then_return(fs_hardware)
Expand All @@ -72,7 +100,7 @@ async def test_retrieve(
result = await subject.execute(data)

if not in_static_mode:
decoy.verify(await fs_hardware.dispense_labware(labware_height=1), times=1)
decoy.verify(await fs_hardware.dispense_labware(labware_height=4), times=1)

assert result == SuccessData(
public=flex_stacker.RetrieveResult(labware_id="labware-id"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Test Flex Stacker store command implementation."""
from decoy import Decoy
import pytest
from decoy import Decoy
from contextlib import nullcontext as does_not_raise
from typing import ContextManager, Any

from opentrons.calibration_storage.helpers import uri_from_details
from opentrons.hardware_control.modules import FlexStacker

from opentrons.protocol_engine.state.update_types import (
Expand All @@ -22,8 +23,17 @@
from opentrons.protocol_engine.commands import flex_stacker
from opentrons.protocol_engine.commands.command import SuccessData
from opentrons.protocol_engine.commands.flex_stacker.store import StoreImpl
from opentrons.protocol_engine.types import Dimensions, OFF_DECK_LOCATION
from opentrons.protocol_engine.types import (
DeckSlotLocation,
Dimensions,
OFF_DECK_LOCATION,
LoadedLabware,
OverlapOffset,
)
from opentrons.protocol_engine.errors import CannotPerformModuleAction
from opentrons.types import DeckSlotName

from opentrons_shared_data.labware.labware_definition import LabwareDefinition


@pytest.mark.parametrize(
Expand All @@ -45,6 +55,7 @@ async def test_store(
equipment: EquipmentHandler,
in_static_mode: bool,
expectation: ContextManager[Any],
tiprack_lid_def: LabwareDefinition,
) -> None:
"""It should be able to store a labware."""
subject = StoreImpl(state_view=state_view, equipment=equipment)
Expand All @@ -64,11 +75,28 @@ async def test_store(
decoy.when(
state_view.labware.get_id_by_module(module_id="flex-stacker-id")
).then_return("labware-id")
decoy.when(state_view.labware.get("labware-id")).then_return(
LoadedLabware(
id="labware-id",
loadName="tiprack",
definitionUri=uri_from_details(namespace="a", load_name="b", version=1),
location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3),
offsetId=None,
lid_id="lid-id",
displayName="Labware",
)
)

decoy.when(state_view.labware.get_dimensions(labware_id="labware-id")).then_return(
Dimensions(x=1, y=1, z=1)
)

decoy.when(state_view.labware.get_definition("lid-id")).then_return(tiprack_lid_def)

decoy.when(
state_view.labware.get_labware_overlap_offsets(tiprack_lid_def, "tiprack")
).then_return(OverlapOffset(x=0, y=0, z=14))

decoy.when(
equipment.get_module_hardware_api(FlexStackerId("flex-stacker-id"))
).then_return(fs_hardware)
Expand All @@ -77,7 +105,7 @@ async def test_store(
result = await subject.execute(data)

if not in_static_mode:
decoy.verify(await fs_hardware.store_labware(labware_height=1), times=1)
decoy.verify(await fs_hardware.store_labware(labware_height=4), times=1)
assert result == SuccessData(
public=flex_stacker.StoreResult(),
state_update=StateUpdate(
Expand Down
8 changes: 8 additions & 0 deletions api/tests/opentrons/protocol_engine/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,14 @@ def magdeck_well_plate_def() -> LabwareDefinition:
)


@pytest.fixture(scope="session")
def tiprack_lid_def() -> LabwareDefinition:
"""Get the definition of the opentrons tiprack lid."""
return LabwareDefinition.model_validate(
load_definition("opentrons_flex_tiprack_lid", 1, schema=3)
)


@pytest.fixture(scope="session")
def tempdeck_v1_def() -> ModuleDefinition:
"""Get the definition of a V1 tempdeck."""
Expand Down
2 changes: 1 addition & 1 deletion shared-data/deck/definitions/5/ot3_standard.json
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,7 @@
"mayMountTo": ["cutoutD3"],
"displayName": "Flex Stacker With Waste Chute Adapter for 96 Channel Pipette or Gripper",
"providesAddressableAreas": {
"cutoutD3": ["1ChannelWasteChute", "8ChannelWasteChute", "D4"]
"cutoutD3": ["1ChannelWasteChute", "8ChannelWasteChute", "flexStackerModuleV1D4"]
},
"fixtureGroup": {},
"height": 124.5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,37 +43,37 @@
"default": {
"x": 0,
"y": 0,
"z": 8.193
"z": 14
},
"opentrons_flex_96_filtertiprack_200ul": {
"x": 0,
"y": 0,
"z": 8.25
"z": 14
},
"opentrons_flex_96_filtertiprack_1000ul": {
"x": 0,
"y": 0,
"z": 8.25
"z": 14
},
"opentrons_flex_96_tiprack_20ul": {
"x": 0,
"y": 0,
"z": 8.25
"z": 14
},
"opentrons_flex_96_tiprack_50ul": {
"x": 0,
"y": 0,
"z": 8.25
"z": 14
},
"opentrons_flex_96_tiprack_200ul": {
"x": 0,
"y": 0,
"z": 8.25
"z": 14
},
"opentrons_flex_96_tiprack_1000ul": {
"x": 0,
"y": 0,
"z": 8.25
"z": 14
}
},
"stackLimit": 1,
Expand All @@ -86,7 +86,7 @@
"opentrons_flex_96_filertiprack_1000ul"
],
"gripForce": 15,
"gripHeightFromLabwareBottom": 8,
"gripHeightFromLabwareBottom": 4,
"gripperOffsets": {
"default": {
"pickUpOffset": {
Expand Down
4 changes: 2 additions & 2 deletions shared-data/python/opentrons_shared_data/labware/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
Schema = NewType("Schema", Dict[str, Any])


def load_definition(loadname: str, version: int) -> "LabwareDefinition":
def load_definition(loadname: str, version: int, schema: int = 2) -> "LabwareDefinition":
return json.loads(
load_shared_data(f"labware/definitions/2/{loadname}/{version}.json")
load_shared_data(f"labware/definitions/{schema}/{loadname}/{version}.json")
)


Expand Down

0 comments on commit a44a108

Please sign in to comment.