From f8c53e824f94315fa408a6a571d7587fb565b946 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Wed, 1 Nov 2023 10:36:20 -0400 Subject: [PATCH] feat(api): Add prepare to aspirate to PAPI and engine (#13827) Add the ability to explicitly call prepare_to_aspirate() to the python protocol API and the json protocol command schema. We make sure the pipette is prepared to aspirate before any aspiration, which is good, but the way we make this absolute is that if the pipette isn't ready for aspirate when you call aspirate(), we move it to the top of the current well (if necessary) to move the plunger up. This is a problem for users who are, for instance, building explicit prewetting behavior out of protocol API calls. It's common to move the pipette into the liquid and then delay to let the liquid settle before pipetting; if the pipette then moves up to prepare for aspirate before coming back down, that settling is undone. By adding the ability to explicitly prepare_for_aspirate(), the user can call it while they know the pipette is above the well. --------- Co-authored-by: Edward Cormany Co-authored-by: Max Marrone --- .../protocol_api/core/engine/instrument.py | 3 + .../opentrons/protocol_api/core/instrument.py | 4 + .../core/legacy/legacy_instrument_core.py | 4 +- .../legacy_instrument_core.py | 4 +- .../protocol_api/instrument_context.py | 44 +++++++++++ .../protocol_engine/clients/sync_client.py | 8 ++ .../protocol_engine/commands/__init__.py | 14 ++++ .../commands/command_unions.py | 13 ++++ .../commands/prepare_to_aspirate.py | 73 +++++++++++++++++++ .../protocol_engine/state/pipettes.py | 5 ++ .../protocol_api/test_instrument_context.py | 22 ++++++ .../core/simulator/test_instrument_context.py | 10 +-- .../commands/test_prepare_to_aspirate.py | 29 ++++++++ .../protocol_engine/state/command_fixtures.py | 14 ++++ .../state/test_pipette_store.py | 38 ++++++++++ shared-data/command/schemas/8.json | 46 ++++++++++++ shared-data/command/types/pipetting.ts | 19 ++++- 17 files changed, 340 insertions(+), 10 deletions(-) create mode 100644 api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py create mode 100644 api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 8e2441234af..79773c98264 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -584,3 +584,6 @@ def configure_for_volume(self, volume: float) -> None: self._engine_client.configure_for_volume( pipette_id=self._pipette_id, volume=volume ) + + def prepare_to_aspirate(self) -> None: + self._engine_client.prepare_to_aspirate(pipette_id=self._pipette_id) diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 6a1ddd8eeb6..bcec7f9c0f6 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -243,5 +243,9 @@ def configure_for_volume(self, volume: float) -> None: """ ... + def prepare_to_aspirate(self) -> None: + """Prepare the pipette to aspirate.""" + ... + InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any]) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index c63904a3158..b9e661a9fce 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -102,7 +102,7 @@ def aspirate( "cause over aspiration if the previous command is a " "blow_out." ) - self.prepare_for_aspirate() + self.prepare_to_aspirate() self.move_to(location=location) elif not in_place: self.move_to(location=location) @@ -443,7 +443,7 @@ def has_tip(self) -> bool: def is_ready_to_aspirate(self) -> bool: return self.get_hardware_state()["ready_to_aspirate"] - def prepare_for_aspirate(self) -> None: + def prepare_to_aspirate(self) -> None: self._protocol_interface.get_hardware().prepare_for_aspirate(self._mount) def get_return_height(self) -> float: diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 29a8b871ac8..ab90676c27e 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -106,7 +106,7 @@ def aspirate( "cause over aspiration if the previous command is a " "blow_out." ) - self.prepare_for_aspirate() + self.prepare_to_aspirate() self.move_to(location=location, well_core=well_core) elif location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) @@ -334,7 +334,7 @@ def has_tip(self) -> bool: def is_ready_to_aspirate(self) -> bool: return self._pipette_dict["ready_to_aspirate"] - def prepare_for_aspirate(self) -> None: + def prepare_to_aspirate(self) -> None: self._raise_if_no_tip(HardwareAction.PREPARE_ASPIRATE.name) def get_return_height(self) -> float: diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 51f9ea36fde..9f03645f69d 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -1614,3 +1614,47 @@ def configure_for_volume(self, volume: float) -> None: if last_location and isinstance(last_location.labware, labware.Well): self.move_to(last_location.labware.top()) self._core.configure_for_volume(volume) + + @requires_version(2, 16) + def prepare_to_aspirate(self) -> None: + """Prepare a pipette for aspiration. + + Before a pipette can aspirate into an empty tip, the plunger must be in its + bottom position. After dropping a tip or blowing out, the plunger will be in a + different position. This function moves the plunger to the bottom position, + regardless of its current position, to make sure that the pipette is ready to + aspirate. + + You rarely need to call this function. The API automatically prepares the + pipette for aspiration as part of other commands: + + - After picking up a tip with :py:meth:`.pick_up_tip`. + - When calling :py:meth:`.aspirate`, if the pipette isn't already prepared. + If the pipette is in a well, it will move out of the well, move the plunger, + and then move back. + + Use ``prepare_to_aspirate`` when you need to control exactly when the plunger + motion will happen. A common use case is a pre-wetting routine, which requires + preparing for aspiration, moving into a well, and then aspirating *without + leaving the well*:: + + pipette.move_to(well.bottom(z=2)) + pipette.delay(5) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.delay(5) + pipette.aspirate(10, well.bottom(z=2)) + + The call to ``prepare_to_aspirate()`` means that the plunger will be in the + bottom position before the call to ``aspirate()``. Since it doesn't need to + prepare again, it will not move up out of the well to move the plunger. It will + aspirate in place. + """ + if self._core.get_current_volume(): + raise CommandPreconditionViolated( + message=f"Cannot prepare {str(self)} for aspirate while it contains liquid." + ) + self._core.prepare_to_aspirate() diff --git a/api/src/opentrons/protocol_engine/clients/sync_client.py b/api/src/opentrons/protocol_engine/clients/sync_client.py index 65eacf32d11..cfef710a5ce 100644 --- a/api/src/opentrons/protocol_engine/clients/sync_client.py +++ b/api/src/opentrons/protocol_engine/clients/sync_client.py @@ -284,6 +284,14 @@ def configure_for_volume( result = self._transport.execute_command(request=request) return cast(commands.ConfigureForVolumeResult, result) + def prepare_to_aspirate(self, pipette_id: str) -> commands.PrepareToAspirateResult: + """Execute a PrepareToAspirate command.""" + request = commands.PrepareToAspirateCreate( + params=commands.PrepareToAspirateParams(pipetteId=pipette_id) + ) + result = self._transport.execute_command(request=request) + return cast(commands.PrepareToAspirateResult, result) + def aspirate( self, pipette_id: str, diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index 90e1387b267..85b25046479 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -266,6 +266,14 @@ ConfigureForVolumeCommandType, ) +from .prepare_to_aspirate import ( + PrepareToAspirate, + PrepareToAspirateCreate, + PrepareToAspirateParams, + PrepareToAspirateResult, + PrepareToAspirateCommandType, +) + __all__ = [ # command type unions "Command", @@ -463,4 +471,10 @@ "ConfigureForVolumeParams", "ConfigureForVolumeResult", "ConfigureForVolumeCommandType", + # prepare pipette for aspirate command bundle + "PrepareToAspirate", + "PrepareToAspirateCreate", + "PrepareToAspirateParams", + "PrepareToAspirateResult", + "PrepareToAspirateCommandType", ] diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 12f6675e28c..bf9ff6b7768 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -235,6 +235,14 @@ ConfigureForVolumePrivateResult, ) +from .prepare_to_aspirate import ( + PrepareToAspirate, + PrepareToAspirateParams, + PrepareToAspirateCreate, + PrepareToAspirateResult, + PrepareToAspirateCommandType, +) + Command = Union[ Aspirate, AspirateInPlace, @@ -257,6 +265,7 @@ MoveRelative, MoveToCoordinates, MoveToWell, + PrepareToAspirate, WaitForResume, WaitForDuration, PickUpTip, @@ -313,6 +322,7 @@ MoveRelativeParams, MoveToCoordinatesParams, MoveToWellParams, + PrepareToAspirateParams, WaitForResumeParams, WaitForDurationParams, PickUpTipParams, @@ -370,6 +380,7 @@ MoveRelativeCommandType, MoveToCoordinatesCommandType, MoveToWellCommandType, + PrepareToAspirateCommandType, WaitForResumeCommandType, WaitForDurationCommandType, PickUpTipCommandType, @@ -426,6 +437,7 @@ MoveRelativeCreate, MoveToCoordinatesCreate, MoveToWellCreate, + PrepareToAspirateCreate, WaitForResumeCreate, WaitForDurationCreate, PickUpTipCreate, @@ -482,6 +494,7 @@ MoveRelativeResult, MoveToCoordinatesResult, MoveToWellResult, + PrepareToAspirateResult, WaitForResumeResult, WaitForDurationResult, PickUpTipResult, diff --git a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py new file mode 100644 index 00000000000..57fa679bb09 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py @@ -0,0 +1,73 @@ +"""Prepare to aspirate command request, result, and implementation models.""" + +from __future__ import annotations +from pydantic import BaseModel +from typing import TYPE_CHECKING, Optional, Type +from typing_extensions import Literal + +from .pipetting_common import ( + PipetteIdMixin, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, +) + +if TYPE_CHECKING: + from ..execution.pipetting import PipettingHandler + +PrepareToAspirateCommandType = Literal["prepareToAspirate"] + + +class PrepareToAspirateParams(PipetteIdMixin): + """Parameters required to prepare a specific pipette for aspiration.""" + + pass + + +class PrepareToAspirateResult(BaseModel): + """Result data from execution of an PrepareToAspirate command.""" + + pass + + +class PrepareToAspirateImplementation( + AbstractCommandImpl[ + PrepareToAspirateParams, + PrepareToAspirateResult, + ] +): + """Prepare for aspirate command implementation.""" + + def __init__(self, pipetting: PipettingHandler, **kwargs: object) -> None: + self._pipetting_handler = pipetting + + async def execute(self, params: PrepareToAspirateParams) -> PrepareToAspirateResult: + """Prepare the pipette to aspirate.""" + await self._pipetting_handler.prepare_for_aspirate( + pipette_id=params.pipetteId, + ) + + return PrepareToAspirateResult() + + +class PrepareToAspirate(BaseCommand[PrepareToAspirateParams, PrepareToAspirateResult]): + """Prepare for aspirate command model.""" + + commandType: PrepareToAspirateCommandType = "prepareToAspirate" + params: PrepareToAspirateParams + result: Optional[PrepareToAspirateResult] + + _ImplementationCls: Type[ + PrepareToAspirateImplementation + ] = PrepareToAspirateImplementation + + +class PrepareToAspirateCreate(BaseCommandCreate[PrepareToAspirateParams]): + """Prepare for aspirate command creation request model.""" + + commandType: PrepareToAspirateCommandType = "prepareToAspirate" + params: PrepareToAspirateParams + + _CommandCls: Type[PrepareToAspirate] = PrepareToAspirate diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 729bcf47383..8c8d7a366cb 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -37,6 +37,7 @@ thermocycler, heater_shaker, CommandPrivateResult, + PrepareToAspirateResult, ) from ..commands.configuring_common import PipetteConfigUpdateResultMixin from ..actions import ( @@ -221,6 +222,10 @@ def _handle_command( # noqa: C901 pipette_id = command.params.pipetteId self._state.aspirated_volume_by_id[pipette_id] = None + elif isinstance(command.result, PrepareToAspirateResult): + pipette_id = command.params.pipetteId + self._state.aspirated_volume_by_id[pipette_id] = 0 + def _update_current_well(self, command: Command) -> None: # These commands leave the pipette in a new well. # Update current_well to reflect that. diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 54ecab4cefb..9a38dfef87b 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -28,6 +28,10 @@ ) from opentrons.types import Location, Mount, Point +from opentrons_shared_data.errors.exceptions import ( + CommandPreconditionViolated, +) + @pytest.fixture(autouse=True) def _mock_validation_module(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: @@ -912,3 +916,21 @@ def test_plunger_speed_removed(subject: InstrumentContext) -> None: """It should raise an error on PAPI >= v2.14.""" with pytest.raises(APIVersionError): subject.speed + + +def test_prepare_to_aspirate( + subject: InstrumentContext, decoy: Decoy, mock_instrument_core: InstrumentCore +) -> None: + """It should call the core function.""" + decoy.when(mock_instrument_core.get_current_volume()).then_return(0) + subject.prepare_to_aspirate() + decoy.verify(mock_instrument_core.prepare_to_aspirate(), times=1) + + +def test_prepare_to_aspirate_checks_volume( + subject: InstrumentContext, decoy: Decoy, mock_instrument_core: InstrumentCore +) -> None: + """It should raise an error if you prepare for aspirate with liquid in the pipette.""" + decoy.when(mock_instrument_core.get_current_volume()).then_return(10) + with pytest.raises(CommandPreconditionViolated): + subject.prepare_to_aspirate() diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index 5555fd5e979..d0916dd4108 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -43,7 +43,7 @@ def test_prepare_to_aspirate_no_tip(subject: InstrumentCore) -> None: with pytest.raises( UnexpectedTipRemovalError, match="Cannot perform PREPARE_ASPIRATE" ): - subject.prepare_for_aspirate() # type: ignore[attr-defined] + subject.prepare_to_aspirate() def test_dispense_no_tip(subject: InstrumentCore) -> None: @@ -161,7 +161,7 @@ def test_aspirate_too_much( increment=None, prep_after=False, ) - subject.prepare_for_aspirate() # type: ignore[attr-defined] + subject.prepare_to_aspirate() with pytest.raises( AssertionError, match="Cannot aspirate more than pipette max volume" ): @@ -215,7 +215,7 @@ def test_pipette_dict( def _aspirate(i: InstrumentCore, labware: LabwareCore) -> None: """pipette dict with tip fixture.""" - i.prepare_for_aspirate() # type: ignore[attr-defined] + i.prepare_to_aspirate() i.aspirate( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), @@ -228,7 +228,7 @@ def _aspirate(i: InstrumentCore, labware: LabwareCore) -> None: def _aspirate_dispense(i: InstrumentCore, labware: LabwareCore) -> None: """pipette dict with tip fixture.""" - i.prepare_for_aspirate() # type: ignore[attr-defined] + i.prepare_to_aspirate() i.aspirate( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), @@ -250,7 +250,7 @@ def _aspirate_dispense(i: InstrumentCore, labware: LabwareCore) -> None: def _aspirate_blowout(i: InstrumentCore, labware: LabwareCore) -> None: """pipette dict with tip fixture.""" - i.prepare_for_aspirate() # type: ignore[attr-defined] + i.prepare_to_aspirate() i.aspirate( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), 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 new file mode 100644 index 00000000000..d3f09d6685f --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py @@ -0,0 +1,29 @@ +"""Test prepare to aspirate commands.""" + +from decoy import Decoy + +from opentrons.protocol_engine.execution import ( + PipettingHandler, +) + +from opentrons.protocol_engine.commands.prepare_to_aspirate import ( + PrepareToAspirateParams, + PrepareToAspirateImplementation, + PrepareToAspirateResult, +) + + +async def test_prepare_to_aspirate_implmenetation( + decoy: Decoy, pipetting: PipettingHandler +) -> None: + """A PrepareToAspirate command should have an executing implementation.""" + subject = PrepareToAspirateImplementation(pipetting=pipetting) + + data = PrepareToAspirateParams(pipetteId="some id") + + decoy.when(await pipetting.prepare_for_aspirate(pipette_id="some id")).then_return( + None + ) + + result = await subject.execute(data) + assert isinstance(result, PrepareToAspirateResult) diff --git a/api/tests/opentrons/protocol_engine/state/command_fixtures.py b/api/tests/opentrons/protocol_engine/state/command_fixtures.py index e0b5368be2f..ef548377a3e 100644 --- a/api/tests/opentrons/protocol_engine/state/command_fixtures.py +++ b/api/tests/opentrons/protocol_engine/state/command_fixtures.py @@ -466,3 +466,17 @@ def create_move_labware_command( params=params, result=result, ) + + +def create_prepare_to_aspirate_command(pipette_id: str) -> cmd.PrepareToAspirate: + """Get a completed PrepareToAspirate command.""" + params = cmd.PrepareToAspirateParams(pipetteId=pipette_id) + result = cmd.PrepareToAspirateResult() + return cmd.PrepareToAspirate( + id="command-id", + key="command-key", + status=cmd.CommandStatus.SUCCEEDED, + createdAt=datetime(year=2023, month=10, day=24), + params=params, + result=result, + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index b4fb8f868ec..25370a410fc 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -46,6 +46,7 @@ create_move_labware_command, create_move_to_coordinates_command, create_move_relative_command, + create_prepare_to_aspirate_command, ) @@ -855,3 +856,40 @@ def test_homing_commands_clear_deck_point( assert subject.state.current_deck_point == CurrentDeckPoint( mount=None, deck_point=None ) + + +@pytest.mark.parametrize( + "previous", + [ + create_blow_out_command(pipette_id="pipette-id", flow_rate=1.0), + create_dispense_command(pipette_id="pipette-id", volume=10, flow_rate=1.0), + ], +) +def test_prepare_to_aspirate_marks_pipette_ready( + subject: PipetteStore, previous: cmd.Command +) -> None: + """It should mark a pipette as ready to aspirate.""" + load_pipette_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P50_MULTI_FLEX, + mount=MountType.LEFT, + ) + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="pipette-id", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + subject.handle_action( + UpdateCommandAction(private_result=None, command=load_pipette_command) + ) + subject.handle_action( + UpdateCommandAction(private_result=None, command=pick_up_tip_command) + ) + + subject.handle_action(UpdateCommandAction(private_result=None, command=previous)) + + prepare_to_aspirate_command = create_prepare_to_aspirate_command( + pipette_id="pipette-id" + ) + subject.handle_action( + UpdateCommandAction(private_result=None, command=prepare_to_aspirate_command) + ) + assert subject.state.aspirated_volume_by_id["pipette-id"] == 0.0 diff --git a/shared-data/command/schemas/8.json b/shared-data/command/schemas/8.json index 7f584822fe7..81f1d37ca5f 100644 --- a/shared-data/command/schemas/8.json +++ b/shared-data/command/schemas/8.json @@ -65,6 +65,9 @@ { "$ref": "#/definitions/MoveToWellCreate" }, + { + "$ref": "#/definitions/PrepareToAspirateCreate" + }, { "$ref": "#/definitions/WaitForResumeCreate" }, @@ -1702,6 +1705,49 @@ }, "required": ["params"] }, + "PrepareToAspirateParams": { + "title": "PrepareToAspirateParams", + "description": "Parameters required to prepare a specific pipette for aspiration.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["pipetteId"] + }, + "PrepareToAspirateCreate": { + "title": "PrepareToAspirateCreate", + "description": "Prepare for aspirate command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "prepareToAspirate", + "enum": ["prepareToAspirate"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/PrepareToAspirateParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, "WaitForResumeParams": { "title": "WaitForResumeParams", "description": "Payload required to pause the protocol.", diff --git a/shared-data/command/types/pipetting.ts b/shared-data/command/types/pipetting.ts index a2acc5b9ccb..1a0c0dd36c8 100644 --- a/shared-data/command/types/pipetting.ts +++ b/shared-data/command/types/pipetting.ts @@ -9,6 +9,7 @@ export type PipettingRunTimeCommand = | DropTipRunTimeCommand | DropTipInPlaceRunTimeCommand | ConfigureForVolumeRunTimeCommand + | PrepareToAspirateRunTimeCommand export type PipettingCreateCommand = | AspirateCreateCommand @@ -20,6 +21,7 @@ export type PipettingCreateCommand = | DropTipCreateCommand | DropTipInPlaceCreateCommand | ConfigureForVolumeCreateCommand + | PrepareToAspirateCreateCommand export interface ConfigureForVolumeCreateCommand extends CommonCommandCreateInfo { @@ -112,6 +114,18 @@ export interface DropTipInPlaceRunTimeCommand result?: any } +export interface PrepareToAspirateCreateCommand + extends CommonCommandCreateInfo { + commandType: 'prepareToAspirate' + params: PipetteIdentityParams +} + +export interface PrepareToAspirateRunTimeCommand + extends CommonCommandRunTimeInfo, + PrepareToAspirateCreateCommand { + result?: any +} + export type AspDispAirgapParams = FlowRateParams & PipetteAccessParams & VolumeParams & @@ -144,8 +158,11 @@ interface FlowRateParams { flowRate: number // µL/s } -interface PipetteAccessParams { +interface PipetteIdentityParams { pipetteId: string +} + +interface PipetteAccessParams extends PipetteIdentityParams { labwareId: string wellName: string }