Skip to content

Commit

Permalink
chore: release->edge for 7.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sfoster1 committed Dec 19, 2023
2 parents 0c24842 + 2bc8378 commit e3c3bf8
Show file tree
Hide file tree
Showing 84 changed files with 1,923 additions and 802 deletions.
2 changes: 1 addition & 1 deletion api/docs/v2/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
# use rst_prolog to hold the subsitution
# update the apiLevel value whenever a new minor version is released
rst_prolog = f"""
.. |apiLevel| replace:: 2.15
.. |apiLevel| replace:: 2.16
.. |release| replace:: {release}
"""

Expand Down
6 changes: 3 additions & 3 deletions api/docs/v2/new_pipette.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ If you're writing a protocol that uses the Flex Gripper, you might think that th
Loading a Flex 96-Channel Pipette
---------------------------------

This code sample loads the Flex 96-Channel Pipette. Because of its size, the Flex 96-Channel Pipette requires the left *and* right pipette mounts. You cannot use this pipette with 1- or 8-Channel Pipette in the same protocol or when these instruments are attached to the robot. To load the 96-Channel Pipette, specify its position as ``mount='left'`` as shown here:
This code sample loads the Flex 96-Channel Pipette. Because of its size, the Flex 96-Channel Pipette requires the left *and* right pipette mounts. You cannot use this pipette with 1- or 8-Channel Pipette in the same protocol or when these instruments are attached to the robot. When loading the 96-Channel Pipette, you can omit the ``mount`` argument from ``load_instrument()`` as shown here:

.. code-block:: python
def run(protocol: protocol_api.ProtocolContext):
left = protocol.load_instrument(
instrument_name='flex_96channel_1000', mount='left')
pipette = protocol.load_instrument(
instrument_name='flex_96channel_1000')
.. versionadded:: 2.15

Expand Down
32 changes: 30 additions & 2 deletions api/docs/v2/versioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ The maximum supported API version for your robot is listed in the Opentrons App

If you upload a protocol that specifies a higher API level than the maximum supported, your robot won't be able to analyze or run your protocol. You can increase the maximum supported version by updating your robot software and Opentrons App.

Opentrons robots running the latest software (7.0.0) support the following version ranges:
Opentrons robots running the latest software (7.1.0) support the following version ranges:

* **Flex:** version 2.15.
* **Flex:** version 2.15–|apiLevel|.
* **OT-2:** versions 2.0–|apiLevel|.


Expand All @@ -82,6 +82,8 @@ This table lists the correspondence between Protocol API versions and robot soft
+-------------+------------------------------+
| API Version | Introduced in Robot Software |
+=============+==============================+
| 2.16 | 7.1.0 |
+-------------+------------------------------+
| 2.15 | 7.0.0 |
+-------------+------------------------------+
| 2.14 | 6.3.0 |
Expand Down Expand Up @@ -122,6 +124,32 @@ This table lists the correspondence between Protocol API versions and robot soft
Changes in API Versions
=======================

Version 2.16
------------

This version introduces new features for Flex and adds and improves methods for aspirating and dispensing. Note that when updating Flex protocols to version 2.16, you *must* load a trash container before dropping tips.

- New features

- Use :py:meth:`.configure_nozzle_layout` to pick up a single column of tips with the 96-channel pipette. See :ref:`Partial Tip Pickup <partial-tip-pickup>`.
- Specify the trash containers attached to your Flex with :py:meth:`.load_waste_chute` and :py:meth:`.load_trash_bin`.
- Dispense, blow out, drop tips, and dispose labware in the waste chute. Disposing labware requires the gripper and calling :py:meth:`.move_labware` with ``use_gripper=True``.
- Perform actions in staging area slots by referencing slots A4 through D4. See :ref:`deck-slots`.
- Explicitly command a pipette to :py:meth:`.prepare_to_aspirate`. The API usually prepares pipettes to aspirate automatically, but this is useful for certain applications, like pre-wetting routines.

- Improved features

- :py:meth:`.aspirate`, :py:meth:`.dispense`, and :py:meth:`.mix` will not move any liquid when called with ``volume=0``.

- Other changes

- :py:obj:`.ProtocolContext.fixed_trash` and :py:obj:`.InstrumentContext.trash_container` now return :py:class:`.TrashBin` objects instead of :py:class:`.Labware` objects.
- Flex will no longer automatically drop tips in the trash at the end of a protocol. You can add a :py:meth:`.drop_tip()` command to your protocol or use the Opentrons App to drop the tips.

- Known issues

