diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index a655cc372850..0ac6bd994b15 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -778,6 +778,7 @@ def air_gap( target = loc.labware.as_well().top(height) self.move_to(target, publish=False) if self.api_version >= _AIR_GAP_TRACKING_ADDED_IN: + self._core.prepare_to_aspirate() c_vol = self._core.get_available_volume() if volume is None else volume flow_rate = self._core.get_aspirate_flow_rate() self._core.air_gap_in_place(c_vol, flow_rate) diff --git a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py index bf6b23d5d316..46be3e0c6d2a 100644 --- a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py @@ -5,7 +5,11 @@ from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from .pipetting_common import OverpressureError, PipetteIdMixin, prepare_for_aspirate +from .pipetting_common import ( + OverpressureError, + PipetteIdMixin, + prepare_for_aspirate, +) from .command import ( AbstractCommandImpl, BaseCommand, @@ -66,6 +70,14 @@ def _transform_result( async def execute(self, params: PrepareToAspirateParams) -> _ExecuteReturn: """Prepare the pipette to aspirate.""" + ready_to_aspirate = self._pipetting_handler.get_is_ready_to_aspirate( + pipette_id=params.pipetteId + ) + if ready_to_aspirate: + return SuccessData( + public=PrepareToAspirateResult(), + ) + current_position = await self._gantry_mover.get_position(params.pipetteId) prepare_result = await prepare_for_aspirate( pipette_id=params.pipetteId, @@ -79,6 +91,7 @@ async def execute(self, params: PrepareToAspirateParams) -> _ExecuteReturn: ) }, ) + if isinstance(prepare_result, DefinedErrorData): return prepare_result else: diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index c9c82046113a..1b8234c4eb21 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -1786,6 +1786,7 @@ def test_air_gap_uses_air_gap( subject.air_gap(volume=10, height=5) decoy.verify(mock_move_to(top_location, publish=False)) + decoy.verify(mock_instrument_core.prepare_to_aspirate()) decoy.verify(mock_instrument_core.air_gap_in_place(10, 11)) diff --git a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py index a113d2670fa8..70edc48b6507 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py @@ -1,4 +1,5 @@ """Test prepare to aspirate commands.""" + from datetime import datetime from opentrons.types import Point import pytest @@ -42,7 +43,9 @@ async def test_prepare_to_aspirate_implementation( """A PrepareToAspirate command should have an executing implementation.""" data = PrepareToAspirateParams(pipetteId="some id") position = Point(x=1, y=2, z=3) - + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="some id")).then_return( + False + ) decoy.when(await pipetting.prepare_for_aspirate(pipette_id="some id")).then_return( None ) @@ -81,6 +84,9 @@ async def test_overpressure_error( data = PrepareToAspirateParams( pipetteId=pipette_id, ) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="pipette-id")).then_return( + False + ) decoy.when( await pipetting.prepare_for_aspirate( @@ -107,3 +113,26 @@ async def test_overpressure_error( ) ), ) + + +async def test_prepare_noops_if_prepared( + decoy: Decoy, + gantry_mover: GantryMover, + pipetting: PipettingHandler, + subject: PrepareToAspirateImplementation, + model_utils: ModelUtils, +) -> None: + """It should do nothing if the pipette does not need to be prepared.""" + data = PrepareToAspirateParams(pipetteId="some id") + position = Point(x=1, y=2, z=3) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="some id")).then_return( + True + ) + decoy.when(await gantry_mover.get_position("some id")).then_return(position) + + result = await subject.execute(data) + decoy.verify(await pipetting.prepare_for_aspirate(pipette_id="some id"), times=0) + assert result == SuccessData( + public=PrepareToAspirateResult(), + state_update=update_types.StateUpdate(), + )