Skip to content

Commit

Permalink
refactor(api): Allow adding/removing tips on HW API without await (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxColoring authored Oct 18, 2024
1 parent b157bb4 commit 7b023dc
Show file tree
Hide file tree
Showing 22 changed files with 61 additions and 74 deletions.
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ async def drop_tip(self, mount: top_types.Mount, home_after: bool = True) -> Non
instrument.set_current_volume(0)

self.set_current_tiprack_diameter(mount, 0.0)
await self.remove_tip(mount)
self.remove_tip(mount)

async def create_simulating_module(
self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ async def reset_nozzle_configuration(self, mount: MountType) -> None:
if instr:
instr.reset_nozzle_configuration()

async def add_tip(self, mount: MountType, tip_length: float) -> None:
def add_tip(self, mount: MountType, tip_length: float) -> None:
instr = self._attached_instruments[mount]
attached = self.attached_instruments
instr_dict = attached[mount]
Expand All @@ -430,7 +430,7 @@ async def add_tip(self, mount: MountType, tip_length: float) -> None:
f"attach tip called while tip already attached to {instr}"
)

async def remove_tip(self, mount: MountType) -> None:
def remove_tip(self, mount: MountType) -> None:
instr = self._attached_instruments[mount]
attached = self.attached_instruments
instr_dict = attached[mount]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ async def reset_nozzle_configuration(self, mount: OT3Mount) -> None:
if instr:
instr.reset_nozzle_configuration()

async def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
instr = self._attached_instruments[mount]
attached = self.attached_instruments
instr_dict = attached[mount]
Expand All @@ -440,7 +440,7 @@ async def add_tip(self, mount: OT3Mount, tip_length: float) -> None:
"attach tip called while tip already attached to {instr}"
)