- It's possible to load a Thermocycler and then load another item in slot A1. Don't do this, as it could lead to unexpected pipetting behavior and crashes.

Version 2.15
------------

Expand Down
1 change: 1 addition & 0 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Welcome to the v7.1.0 release of the Opentrons robot software! This release incl

### Improved Features

- The Ethernet port on Flex now supports direct connection to a computer.
- Improves aspirate, dispense, and mix behavior with volumes set to zero.
- The `opentrons_simulate` command-line tool now works with all Python API versions.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,6 @@ def remove_tip(self) -> None:
Remove the tip from the pipette (effectively updates the pipette's
critical point)
"""
assert self.has_tip
self._has_tip = False
self._current_tip_length = 0.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,6 @@ def remove_tip(self) -> None:
Remove the tip from the pipette (effectively updates the pipette's
critical point)
"""
assert self.has_tip_length
self._current_tip_length = 0.0
self._has_tip_length = False

Expand Down
9 changes: 9 additions & 0 deletions api/src/opentrons/hardware_control/nozzle_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ def xy_center_offset(self) -> Point:
difference[0] / 2, difference[1] / 2, 0
)

@property
def y_center_offset(self) -> Point:
"""The position in the center of the primary column of the map."""
front_left = next(reversed(list(self.rows.values())))[0]
difference = self.map_store[front_left] - self.map_store[self.back_left]
return self.map_store[self.back_left] + Point(0, difference[1] / 2, 0)

@property
def front_nozzle_offset(self) -> Point:
"""The offset for the front_left nozzle."""
Expand Down Expand Up @@ -319,6 +326,8 @@ def critical_point_with_tip_length(
) -> Point:
if cp_override == CriticalPoint.XY_CENTER:
current_nozzle = self._current_nozzle_configuration.xy_center_offset
elif cp_override == CriticalPoint.Y_CENTER:
current_nozzle = self._current_nozzle_configuration.y_center_offset
elif cp_override == CriticalPoint.FRONT_NOZZLE:
current_nozzle = self._current_nozzle_configuration.front_nozzle_offset
else:
Expand Down
2 changes: 1 addition & 1 deletion api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ async def _configure_instruments(self) -> None:
"""Configure instruments"""
await self.set_gantry_load(self._gantry_load_from_instruments())
await self.refresh_positions()
await self.reset_tip_detectors()
await self.reset_tip_detectors(False)

