Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): Addition of Evotip specific commands #17351

Merged
merged 27 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5b49e05
feat(api): Add the seal command
Laura-Danielle Dec 13, 2024
4ae7f0a
feat(api): Add evotip specific instrument context commands
Laura-Danielle Jan 8, 2025
3a6700e
feat(protocol_engine): Add evotip specific commands to the protocol e…
Laura-Danielle Jan 8, 2025
ce55343
api and core hookup for evotip specific commands
CaseyBatten Jan 22, 2025
299bdf3
context cleanup and parameterization
CaseyBatten Jan 22, 2025
c456ad7
seal command functionality
CaseyBatten Jan 22, 2025
8912d90
update evotip dispense command
Laura-Danielle Jan 23, 2025
477b1c8
update unseal command with correct parameters
Laura-Danielle Jan 23, 2025
7b06608
fix: handle expected stalls in gantry mover
Laura-Danielle Jan 23, 2025
44b4faf
seal command state update and functionality testing
CaseyBatten Jan 23, 2025
ebdfe93
ensure evo tip dispense and unseal functions work
CaseyBatten Jan 24, 2025
4396fa5
papi resin tip command name and doc updates
CaseyBatten Jan 27, 2025
7390be3
default volume and flow rate based on internal evotips testing
CaseyBatten Jan 27, 2025
ba1b3ae
fix naming mismatches in core and engine
Laura-Danielle Jan 29, 2025
8447056
fix test mocks missing new parameters
Laura-Danielle Jan 29, 2025
1689445
Merge branch 'chore_release-8.3.0' into EXEC-907-evotip-specific-comm…
CaseyBatten Jan 29, 2025
4e6c7c7
Engine behavior clean up and removal of unnecessary parameters
CaseyBatten Jan 29, 2025
61da984
pickup and drop offset adjustments as well as test fixture correction
CaseyBatten Jan 29, 2025
1577a5c
evotip seal test cleanup and ignore plunger correction
CaseyBatten Jan 30, 2025
117918f
fixing linter errors
Laura-Danielle Jan 30, 2025
a131b39
Merge branch 'EXEC-907-evotip-specific-commands' of github.com:Opentr…
Laura-Danielle Jan 30, 2025
b1a3619
fix lint and formatting
Laura-Danielle Jan 30, 2025
ee624cf
more linter fixes
Laura-Danielle Jan 30, 2025
2288583
fix evotip dispense test and add movement state
Laura-Danielle Jan 30, 2025
9d17f2d
unseal test cleanup removal of unused pushout command and command sch…
CaseyBatten Jan 30, 2025
343ae3c
hardware testing fixes
CaseyBatten Jan 30, 2025
03110bc
clean up docs ref
CaseyBatten Jan 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion api/src/opentrons/hardware_control/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,7 @@ async def move_axes(
position: Mapping[Axis, float],
speed: Optional[float] = None,
max_speeds: Optional[Dict[Axis, float]] = None,
expect_stalls: bool = False,
) -> None:
"""Moves the effectors of the specified axis to the specified position.
The effector of the x,y axis is the center of the carriage.
Expand Down Expand Up @@ -1248,7 +1249,10 @@ async def pick_up_tip(
await self.prepare_for_aspirate(mount)

async def tip_drop_moves(
self, mount: top_types.Mount, home_after: bool = True
self,
mount: top_types.Mount,
home_after: bool = True,
ignore_plunger: bool = False,
) -> None:
spec, _ = self.plan_check_drop_tip(mount, home_after)

Expand Down
26 changes: 18 additions & 8 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1189,7 +1189,7 @@ async def move_to(
speed: Optional[float] = None,
critical_point: Optional[CriticalPoint] = None,
max_speeds: Union[None, Dict[Axis, float], OT3AxisMap[float]] = None,
_expect_stalls: bool = False,
expect_stalls: bool = False,
) -> None:
"""Move the critical point of the specified mount to a location
relative to the deck, at the specified speed."""
Expand Down Expand Up @@ -1233,14 +1233,15 @@ async def move_to(
target_position,
speed=speed,
max_speeds=checked_max,
expect_stalls=_expect_stalls,
expect_stalls=expect_stalls,
)

async def move_axes( # noqa: C901
self,
position: Mapping[Axis, float],
speed: Optional[float] = None,
max_speeds: Optional[Dict[Axis, float]] = None,
expect_stalls: bool = False,
) -> None:
"""Moves the effectors of the specified axis to the specified position.
The effector of the x,y axis is the center of the carriage.
Expand Down Expand Up @@ -1296,7 +1297,11 @@ async def move_axes( # noqa: C901
if axis not in absolute_positions:
absolute_positions[axis] = position_value

await self._move(target_position=absolute_positions, speed=speed)
await self._move(
target_position=absolute_positions,
speed=speed,
expect_stalls=expect_stalls,
)

async def move_rel(
self,
Expand All @@ -1306,7 +1311,7 @@ async def move_rel(
max_speeds: Union[None, Dict[Axis, float], OT3AxisMap[float]] = None,
check_bounds: MotionChecks = MotionChecks.NONE,
fail_on_not_homed: bool = False,
_expect_stalls: bool = False,
expect_stalls: bool = False,
) -> None:
"""Move the critical point of the specified mount by a specified
displacement in a specified direction, at the specified speed."""
Expand Down Expand Up @@ -1348,7 +1353,7 @@ async def move_rel(
speed=speed,
max_speeds=checked_max,
check_bounds=check_bounds,
expect_stalls=_expect_stalls,
expect_stalls=expect_stalls,
)

async def _cache_and_maybe_retract_mount(self, mount: OT3Mount) -> None:
Expand Down Expand Up @@ -2320,11 +2325,16 @@ def set_working_volume(
instrument.working_volume = tip_volume

async def tip_drop_moves(
self, mount: Union[top_types.Mount, OT3Mount], home_after: bool = False
self,
mount: Union[top_types.Mount, OT3Mount],
home_after: bool = False,
ignore_plunger: bool = False,
) -> None:
realmount = OT3Mount.from_mount(mount)

await self._move_to_plunger_bottom(realmount, rate=1.0, check_current_vol=False)
if ignore_plunger is False:
await self._move_to_plunger_bottom(
realmount, rate=1.0, check_current_vol=False
)

if self.gantry_load == GantryLoad.HIGH_THROUGHPUT:
spec = self._pipette_handler.plan_ht_drop_tip()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ async def pick_up_tip(
...

async def tip_drop_moves(
self, mount: MountArgType, home_after: bool = True
self,
mount: MountArgType,
home_after: bool = True,
ignore_plunger: bool = False,
) -> None:
...

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ async def move_axes(
position: Mapping[Axis, float],
speed: Optional[float] = None,
max_speeds: Optional[Dict[Axis, float]] = None,
expect_stalls: bool = False,
) -> None:
"""Moves the effectors of the specified axis to the specified position.
The effector of the x,y axis is the center of the carriage.
Expand Down
37 changes: 37 additions & 0 deletions api/src/opentrons/legacy_commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,40 @@ def move_to_disposal_location(
"name": command_types.MOVE_TO_DISPOSAL_LOCATION,
"payload": {"instrument": instrument, "location": location, "text": text},
}


def seal(
instrument: InstrumentContext,
location: Well,
) -> command_types.SealCommand:
location_text = stringify_location(location)
text = f"Sealing to {location_text}"
return {
"name": command_types.SEAL,
"payload": {"instrument": instrument, "location": location, "text": text},
}


def unseal(
instrument: InstrumentContext,
location: Well,
) -> command_types.UnsealCommand:
location_text = stringify_location(location)
text = f"Unsealing from {location_text}"
return {
"name": command_types.UNSEAL,
"payload": {"instrument": instrument, "location": location, "text": text},
}


def resin_tip_dispense(
instrument: InstrumentContext,
flow_rate: float | None,
) -> command_types.PressurizeCommand:
if flow_rate is None:
flow_rate = 10 # The Protocol Engine default for Resin Tip Dispense
text = f"Pressurize pipette to dispense from resin tip at {flow_rate}uL/s."
return {
"name": command_types.PRESSURIZE,
"payload": {"instrument": instrument, "text": text},
}
39 changes: 39 additions & 0 deletions api/src/opentrons/legacy_commands/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
RETURN_TIP: Final = "command.RETURN_TIP"
MOVE_TO: Final = "command.MOVE_TO"
MOVE_TO_DISPOSAL_LOCATION: Final = "command.MOVE_TO_DISPOSAL_LOCATION"
SEAL: Final = "command.SEAL"
UNSEAL: Final = "command.UNSEAL"
PRESSURIZE: Final = "command.PRESSURIZE"


# Modules #

Expand Down Expand Up @@ -535,11 +539,40 @@ class MoveLabwareCommandPayload(TextOnlyPayload):
pass


class SealCommandPayload(TextOnlyPayload):
instrument: InstrumentContext
location: Union[None, Location, Well]


class UnsealCommandPayload(TextOnlyPayload):
instrument: InstrumentContext
location: Union[None, Location, Well]


class PressurizeCommandPayload(TextOnlyPayload):
instrument: InstrumentContext


class MoveLabwareCommand(TypedDict):
name: Literal["command.MOVE_LABWARE"]
payload: MoveLabwareCommandPayload


class SealCommand(TypedDict):
name: Literal["command.SEAL"]
payload: SealCommandPayload


class UnsealCommand(TypedDict):
name: Literal["command.UNSEAL"]
payload: UnsealCommandPayload


class PressurizeCommand(TypedDict):
name: Literal["command.PRESSURIZE"]
payload: PressurizeCommandPayload


Command = Union[
DropTipCommand,
DropTipInDisposalLocationCommand,
Expand Down Expand Up @@ -588,6 +621,9 @@ class MoveLabwareCommand(TypedDict):
MoveToCommand,
MoveToDisposalLocationCommand,
MoveLabwareCommand,
SealCommand,
UnsealCommand,
PressurizeCommand,
]


Expand Down Expand Up @@ -637,6 +673,9 @@ class MoveLabwareCommand(TypedDict):
MoveToCommandPayload,
MoveToDisposalLocationCommandPayload,
MoveLabwareCommandPayload,
SealCommandPayload,
UnsealCommandPayload,
PressurizeCommandPayload,
]


Expand Down
109 changes: 109 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
from opentrons.protocol_api._liquid import LiquidClass

_DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17)
_RESIN_TIP_DEFAULT_VOLUME = 400
_RESIN_TIP_DEFAULT_FLOW_RATE = 10.0


class InstrumentCore(AbstractInstrument[WellCore]):
Expand Down Expand Up @@ -678,6 +680,113 @@ def move_to(
location=location, mount=self.get_mount()
)

def resin_tip_seal(
self, location: Location, well_core: WellCore, in_place: Optional[bool] = False
) -> None:
labware_id = well_core.labware_id
well_name = well_core.get_name()
well_location = (
self._engine_client.state.geometry.get_relative_pick_up_tip_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
)

self._engine_client.execute_command(
cmd.EvotipSealPipetteParams(
pipetteId=self._pipette_id,
labwareId=labware_id,
wellName=well_name,
wellLocation=well_location,
)
)

def resin_tip_unseal(self, location: Location, well_core: WellCore) -> None:
well_name = well_core.get_name()
labware_id = well_core.labware_id

if location is not None:
relative_well_location = (
self._engine_client.state.geometry.get_relative_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
)
)

well_location = DropTipWellLocation(
origin=DropTipWellOrigin(relative_well_location.origin.value),
offset=relative_well_location.offset,
)
else:
well_location = DropTipWellLocation()

pipette_movement_conflict.check_safe_for_pipette_movement(
engine_state=self._engine_client.state,
pipette_id=self._pipette_id,
labware_id=labware_id,
well_name=well_name,
well_location=well_location,
)
self._engine_client.execute_command(
cmd.EvotipUnsealPipetteParams(
pipetteId=self._pipette_id,
labwareId=labware_id,
wellName=well_name,
wellLocation=well_location,
)
)

self._protocol_core.set_last_location(location=location, mount=self.get_mount())

def resin_tip_dispense(
self,
location: Location,
well_core: WellCore,
volume: Optional[float] = None,
flow_rate: Optional[float] = None,
) -> None:
"""
Args:
volume: The volume of liquid to dispense, in microliters. Defaults to 400uL.
location: The exact location to dispense to.
well_core: The well to dispense to, if applicable.
flow_rate: The flow rate in µL/s to dispense at. Defaults to 10.0uL/S.
"""
if isinstance(location, (TrashBin, WasteChute)):
raise ValueError("Trash Bin and Waste Chute have no Wells.")
well_name = well_core.get_name()
labware_id = well_core.labware_id
if volume is None:
volume = _RESIN_TIP_DEFAULT_VOLUME
if flow_rate is None:
flow_rate = _RESIN_TIP_DEFAULT_FLOW_RATE

well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location(
labware_id=labware_id,
well_name=well_name,
absolute_point=location.point,
is_meniscus=None,
)
pipette_movement_conflict.check_safe_for_pipette_movement(
engine_state=self._engine_client.state,
pipette_id=self._pipette_id,
labware_id=labware_id,
well_name=well_name,
well_location=well_location,
)
self._engine_client.execute_command(
cmd.EvotipDispenseParams(
pipetteId=self._pipette_id,
labwareId=labware_id,
wellName=well_name,
wellLocation=well_location,
volume=volume,
flowRate=flow_rate,
)
)

def get_mount(self) -> Mount:
"""Get the mount the pipette is attached to."""
return self._engine_client.state.pipettes.get(
Expand Down
27 changes: 27 additions & 0 deletions api/src/opentrons/protocol_api/core/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,33 @@ def move_to(
) -> None:
...

@abstractmethod
def resin_tip_seal(
self,
location: types.Location,
well_core: WellCoreType,
in_place: Optional[bool] = False,
) -> None:
...

@abstractmethod
def resin_tip_unseal(
self,
location: types.Location,
well_core: WellCoreType,
) -> None:
...

@abstractmethod
def resin_tip_dispense(
self,
location: types.Location,
well_core: WellCoreType,
volume: Optional[float] = None,
flow_rate: Optional[float] = None,
) -> None:
...

@abstractmethod
def get_mount(self) -> types.Mount:
...
Expand Down
Loading
Loading