Skip to content

Commit

Permalink
Merge branch 'edge' into tip_tracking_for_alternative_configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
CaseyBatten authored Mar 6, 2024
2 parents 149f6c5 + d6d9416 commit e43c5db
Show file tree
Hide file tree
Showing 442 changed files with 9,820 additions and 2,230 deletions.
4 changes: 2 additions & 2 deletions api-client/src/calibration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface PipOffsetDeletionParams {

export interface TipLengthDeletionParams {
calType: 'tipLength'
tiprack_hash: string
tiprack_uri: string
pipette_id: string
}
export type DeleteCalRequestParams =
Expand Down Expand Up @@ -93,7 +93,7 @@ export interface TipLengthCalibration {
source: CalibrationSourceType
status: IndividualCalibrationHealthStatus
id: string
uri?: string | null
uri: string
}

export interface AllTipLengthCalibrations {
Expand Down
10 changes: 7 additions & 3 deletions api/docs/v2/basic_commands/liquids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ To dispense liquid from a pipette tip, call the :py:meth:`.InstrumentContext.dis
pipette.dispense(200, plate["B1"])
.. note::
In API version 2.16 and earlier, you could pass a ``volume`` argument to ``dispense()`` greater than what was aspirated into the pipette. In this case, the API would ignore ``volume`` and dispense the pipette's :py:obj:`~.InstrumentContext.current_volume`. The robot *would not* move the plunger lower as a result.

In version 2.17 and later, passing such values raises an error.

To move the plunger a small extra amount, add a :ref:`push out <push-out-dispense>`. Or to move it a large amount, use :ref:`blow out <blow-out>`.

If the pipette doesn’t move, you can specify an additional dispense action without including a location. To demonstrate, this code snippet pauses the protocol, automatically resumes it, and dispense a second time from location B1.

.. code-block:: python
Expand Down Expand Up @@ -129,9 +136,6 @@ For example, this dispense action moves the plunger the equivalent of an additio

.. versionadded:: 2.15

.. note::
In version 7.0.2 and earlier of the robot software, you could accomplish a similar result by dispensing a volume greater than what was aspirated into the pipette. In version 7.1.0 and later, the API will return an error. Calculate the difference between the two amounts and use that as the value of ``push_out``.

.. _new-blow-out:

.. _blow-out:
Expand Down
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.16
.. |apiLevel| replace:: 2.17
.. |release| replace:: {release}
"""

Expand Down
3 changes: 2 additions & 1 deletion api/docs/v2/new_protocol_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ Labware
signatures, since users should never construct these directly.
.. autoclass:: opentrons.protocol_api.TrashBin()
:members:

.. autoclass:: opentrons.protocol_api.WasteChute()

:members:

Wells and Liquids
=================
Expand Down
13 changes: 8 additions & 5 deletions api/docs/v2/versioning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ 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.1.0) support the following version ranges:
Opentrons robots running the latest software (7.2.0) support the following version ranges:

* **Flex:** version 2.15–|apiLevel|.
* **OT-2:** versions 2.0–|apiLevel|.
Expand All @@ -84,6 +84,8 @@ This table lists the correspondence between Protocol API versions and robot soft
+-------------+------------------------------+
| API Version | Introduced in Robot Software |
+=============+==============================+
| 2.17 | 7.2.0 |
+-------------+------------------------------+
| 2.16 | 7.1.0 |
+-------------+------------------------------+
| 2.15 | 7.0.0 |
Expand Down Expand Up @@ -126,6 +128,11 @@ This table lists the correspondence between Protocol API versions and robot soft
Changes in API Versions
=======================

Version 2.17
------------

- :py:meth:`.dispense` now raises an error if you try to dispense more than :py:obj:`.InstrumentContext.current_volume`.

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

Expand All @@ -147,10 +154,6 @@ This version introduces new features for Flex and adds and improves methods for

- :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
31 changes: 21 additions & 10 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,34 @@ log][]. For a list of currently known issues, please see the [Opentrons issue tr

---

## Opentrons Robot Software Changes in [!!EDIT ME WITH THE ACTUAL NUMBER OF THE NEXT RELEASE!!]
## Opentrons Robot Software Changes in 7.2.0

### HTTP API
Welcome to the v7.2.0 release of the Opentrons robot software!

- In the `/runs/commands`, `/maintenance_runs/commands`, and `/protocols` endpoints, the `dispense` command will now return an error if you try to dispense more than you've aspirated, instead of silently clamping.
- The `/notifications/subscribe` WebSocket endpoint has been removed. See https://github.com/Opentrons/opentrons/pull/14280 for details.
- The `/runs/commands` endpoints are significantly faster when you request a small number of commands from a stored run.
This update may take longer than usual if your robot has a lot of long protocols and runs stored on it. Allow *approximately 20 minutes* for your robot to restart. This delay will only happen once.

### Other Changes
If you don't care about preserving your labware offsets and run history, you can avoid the delay by clearing your runs and protocols before starting this update. Go to **Robot Settings** > **Device Reset** and select **Clear protocol run history**.

### Improved Features

- The robot software now runs Python 3.10. Many built-in Python packages were updated to match. If you have installed your own Python packages on the robot, re-install them to ensure compatibility.
- Added error handling when dispensing. The `/runs/commands`, `/maintenance_runs/commands`, and `/protocols` HTTP API endpoints now return an error if you try to dispense more than you've aspirated.
- Improved performance of the `/runs/commands` endpoints. They are now significantly faster when requesting a small number of commands from a stored run.

- The `notify_server` Python package has been removed. See https://github.com/Opentrons/opentrons/pull/14280 for details.
### Bug Fixes

- The OT-2 now consistently applies tip length calibration. There used to be a height discrepancy between Labware Position Check and protocol runs. If you previously compensated for the inconsistent pipette height with labware offsets, re-run Labware Position Check to avoid pipette crashes.
- The OT-2 now accurately calculates the position of the Thermocycler. If you previously compensated for the incorrect position with labware offsets, re-run Labware Position Check to avoid pipette crashes.
- The Flex Gripper will no longer pick up large labware that could collide with tips held by an adjoining pipette.
- Flex now properly configures itself when connected by Ethernet directly to a computer.

### Upgrade Notes
### Removals

This update may take longer than usual if your robot has a lot of long protocols and runs stored on it. Allow **approximately 25 minutes** for your robot to restart. This delay will only happen once.
- Removed the `notify_server` Python package and `/notifications/subscribe` WebSocket endpoint, as they were never fully used. (See pull request [#14280](https://github.com/Opentrons/opentrons/pull/14280) for details.)

### Known Issues

If you don't care about preserving your labware offsets and run history, you can avoid the delay. Clear your runs and protocols before starting this update. Go to **Robot Settings** > **Device Reset** and select **Clear protocol run history**.
- Downgrading an OT-2 to an earlier software version will delete tip length calibrations created with version 7.2.0. If you need to downgrade, re-run all pipette calibrations afterward.

---

Expand Down
8 changes: 6 additions & 2 deletions api/src/opentrons/calibration_storage/ot2/models/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ class TipLengthModel(BaseModel):
default_factory=CalibrationStatus,
description="The status of the calibration data.",
)
uri: typing.Union[LabwareUri, Literal[""]] = Field(
..., description="The tiprack URI associated with the tip length data."
# Old data may have a `uri` field, replaced later by `definitionHash`.
# uri: typing.Union[LabwareUri, Literal[""]] = Field(
# ..., description="The tiprack URI associated with the tip length data."
# )
definitionHash: str = Field(
..., description="The tiprack hash associated with the tip length data."
)

@validator("tipLength")
Expand Down
78 changes: 51 additions & 27 deletions api/src/opentrons/calibration_storage/ot2/tip_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from opentrons import config

from .. import file_operators as io, helpers, types as local_types
from opentrons_shared_data.pipette.dev_types import LabwareUri

from opentrons.protocols.api_support.constants import OPENTRONS_NAMESPACE
from opentrons.util.helpers import utc_now
Expand All @@ -22,9 +23,9 @@
# Get Tip Length Calibration


def _conver_tip_length_model_to_dict(
to_dict: typing.Dict[str, v1.TipLengthModel]
) -> typing.Dict[str, typing.Any]:
def _convert_tip_length_model_to_dict(
to_dict: typing.Dict[LabwareUri, v1.TipLengthModel]
) -> typing.Dict[LabwareUri, typing.Any]:
# This is a workaround since pydantic doesn't have a nice way to
# add encoders when converting to a dict.
dict_of_tip_lengths = {}
Expand All @@ -35,17 +36,23 @@ def _conver_tip_length_model_to_dict(

def tip_lengths_for_pipette(
pipette_id: str,
) -> typing.Dict[str, v1.TipLengthModel]:
) -> typing.Dict[LabwareUri, v1.TipLengthModel]:
tip_lengths = {}
try:
tip_length_filepath = config.get_tip_length_cal_path() / f"{pipette_id}.json"
all_tip_lengths_for_pipette = io.read_cal_file(tip_length_filepath)
for tiprack, data in all_tip_lengths_for_pipette.items():
for tiprack_identifier, data in all_tip_lengths_for_pipette.items():
# We normally key these calibrations by their tip rack URI,
# but older software had them keyed by their tip rack hash.
# Migrate from the old format, if necessary.
if "/" not in tiprack_identifier:
data["definitionHash"] = tiprack_identifier
tiprack_identifier = data.pop("uri")
try:
tip_lengths[tiprack] = v1.TipLengthModel(**data)
tip_lengths[LabwareUri(tiprack_identifier)] = v1.TipLengthModel(**data)
except (json.JSONDecodeError, ValidationError):
log.warning(
f"Tip length calibration is malformed for {tiprack} on {pipette_id}"
f"Tip length calibration is malformed for {tiprack_identifier} on {pipette_id}"
)
pass
return tip_lengths
Expand All @@ -64,10 +71,10 @@ def load_tip_length_calibration(
:param pip_id: pipette you are using
:param definition: full definition of the tiprack
"""
labware_hash = helpers.hash_labware_def(definition)
labware_uri = helpers.uri_from_definition(definition)
load_name = definition["parameters"]["loadName"]
try:
return tip_lengths_for_pipette(pip_id)[labware_hash]
return tip_lengths_for_pipette(pip_id)[labware_uri]
except KeyError as e:
raise local_types.TipLengthCalNotFound(
f"Tip length of {load_name} has not been "
Expand All @@ -89,16 +96,16 @@ def get_all_tip_length_calibrations() -> typing.List[v1.TipLengthCalibration]:
if filepath.stem == "index":
continue
tip_lengths = tip_lengths_for_pipette(filepath.stem)
for tiprack_hash, tip_length in tip_lengths.items():
for tiprack_uri, tip_length in tip_lengths.items():
all_tip_lengths_available.append(
v1.TipLengthCalibration(
pipette=filepath.stem,
tiprack=tiprack_hash,
tiprack=tip_length.definitionHash,
tipLength=tip_length.tipLength,
lastModified=tip_length.lastModified,
source=tip_length.source,
status=tip_length.status,
uri=tip_length.uri,
uri=tiprack_uri,
)
)
return all_tip_lengths_available
Expand Down Expand Up @@ -129,28 +136,45 @@ def get_custom_tiprack_definition_for_tlc(labware_uri: str) -> "LabwareDefinitio
# Delete Tip Length Calibration


def delete_tip_length_calibration(tiprack: str, pipette_id: str) -> None:
def delete_tip_length_calibration(
pipette_id: str,
tiprack_uri: typing.Optional[LabwareUri] = None,
tiprack_hash: typing.Optional[str] = None,
) -> None:
"""
Delete tip length calibration based on tiprack hash and
pipette serial number
Delete tip length calibration based on an optional tiprack uri or
tiprack hash and pipette serial number.
:param tiprack: tiprack hash
:param tiprack_uri: tiprack uri
:param tiprack_hash: tiprack uri
:param pipette: pipette serial number
"""
tip_lengths = tip_lengths_for_pipette(pipette_id)

if tiprack in tip_lengths:
tip_length_dir = config.get_tip_length_cal_path()
if tiprack_uri in tip_lengths:
# maybe make modify and delete same file?
del tip_lengths[tiprack]
tip_length_dir = config.get_tip_length_cal_path()
del tip_lengths[tiprack_uri]

if tip_lengths:
dict_of_tip_lengths = _convert_tip_length_model_to_dict(tip_lengths)
io.save_to_file(tip_length_dir, pipette_id, dict_of_tip_lengths)
else:
io.delete_file(tip_length_dir / f"{pipette_id}.json")
elif tiprack_hash and any(tiprack_hash in v.dict() for v in tip_lengths.values()):
# NOTE this is for backwards compatibilty only
# TODO delete this check once the tip_length DELETE router
# no longer depends on a tiprack hash
for k, v in tip_lengths.items():
if tiprack_hash in v.dict():
tip_lengths.pop(k)
if tip_lengths:
dict_of_tip_lengths = _conver_tip_length_model_to_dict(tip_lengths)
dict_of_tip_lengths = _convert_tip_length_model_to_dict(tip_lengths)
io.save_to_file(tip_length_dir, pipette_id, dict_of_tip_lengths)
else:
io.delete_file(tip_length_dir / f"{pipette_id}.json")
else:
raise local_types.TipLengthCalNotFound(
f"Tip length for hash {tiprack} has not been "
f"Tip length for uri {tiprack_uri} and hash {tiprack_hash} has not been "
f"calibrated for this pipette: {pipette_id} and cannot"
"be loaded"
)
Expand All @@ -176,7 +200,7 @@ def create_tip_length_data(
cal_status: typing.Optional[
typing.Union[local_types.CalibrationStatus, v1.CalibrationStatus]
] = None,
) -> typing.Dict[str, v1.TipLengthModel]:
) -> typing.Dict[LabwareUri, v1.TipLengthModel]:
"""
Function to correctly format tip length data.
Expand All @@ -197,13 +221,13 @@ def create_tip_length_data(
lastModified=utc_now(),
source=local_types.SourceType.user,
status=cal_status_model,
uri=labware_uri,
definitionHash=labware_hash,
)

if not definition.get("namespace") == OPENTRONS_NAMESPACE:
_save_custom_tiprack_definition(labware_uri, definition)

data = {labware_hash: tip_length_data}
data = {labware_uri: tip_length_data}
return data


Expand All @@ -220,7 +244,7 @@ def _save_custom_tiprack_definition(

def save_tip_length_calibration(
pip_id: str,
tip_length_cal: typing.Dict[str, v1.TipLengthModel],
tip_length_cal: typing.Dict[LabwareUri, v1.TipLengthModel],
) -> None:
"""
Function used to save tip length calibration to file.
Expand All @@ -235,5 +259,5 @@ def save_tip_length_calibration(

all_tip_lengths.update(tip_length_cal)

dict_of_tip_lengths = _conver_tip_length_model_to_dict(all_tip_lengths)
dict_of_tip_lengths = _convert_tip_length_model_to_dict(all_tip_lengths)
io.save_to_file(tip_length_dir_path, pip_id, dict_of_tip_lengths)
3 changes: 1 addition & 2 deletions api/src/opentrons/commands/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from . import types as command_types

from opentrons.types import Location
from opentrons.protocol_api._trash_bin import TrashBin
from opentrons.protocol_api._waste_chute import WasteChute
from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute

if TYPE_CHECKING:
from opentrons.protocol_api import InstrumentContext
Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/commands/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

from opentrons.protocol_api.labware import Well, Labware
from opentrons.protocol_api.module_contexts import ModuleContext
from opentrons.protocol_api._trash_bin import TrashBin
from opentrons.protocol_api._waste_chute import WasteChute
from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute
from opentrons.protocol_api._types import OffDeckType
from opentrons.types import Location, DeckLocation

Expand Down
3 changes: 1 addition & 2 deletions api/src/opentrons/commands/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
if TYPE_CHECKING:
from opentrons.protocol_api import InstrumentContext
from opentrons.protocol_api.labware import Well
from opentrons.protocol_api._trash_bin import TrashBin
from opentrons.protocol_api._waste_chute import WasteChute
from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute

from opentrons.types import Location

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ async def watch(self, loop: asyncio.AbstractEventLoop) -> None:
def axis_bounds(self) -> OT3AxisMap[Tuple[float, float]]:
"""Get the axis bounds."""
# TODO (AL, 2021-11-18): The bounds need to be defined
phony_bounds = (0, 10000)
phony_bounds = (0, 500)
return {
Axis.Z_L: phony_bounds,
Axis.Z_R: phony_bounds,
Expand Down
4 changes: 3 additions & 1 deletion api/src/opentrons/hardware_control/dev_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from opentrons_shared_data.pipette.pipette_definition import (
PipetteConfigurations,
SupportedTipsDefinition,
PipetteBoundingBoxOffsetDefinition,
)
from opentrons_shared_data.gripper import (
GripperModel,
Expand Down Expand Up @@ -95,7 +96,8 @@ class PipetteDict(InstrumentDict):
has_tip: bool
default_push_out_volume: Optional[float]
supported_tips: Dict[PipetteTipType, SupportedTipsDefinition]
current_nozzle_map: NozzleMap # spp: why was this Optional?
pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition
current_nozzle_map: NozzleMap


class PipetteStateDict(TypedDict):
Expand Down
Loading

0 comments on commit e43c5db

Please sign in to comment.