-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): implement liquid probe protocol engine command (#15351)
<!-- Thanks for taking the time to open a pull request! Please make sure you've read the "Opening Pull Requests" section of our Contributing Guide: https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests To ensure your code is reviewed quickly and thoroughly, please fill out the sections below to the best of your ability! --> # Overview <!-- Use this section to describe your pull-request at a high level. If the PR addresses any open issues, please tag the issues here. --> This closes EXEC-436. Pulls well dimension data from Protocol Engine StateView, which is now used for process `max_z_distance`. Also bubbles up LiquidNotFoundError. # Test Plan <!-- Use this section to describe the steps that you took to test your Pull Request. If you did not perform any testing provide justification why. OT-3 Developers: You should default to testing on actual physical hardware. Once again, if you did not perform testing against hardware, justify why. Note: It can be helpful to write a test plan before doing development Example Test Plan (HTTP API Change) - Verified that new optional argument `dance-party` causes the robot to flash its lights, move the pipettes, then home. - Verified that when you omit the `dance-party` option the robot homes normally - Added protocol that uses `dance-party` argument to G-Code Testing Suite - Ran protocol that did not use `dance-party` argument and everything was successful - Added unit tests to validate that changes to pydantic model are correct --> Added protocol engine command test. # Changelog <!-- List out the changes to the code in this PR. Please try your best to categorize your changes and describe what has changed and why. Example changelog: - Fixed app crash when trying to calibrate an illegal pipette - Added state to API to track pipette usage - Updated API docs to mention only two pipettes are supported IMPORTANT: MAKE SURE ANY BREAKING CHANGES ARE PROPERLY COMMUNICATED --> # Review requests <!-- Describe any requests for your reviewers here. --> # Risk assessment <!-- Carefully go over your pull request and look at the other parts of the codebase it may affect. Look for the possibility, even if you think it's small, that your change may affect some other part of the system - for instance, changing return tip behavior in protocol may also change the behavior of labware calibration. Identify the other parts of the system your codebase may affect, so that in addition to your own review and testing, other people who may not have the system internalized as much as you can focus their attention and testing there. --> Low. Largely builds off existing Aspirate protocol engine command. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: pmoegenburg <[email protected]>
- Loading branch information
1 parent
70b4174
commit f8d7d64
Showing
22 changed files
with
594 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
154 changes: 154 additions & 0 deletions
154
api/src/opentrons/protocol_engine/commands/liquid_probe.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
"""Liquid-probe command for OT3 hardware. request, result, and implementation models.""" | ||
from __future__ import annotations | ||
from typing import TYPE_CHECKING, Optional, Type, Union | ||
from opentrons_shared_data.errors.exceptions import PipetteLiquidNotFoundError | ||
from typing_extensions import Literal | ||
|
||
from pydantic import Field | ||
|
||
from ..types import WellLocation, WellOrigin, CurrentWell, DeckPoint | ||
from .pipetting_common import ( | ||
LiquidNotFoundError, | ||
LiquidNotFoundErrorInternalData, | ||
PipetteIdMixin, | ||
WellLocationMixin, | ||
DestinationPositionResult, | ||
) | ||
from .command import ( | ||
AbstractCommandImpl, | ||
BaseCommand, | ||
BaseCommandCreate, | ||
DefinedErrorData, | ||
SuccessData, | ||
) | ||
from ..errors.error_occurrence import ErrorOccurrence | ||
|
||
if TYPE_CHECKING: | ||
from ..execution import MovementHandler, PipettingHandler | ||
from ..resources import ModelUtils | ||
|
||
|
||
LiquidProbeCommandType = Literal["liquidProbe"] | ||
|
||
|
||
class LiquidProbeParams(PipetteIdMixin, WellLocationMixin): | ||
"""Parameters required to liquid probe a specific well.""" | ||
|
||
pass | ||
|
||
|
||
class LiquidProbeResult(DestinationPositionResult): | ||
"""Result data from the execution of a liquid-probe command.""" | ||
|
||
z_position: float = Field( | ||
..., description="The Z coordinate, in mm, of the found liquid in deck space." | ||
) | ||
|
||
|
||
_ExecuteReturn = Union[ | ||
SuccessData[LiquidProbeResult, None], | ||
DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData], | ||
] | ||
|
||
|
||
class LiquidProbeImplementation(AbstractCommandImpl[LiquidProbeParams, _ExecuteReturn]): | ||
"""The implementation of a `liquidProbe` command.""" | ||
|
||
def __init__( | ||
self, | ||
movement: MovementHandler, | ||
pipetting: PipettingHandler, | ||
model_utils: ModelUtils, | ||
**kwargs: object, | ||
) -> None: | ||
self._movement = movement | ||
self._pipetting = pipetting | ||
self._model_utils = model_utils | ||
|
||
async def execute(self, params: LiquidProbeParams) -> _ExecuteReturn: | ||
"""Move to and liquid probe the requested well. | ||
Return the z-position of the found liquid. | ||
Raises: | ||
LiquidNotFoundError: if liquid is not found during the probe process. | ||
""" | ||
pipette_id = params.pipetteId | ||
labware_id = params.labwareId | ||
well_name = params.wellName | ||
|
||
ready_to_probe = self._pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id) | ||
|
||
current_well = None | ||
|
||
if not ready_to_probe: | ||
await self._movement.move_to_well( | ||
pipette_id=pipette_id, | ||
labware_id=labware_id, | ||
well_name=well_name, | ||
well_location=WellLocation(origin=WellOrigin.TOP), | ||
) | ||
|
||
current_well = CurrentWell( | ||
pipette_id=pipette_id, | ||
labware_id=labware_id, | ||
well_name=well_name, | ||
) | ||
|
||
# liquid_probe process start position | ||
position = await self._movement.move_to_well( | ||
pipette_id=pipette_id, | ||
labware_id=labware_id, | ||
well_name=well_name, | ||
well_location=params.wellLocation, | ||
current_well=current_well, | ||
) | ||
|
||
try: | ||
z_pos = await self._pipetting.liquid_probe_in_place( | ||
pipette_id=pipette_id, labware_id=labware_id, well_name=well_name | ||
) | ||
except PipetteLiquidNotFoundError as e: | ||
return DefinedErrorData( | ||
public=LiquidNotFoundError( | ||
id=self._model_utils.generate_id(), | ||
createdAt=self._model_utils.get_timestamp(), | ||
wrappedErrors=[ | ||
ErrorOccurrence.from_failed( | ||
id=self._model_utils.generate_id(), | ||
createdAt=self._model_utils.get_timestamp(), | ||
error=e, | ||
) | ||
], | ||
), | ||
private=LiquidNotFoundErrorInternalData( | ||
position=DeckPoint(x=position.x, y=position.y, z=position.z) | ||
), | ||
) | ||
else: | ||
return SuccessData( | ||
public=LiquidProbeResult( | ||
z_position=z_pos, | ||
position=DeckPoint(x=position.x, y=position.y, z=position.z), | ||
), | ||
private=None, | ||
) | ||
|
||
|
||
class LiquidProbe(BaseCommand[LiquidProbeParams, LiquidProbeResult, ErrorOccurrence]): | ||
"""LiquidProbe command model.""" | ||
|
||
commandType: LiquidProbeCommandType = "liquidProbe" | ||
params: LiquidProbeParams | ||
result: Optional[LiquidProbeResult] | ||
|
||
_ImplementationCls: Type[LiquidProbeImplementation] = LiquidProbeImplementation | ||
|
||
|
||
class LiquidProbeCreate(BaseCommandCreate[LiquidProbeParams]): | ||
"""Create LiquidProbe command request model.""" | ||
|
||
commandType: LiquidProbeCommandType = "liquidProbe" | ||
params: LiquidProbeParams | ||
|
||
_CommandCls: Type[LiquidProbe] = LiquidProbe |
Oops, something went wrong.