async def reset_tip_detectors(
self,
Expand Down
10 changes: 10 additions & 0 deletions api/src/opentrons/hardware_control/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,16 @@ class CriticalPoint(enum.Enum):
back calibration pin slot.
"""

Y_CENTER = enum.auto()
"""
Y_CENTER means the critical point under consideration is at the same X
coordinate as the default nozzle point (i.e. TIP | NOZZLE | FRONT_NOZZLE)
but halfway in between the Y axis bounding box of the pipette - it is the
XY center of the first column in the pipette. It's really only relevant for
the 96; it will produce the same position as XY_CENTER on an eight or one
channel pipette.
"""


class ExecutionState(enum.Enum):
RUNNING = enum.auto()
Expand Down
22 changes: 22 additions & 0 deletions api/src/opentrons/protocol_api/core/engine/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,28 @@ def get_nozzle_configuration(self) -> NozzleConfigurationType:
self._pipette_id
)

def is_tip_tracking_available(self) -> bool:
primary_nozzle = self._engine_client.state.pipettes.get_primary_nozzle(
self._pipette_id
)
if self.get_nozzle_configuration() == NozzleConfigurationType.FULL:
return True
else:
if self.get_channels() == 96:
# SINGLE configuration with H12 nozzle is technically supported by the
# current tip tracking implementation but we don't do any deck conflict
# checks for it, so we won't provide full support for it yet.
return (
self.get_nozzle_configuration() == NozzleConfigurationType.COLUMN
and primary_nozzle == "A12"
)
if self.get_channels() == 8:
return (
self.get_nozzle_configuration() == NozzleConfigurationType.SINGLE
and primary_nozzle == "H1"
)
return False

def set_flow_rate(
self,
aspirate: Optional[float] = None,
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/protocol_api/core/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,5 +274,9 @@ def configure_nozzle_layout(
"""
...

def is_tip_tracking_available(self) -> bool:
"""Return whether auto tip tracking is available for the pipette's current nozzle configuration."""
...


InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any])
Original file line number Diff line number Diff line change
Expand Up @@ -547,3 +547,7 @@ def configure_nozzle_layout(
def get_active_channels(self) -> int:
"""This will never be called because it was added in API 2.16."""
assert False, "get_active_channels only supported in API 2.16 & later"

def is_tip_tracking_available(self) -> bool:
# Tip tracking is always available in legacy context
return True
Original file line number Diff line number Diff line change
Expand Up @@ -465,3 +465,7 @@ def configure_nozzle_layout(
def get_active_channels(self) -> int:
"""This will never be called because it was added in API 2.16."""
assert False, "get_active_channels only supported in API 2.16 & later"

def is_tip_tracking_available(self) -> bool:
# Tip tracking is always available in legacy context
return True
26 changes: 18 additions & 8 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from opentrons_shared_data.errors.exceptions import (
CommandPreconditionViolated,
CommandParameterLimitViolated,
UnexpectedTipRemovalError,
)
from opentrons.legacy_broker import LegacyBroker
from opentrons.hardware_control.dev_types import PipetteDict
Expand All @@ -26,7 +27,6 @@
requires_version,
APIVersionError,
)
from opentrons_shared_data.errors.exceptions import UnexpectedTipRemovalError

from .core.common import InstrumentCore, ProtocolCore
from .core.engine import ENGINE_CORE_API_VERSION
Expand Down Expand Up @@ -860,6 +860,14 @@ def pick_up_tip(
)

if location is None:
if not self._core.is_tip_tracking_available():
raise CommandPreconditionViolated(
"Automatic tip tracking is not available for the current pipette"
" nozzle configuration. We suggest switching to a configuration"
" that supports automatic tip tracking or specifying the exact tip"
" to pick up."
)

tip_rack, well = labware.next_available_tip(
starting_tip=self.starting_tip,
tip_racks=self.tip_racks,
Expand Down Expand Up @@ -950,7 +958,7 @@ def drop_tip(
See :ref:`pipette-drop-tip` for examples.
If no location is passed (e.g. ``pipette.drop_tip()``), the pipette will drop
the attached tip into its default :py:attr:`trash_container`.
the attached tip into its :py:attr:`trash_container`.
Starting with API version 2.15, if the trash container is the default fixed
trash, the API will instruct the pipette to drop tips in different locations
Expand Down Expand Up @@ -1530,12 +1538,14 @@ def trash_container(self) -> Union[labware.Labware, TrashBin, WasteChute]:
This is the property used to determine where to drop tips and blow out liquids
when calling :py:meth:`drop_tip` or :py:meth:`blow_out` without arguments.
On a Flex running a protocol with API version 2.16 or higher, ``trash_container`` is
the first ``TrashBin`` or ``WasteChute`` object loaded in the protocol.
On a Flex running a protocol with API version 2.15, ``trash_container`` is
a single-well fixed trash labware in slot D3.
On a an OT-2, ``trash_container`` is always a single-well fixed trash labware
in slot 12.
You can set this to a :py:obj:`Labware`, :py:obj:`TrashBin`, or :py:obj:`WasteChute`.
The default value depends on the robot type and API version:
- :py:obj:`ProtocolContext.fixed_trash`, if it exists.
- Otherwise, the first item previously loaded with
:py:obj:`ProtocolContext.load_trash_bin()` or
:py:obj:`ProtocolContext.load_waste_chute()`.
.. versionchanged:: 2.16
Added support for ``TrashBin`` and ``WasteChute`` objects.
Expand Down
37 changes: 22 additions & 15 deletions api/src/opentrons/protocol_api/protocol_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ def loaded_modules(self) -> Dict[int, ModuleTypes]:
def load_instrument(
self,
instrument_name: str,
mount: Union[Mount, str],
mount: Union[Mount, str, None] = None,
tip_racks: Optional[List[Labware]] = None,
replace: bool = False,
) -> InstrumentContext:
Expand All @@ -831,15 +831,16 @@ def load_instrument(
ensure that the correct instrument is attached in the specified
location.
:param str instrument_name: The name of the instrument model, or a
prefix. For instance, 'p10_single' may be
used to request a P10 single regardless of
the version.
:param mount: The mount in which this instrument should be attached.
:param str instrument_name: Which instrument you want to load. See :ref:`new-pipette-models`
for the valid values.
:param mount: The mount where this instrument should be attached.
This can either be an instance of the enum type
:py:class:`.types.Mount` or one of the strings `'left'`
and `'right'`.
:type mount: types.Mount or str
:py:class:`.types.Mount` or one of the strings ``"left"``
or ``"right"``. If you're loading a Flex 96-Channel Pipette
(``instrument_name="flex_96channel_1000"``), you can leave this unspecified,
since it always occupies both mounts; if you do specify a value, it will be
ignored.
:type mount: types.Mount or str or ``None``
:param tip_racks: A list of tip racks from which to pick tips if
:py:meth:`.InstrumentContext.pick_up_tip` is called
without arguments.
Expand All @@ -850,9 +851,11 @@ def load_instrument(
"""
instrument_name = validation.ensure_lowercase_name(instrument_name)
checked_instrument_name = validation.ensure_pipette_name(instrument_name)
is_96_channel = checked_instrument_name == PipetteNameType.P1000_96
checked_mount = validation.ensure_mount_for_pipette(
mount, checked_instrument_name
)

checked_mount = Mount.LEFT if is_96_channel else validation.ensure_mount(mount)
is_96_channel = checked_instrument_name == PipetteNameType.P1000_96

tip_racks = tip_racks or []

Expand Down Expand Up @@ -1052,12 +1055,16 @@ def deck(self) -> Deck:
@property # type: ignore
@requires_version(2, 0)
def fixed_trash(self) -> Union[Labware, TrashBin]:
"""The trash fixed to slot 12 of the robot deck.
"""The trash fixed to slot 12 of an OT-2's deck.
In API version 2.15 and earlier, the fixed trash is a :py:class:`.Labware` object with one well. Access it like labware in your protocol. For example, ``protocol.fixed_trash['A1']``.
In API version 2.15 only, Flex protocols have a fixed trash in slot A3.
In API Versions prior to 2.16 it has one well and should be accessed like labware in your protocol.
e.g. ``protocol.fixed_trash['A1']``
In API version 2.16 and later, the fixed trash only exists in OT-2 protocols. It is a :py:class:`.TrashBin` object, which doesn't have any wells. Trying to access ``fixed_trash`` in a Flex protocol will raise an error. See :ref:`configure-trash-bin` for details on using the movable trash in Flex protocols.
In API Version 2.16 and above it returns a Trash fixture for OT-2 Protocols.
.. versionchanged:: 2.16
Returns a :py:class:`.TrashBin` object.
"""
if self._api_version >= APIVersion(2, 16):
if self._core.robot_type == "OT-3 Standard":
Expand Down
23 changes: 21 additions & 2 deletions api/src/opentrons/protocol_api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,27 @@ class InvalidTrashBinLocationError(ValueError):
"""An error raised when attempting to load trash bins in invalid slots."""


def ensure_mount(mount: Union[str, Mount]) -> Mount:
def ensure_mount_for_pipette(
mount: Union[str, Mount, None], pipette: PipetteNameType
) -> Mount:
"""Ensure that an input value represents a valid mount, and is valid for the given pipette."""
if pipette == PipetteNameType.P1000_96:
# Always validate the raw mount input, even if the pipette is a 96-channel and we're not going
# to use the mount value.
if mount is not None:
_ensure_mount(mount)
# Internal layers treat the 96-channel as being on the left mount.
return Mount.LEFT
else:
if mount is None:
raise InvalidPipetteMountError(
f"You must specify a left or right mount to load {pipette.value}."
)
else:
return _ensure_mount(mount)


def _ensure_mount(mount: Union[str, Mount]) -> Mount:
"""Ensure that an input value represents a valid Mount."""
if mount in [Mount.EXTENSION, "extension"]:
# This would cause existing protocols that might be iterating over mount types
Expand Down Expand Up @@ -274,7 +294,6 @@ def ensure_module_model(load_name: str) -> ModuleModel:
def ensure_and_convert_trash_bin_location(
deck_slot: Union[int, str], api_version: APIVersion, robot_type: RobotType
) -> str:

"""Ensure trash bin load location is valid.
Also, convert the deck slot to a valid trash bin addressable area.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ class MoveToAddressableAreaParams(PipetteIdMixin, MovementMixin):
AddressableOffsetVector(x=0, y=0, z=0),
description="Relative offset of addressable area to move pipette's critical point.",
)
stayAtHighestPossibleZ: bool = Field(
False,
description=(
"If `true`, the pipette will retract to its highest possible height"
" and stay there instead of descending to the destination."
" `minimumZHeight` will be ignored."
),
)


class MoveToAddressableAreaResult(DestinationPositionResult):
Expand Down Expand Up @@ -93,6 +101,7 @@ async def execute(
force_direct=params.forceDirect,
minimum_z_height=params.minimumZHeight,
speed=params.speed,
stay_at_highest_possible_z=params.stayAtHighestPossibleZ,
)

return MoveToAddressableAreaResult(position=DeckPoint(x=x, y=y, z=z))
Expand Down
Loading

0 comments on commit e3c3bf8

Please sign in to comment.