async def remove_tip(self, mount: OT3Mount) -> None:
def remove_tip(self, mount: OT3Mount) -> None:
instr = self._attached_instruments[mount]
attached = self.attached_instruments
instr_dict = attached[mount]
Expand Down
12 changes: 6 additions & 6 deletions api/src/opentrons/hardware_control/ot3_calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,13 +819,13 @@ async def find_pipette_offset(
try:
if reset_instrument_offset:
await hcapi.reset_instrument_offset(mount)
await hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
offset = await _calibrate_mount(
hcapi, mount, slot, method, raise_verify_error, probe=probe
)
return offset
finally:
await hcapi.remove_tip(mount)
hcapi.remove_tip(mount)


async def calibrate_pipette(
Expand Down Expand Up @@ -877,7 +877,7 @@ async def calibrate_module(
if mount == OT3Mount.GRIPPER:
hcapi.add_gripper_probe(GripperProbe.FRONT)
else:
await hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
hcapi.add_tip(mount, hcapi.config.calibration.probe_length)

LOG.info(
f"Starting module calibration for {module_id} at {nominal_position} using {mount}"
Expand All @@ -903,7 +903,7 @@ async def calibrate_module(
hcapi.remove_gripper_probe()
await hcapi.ungrip()
else:
await hcapi.remove_tip(mount)
hcapi.remove_tip(mount)


async def calibrate_belts(
Expand All @@ -927,15 +927,15 @@ async def calibrate_belts(
raise RuntimeError("Must use pipette mount, not gripper")
try:
hcapi.reset_deck_calibration()
await hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
hcapi.add_tip(mount, hcapi.config.calibration.probe_length)
belt_attitude, alignment_details = await _determine_transform_matrix(
hcapi, mount
)
save_robot_belt_attitude(belt_attitude, pipette_id)
return belt_attitude, alignment_details
finally:
hcapi.load_deck_calibration()
await hcapi.remove_tip(mount)
hcapi.remove_tip(mount)


def apply_machine_transform(
Expand Down
10 changes: 5 additions & 5 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2338,7 +2338,7 @@ async def drop_tip(
instrument.set_current_volume(0)

self.set_current_tiprack_diameter(mount, 0.0)
await self.remove_tip(mount)
self.remove_tip(mount)

async def clean_up(self) -> None:
"""Get the API ready to stop cleanly."""
Expand Down Expand Up @@ -2607,13 +2607,13 @@ async def update_nozzle_configuration_for_mount(
starting_nozzle,
)

async def add_tip(
def add_tip(
self, mount: Union[top_types.Mount, OT3Mount], tip_length: float
) -> None:
await self._pipette_handler.add_tip(OT3Mount.from_mount(mount), tip_length)
self._pipette_handler.add_tip(OT3Mount.from_mount(mount), tip_length)

async def remove_tip(self, mount: Union[top_types.Mount, OT3Mount]) -> None:
await self._pipette_handler.remove_tip(OT3Mount.from_mount(mount))
def remove_tip(self, mount: Union[top_types.Mount, OT3Mount]) -> None:
self._pipette_handler.remove_tip(OT3Mount.from_mount(mount))

def add_gripper_probe(self, probe: GripperProbe) -> None:
self._gripper_handler.add_probe(probe)
Expand Down
12 changes: 1 addition & 11 deletions api/src/opentrons/hardware_control/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,6 @@ class HardwareControlInterface(
def get_robot_type(self) -> Type[OT2RobotType]:
return OT2RobotType

# todo(mm, 2024-10-17): This probably belongs in InstrumentConfigurer, alongside
# add_tip() and remove_tip().
def cache_tip(self, mount: MountArgType, tip_length: float) -> None:
...


class FlexHardwareControlInterface(
PositionEstimator,
Expand All @@ -89,14 +84,9 @@ class FlexHardwareControlInterface(
def get_robot_type(self) -> Type[FlexRobotType]:
return FlexRobotType

# todo(mm, 2024-10-17): This probably belongs in InstrumentConfigurer, alongside
# add_tip() and remove_tip().
def cache_tip(self, mount: MountArgType, tip_length: float) -> None:
...


__all__ = [
"HardwareControlAPI",
"HardwareControlInterface",
"FlexHardwareControlInterface",
"Simulatable",
"Stoppable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,19 +142,20 @@ def get_instrument_max_height(
"""
...

# todo(mm, 2024-10-17): Can this be made non-async?
# todo(mm, 2024-10-17): Consider deleting this in favor of cache_tip(), which is
# the same except for `assert`s, if we can do so without breaking anything.
async def add_tip(self, mount: MountArgType, tip_length: float) -> None:
def add_tip(self, mount: MountArgType, tip_length: float) -> None:
"""Inform the hardware that a tip is now attached to a pipette.
This changes the critical point of the pipette to make sure that
the end of the tip is what moves around, and allows liquid handling.
"""
...

# todo(mm, 2024-10-17): Can this be made non-async?
async def remove_tip(self, mount: MountArgType) -> None:
def cache_tip(self, mount: MountArgType, tip_length: float) -> None:
...

def remove_tip(self, mount: MountArgType) -> None:
"""Inform the hardware that a tip is no longer attached to a pipette.
This changes the critical point of the system to the end of the
Expand Down
4 changes: 2 additions & 2 deletions api/src/opentrons/protocol_engine/execution/tip_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,14 @@ async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None:
# Allow TipNotAttachedError to propagate.
await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT)

await self._hardware_api.remove_tip(hw_mount)
self._hardware_api.remove_tip(hw_mount)
self._hardware_api.set_current_tiprack_diameter(hw_mount, 0)

async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None:
"""See documentation on abstract base class."""
hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount()

await self._hardware_api.add_tip(mount=hw_mount, tip_length=tip.length)
self._hardware_api.add_tip(mount=hw_mount, tip_length=tip.length)

self._hardware_api.set_current_tiprack_diameter(
mount=hw_mount,
Expand Down
6 changes: 3 additions & 3 deletions api/tests/opentrons/hardware_control/test_moves.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ async def test_shake_during_drop(
},
}
await hardware_api.cache_instruments()
await hardware_api.add_tip(types.Mount.RIGHT, 50.0)
hardware_api.add_tip(types.Mount.RIGHT, 50.0)
hardware_api.set_current_tiprack_diameter(types.Mount.RIGHT, 2.0 * 4)

shake_tips_drop = mock.Mock(side_effect=hardware_api._shake_off_tips_drop)
Expand All @@ -515,7 +515,7 @@ async def test_shake_during_drop(

# Test drop tip shake with 25% of tiprack well diameter
# over upper (2.25 mm) limit
await hardware_api.add_tip(types.Mount.RIGHT, 20)
hardware_api.add_tip(types.Mount.RIGHT, 20)
hardware_api.set_current_tiprack_diameter(types.Mount.RIGHT, 2.3 * 4)
shake_tips_drop.reset_mock()
move_rel.reset_move()
Expand All @@ -530,7 +530,7 @@ async def test_shake_during_drop(

# Test drop tip shake with 25% of tiprack well diameter
# below lower (1.0 mm) limit
await hardware_api.add_tip(types.Mount.RIGHT, 50)
hardware_api.add_tip(types.Mount.RIGHT, 50)
hardware_api.set_current_tiprack_diameter(types.Mount.RIGHT, 0.9 * 4)
shake_tips_drop.reset_mock()
move_rel.reset_mock()
Expand Down
16 changes: 8 additions & 8 deletions api/tests/opentrons/hardware_control/test_ot3_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ async def test_liquid_probe(
pipette = ot3_hardware.hardware_pipettes[mount.to_mount()]

assert pipette
await ot3_hardware.add_tip(mount, 100)
ot3_hardware.add_tip(mount, 100)
await ot3_hardware.home()
mock_move_to.return_value = None

Expand Down Expand Up @@ -905,7 +905,7 @@ async def test_liquid_probe_plunger_moves(
pipette = ot3_hardware.hardware_pipettes[mount.to_mount()]

assert pipette
await ot3_hardware.add_tip(mount, 100)
ot3_hardware.add_tip(mount, 100)
await ot3_hardware.home()
mock_move_to.return_value = None

Expand Down Expand Up @@ -1012,7 +1012,7 @@ async def test_liquid_probe_mount_moves(
pipette = ot3_hardware.hardware_pipettes[mount.to_mount()]

assert pipette
await ot3_hardware.add_tip(mount, 100)
ot3_hardware.add_tip(mount, 100)
await ot3_hardware.home()
mock_move_to.return_value = None

Expand Down Expand Up @@ -1073,7 +1073,7 @@ async def test_multi_liquid_probe(
await ot3_hardware.cache_pipette(OT3Mount.LEFT, instr_data, None)
pipette = ot3_hardware.hardware_pipettes[OT3Mount.LEFT.to_mount()]
assert pipette
await ot3_hardware.add_tip(OT3Mount.LEFT, 100)
ot3_hardware.add_tip(OT3Mount.LEFT, 100)
await ot3_hardware.home()
mock_move_to.return_value = None

Expand Down Expand Up @@ -1142,7 +1142,7 @@ async def test_liquid_not_found(
await ot3_hardware.cache_pipette(OT3Mount.LEFT, instr_data, None)
pipette = ot3_hardware.hardware_pipettes[OT3Mount.LEFT.to_mount()]
assert pipette
await ot3_hardware.add_tip(OT3Mount.LEFT, 100)
ot3_hardware.add_tip(OT3Mount.LEFT, 100)
await ot3_hardware.home()
await ot3_hardware.move_to(OT3Mount.LEFT, Point(10, 10, 10))

Expand Down Expand Up @@ -1633,7 +1633,7 @@ async def test_prepare_for_aspirate(
await ot3_hardware.cache_pipette(mount, instr_data, None)
assert ot3_hardware.hardware_pipettes[mount.to_mount()]

await ot3_hardware.add_tip(mount, 100)
ot3_hardware.add_tip(mount, 100)
await ot3_hardware.prepare_for_aspirate(OT3Mount.LEFT)
mock_move_to_plunger_bottom.assert_called_once_with(OT3Mount.LEFT, 1.0)

Expand Down Expand Up @@ -1668,7 +1668,7 @@ async def test_plunger_ready_to_aspirate_after_dispense(
await ot3_hardware.cache_pipette(mount, instr_data, None)
assert ot3_hardware.hardware_pipettes[mount.to_mount()]

await ot3_hardware.add_tip(mount, 100)
ot3_hardware.add_tip(mount, 100)
await ot3_hardware.prepare_for_aspirate(OT3Mount.LEFT)
assert ot3_hardware.hardware_pipettes[mount.to_mount()].ready_to_aspirate

Expand Down Expand Up @@ -1729,7 +1729,7 @@ async def test_move_to_plunger_bottom(

# tip attached, moving DOWN towards "bottom" position
await ot3_hardware.home()
await ot3_hardware.add_tip(mount, 100)
ot3_hardware.add_tip(mount, 100)
mock_move.reset_mock()
await ot3_hardware.prepare_for_aspirate(mount)
# make sure we've done the backlash compensation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ async def test_drop_tip(
decoy.verify(
await mock_hardware_api.tip_drop_moves(mount=Mount.RIGHT, home_after=True)
)
decoy.verify(await mock_hardware_api.remove_tip(mount=Mount.RIGHT))
decoy.verify(mock_hardware_api.remove_tip(mount=Mount.RIGHT))
decoy.verify(
mock_hardware_api.set_current_tiprack_diameter(
mount=Mount.RIGHT, tiprack_diameter=0
Expand Down Expand Up @@ -292,7 +292,7 @@ async def test_add_tip(
await subject.add_tip(pipette_id="pipette-id", tip=tip)

decoy.verify(
await mock_hardware_api.add_tip(mount=Mount.LEFT, tip_length=50),
mock_hardware_api.add_tip(mount=Mount.LEFT, tip_length=50),
mock_hardware_api.set_current_tiprack_diameter(
mount=Mount.LEFT,
tiprack_diameter=5,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ async def _main(is_simulating: bool, cycles: int, stable: bool) -> None:
raise RuntimeError("No pipette attached")

# add length to the pipette, to account for the attached probe
await api.add_tip(mount, PROBE_LENGTH)
api.add_tip(mount, PROBE_LENGTH)

await helpers_ot3.home_ot3(api)
for c in range(cycles):
Expand All @@ -154,7 +154,7 @@ async def _main(is_simulating: bool, cycles: int, stable: bool) -> None:
z_ax = types.Axis.by_mount(mount)
top_z = helpers_ot3.get_endstop_position_ot3(api, mount)[z_ax]
await api.move_to(mount, ASSUMED_XY_LOCATION._replace(z=top_z))
await api.remove_tip(mount)
api.remove_tip(mount)
await api.disengage_axes([types.Axis.X, types.Axis.Y])


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async def _main(is_simulating: bool, cycles: int, stable: bool) -> None:
raise RuntimeError("No pipette attached")

# add length to the pipette, to account for the attached probe
await api.add_tip(mount, PROBE_LENGTH)
api.add_tip(mount, PROBE_LENGTH)

await helpers_ot3.home_ot3(api)
for c in range(cycles):
Expand All @@ -96,7 +96,7 @@ async def _main(is_simulating: bool, cycles: int, stable: bool) -> None:
z_ax = types.Axis.by_mount(mount)
top_z = helpers_ot3.get_endstop_position_ot3(api, mount)[z_ax]
await api.move_to(mount, ASSUMED_XY_LOCATION._replace(z=top_z))
await api.remove_tip(mount)
api.remove_tip(mount)
await api.disengage_axes([types.Axis.X, types.Axis.Y])


Expand Down
4 changes: 2 additions & 2 deletions hardware-testing/hardware_testing/examples/plunger_ot3.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ async def _main(is_simulating: bool) -> None:
await api.home_plunger(mount)

# move the plunger based on volume (aspirate/dispense)
await api.add_tip(mount, tip_length=10)
api.add_tip(mount, tip_length=10)
await api.prepare_for_aspirate(mount)
max_vol = pipette.working_volume
for vol in [max_vol, max_vol / 2, max_vol / 10]:
await api.aspirate(mount, volume=vol)
await api.dispense(mount, volume=vol)
await api.prepare_for_aspirate(mount)
await api.remove_tip(mount)
api.remove_tip(mount)

# move the plunger based on position (millimeters)
plunger_poses = helpers_ot3.get_plunger_positions_ot3(api, mount)
Expand Down
8 changes: 2 additions & 6 deletions hardware-testing/hardware_testing/gravimetric/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@
import opentrons.protocol_engine.execution.pipetting as PE_pipetting
from opentrons.protocol_engine.notes import CommandNoteAdder

from opentrons.protocol_engine import (
StateView,
WellLocation,
DropTipWellLocation,
)
from opentrons.protocol_engine import StateView
from opentrons.protocol_api.core.engine import pipette_movement_conflict


Expand Down Expand Up @@ -267,7 +263,7 @@ def _override_check_safe_for_pipette_movement(
pipette_id: str,
labware_id: str,
well_name: str,
well_location: Union[WellLocation, DropTipWellLocation],
well_location: object,
) -> None:
pass

Expand Down
Loading

0 comments on commit 7b023dc

Please sign in to comment.