Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
890c8da
feat(labware-library): Add images for new labware (#19365)
rclarke0 Aug 26, 2025
268c297
Merge back 'chore_release-8.6.0' into 'chore_release-ll-3.5.0', to pi…
ddcc4 Aug 26, 2025
363a797
fix(labware-library): Fix nest 12 well reservoir 22ml image (#19382)
rclarke0 Aug 27, 2025
bd6942e
Merge back 'chore_release-ll-3.5.0' into 'hold-for-8.6.1' (#19396)
ddcc4 Aug 29, 2025
ba6f211
feat(api): add version argument to protocol context get_liquid_class …
jbleon95 Aug 29, 2025
7799802
fix(shared-data): update lid def for mp geometry (#19407)
sfoster1 Aug 29, 2025
d0cda59
feat(api): Consistently allow specifying namespaces and versions of a…
SyntaxColoring Sep 2, 2025
b220c84
feat(shared-data): restore liquid class `water` v2 definitions (#19436)
ddcc4 Sep 5, 2025
67987f5
feat(app,labware-library): expose tough lid (#19487)
sfoster1 Sep 8, 2025
bf51207
Create the 'chore_release-8.7.0' branch with PRs queued from 'hold-fo…
ddcc4 Sep 10, 2025
ca57ccb
feat(app): split store/retrieve ER flows (#19516)
TamarZanzouri Sep 10, 2025
2e0de49
fix(app): add missing shuttle full screens (#19527)
TamarZanzouri Sep 11, 2025
da955a1
feat(robot-server,app): Show incompatible module banner for the Flex …
vegano1 Sep 11, 2025
6d4aaad
feat(api): improve gripper error checking (#19522)
sfoster1 Sep 11, 2025
7b14eb7
fix(app): text-align center for both desktop and odd in progress moda…
smb2268 Sep 11, 2025
345e296
fix(protocol-designer): do not null tiprack when updating pipette (#…
jerader Sep 11, 2025
57ffc6e
fix(app): always assign a liquid color if one is not passed in (#19534)
smb2268 Sep 11, 2025
74863f0
fix(app): display 96 channel cam in robot context (#19544)
sfoster1 Sep 11, 2025
95fb7c0
fix(app): fix retriving no liquid class values for QT (#19541)
jerader Sep 12, 2025
7caab4e
fix(app): allow the ability to change blowout settings (#19552)
jerader Sep 12, 2025
4cb37f6
fix(step-generation): add prepareToAspirate before airGapInPlace disp…
jerader Sep 12, 2025
d95b3b7
fix(app): increase QT versioning to 1.2.0 for liquid classes (#19557)
jerader Sep 12, 2025
93eecbd
Merge back 'chore_release-pd-8.5.4' into 'chore_release-8.7.0' (#19559)
ddcc4 Sep 12, 2025
fa99fe5
feat(app): add shuttle empty ER flow for store command (#19551)
TamarZanzouri Sep 13, 2025
f3ae9d4
fix(hardware-testing): Update the stacker qc protocol to use 384 labw…
vegano1 Sep 15, 2025
d6ebfa3
fix(app): Make sure all move labware maps have access to the correct …
smb2268 Sep 15, 2025
5c15a7b
fix(api): Implement coverage of column 4 modules in collision checkin…
CaseyBatten Sep 16, 2025
2918364
fix(protocol-designer): include stack traces for console events to Se…
ddcc4 Sep 16, 2025
95a1894
fix(app): fix offset and formToArg values in QT (#19555)
koji Sep 16, 2025
24c0cd9
feat(shared-data): create `ethanol_80/2.json` and `glycerol_50/2.json…
ddcc4 Sep 16, 2025
a2736db
fix(app): ER store shuttle empty full flow fixes (#19576)
TamarZanzouri Sep 16, 2025
4c689f8
fix(api): Ensure parent to lid stack is valid source for move lid act…
CaseyBatten Sep 16, 2025
af51464
fix(shared-data): Enabled lid stacking for corning 96 wellplate 360mL…
CaseyBatten Sep 16, 2025
281298a
feat(shared-data, protocol-designer, app): return v2 defs from getAll…
jerader Sep 16, 2025
463fcce
feat(app): add feature flag for protocol contents log (#19581)
koji Sep 16, 2025
48080b9
fix(app): Hide tiprack lids in error recovery deck maps (#19579)
smb2268 Sep 16, 2025
1b9c84b
Revert "fix(app): Hide tiprack lids in error recovery deck maps (#195…
smb2268 Sep 17, 2025
32c8d67
feat(shared-data): import preliminary liquid class v2 values for LV96…
ddcc4 Sep 17, 2025
b1a5e97
fix(app): shuttle empty error banner (#19593)
TamarZanzouri Sep 17, 2025
d1a6beb
fix(app): show disposal volume with distributes and not consolidates …
jerader Sep 18, 2025
452e2d0
fix(app,shared-data): Fix the position of a labware on a labware on a…
SyntaxColoring Sep 18, 2025
f2deefd
feat(app): enable py generation in QT (#19605)
jerader Sep 18, 2025
f54701a
Merge back 'chore_release-pd-8.5.4' into 'chore_release-8.7.0' (#19607)
ddcc4 Sep 18, 2025
440390e
feat(shared-data): more liquid class values v2 changes (#19615)
ddcc4 Sep 18, 2025
45183bc
fix(app): Quick Transfer settings overview style (#19614)
ncdiehl11 Sep 19, 2025
ec8d0d6
fix(protocol-designer): always show aspirate tip position button (#19…
ncdiehl11 Sep 19, 2025
4dba75f
fix(app): pass in LABWAREV2_DO_NOT_LIST into QT (#19617)
jerader Sep 19, 2025
35ed338
fix(app): fix tip positions in Quick Transfer (#19613)
ncdiehl11 Sep 19, 2025
62c5540
fix(step-generation): do not emit touchTip when blowout in touchTipDi…
jerader Sep 19, 2025
d245c51
fix(app): text fix for store shuttle empty ER error modal (#19621)
TamarZanzouri Sep 19, 2025
2769005
feat(app): update QT version to 2.0.0 to include py generation (#19610)
jerader Sep 19, 2025
ea6faa1
fix(protocol-designer): handle missing pipette in pipetteTiprackAssig…
ddcc4 Sep 19, 2025
d2d49ee
fix(app): blowout and disposal volume form fix (#19625)
jerader Sep 19, 2025
e1d0b2e
fix(protocol-designer): instrumentation to see why getMinXYDimension(…
ddcc4 Sep 19, 2025
c0ccd2e
fix(app): fix air gap display issue in aspirate (#19601)
koji Sep 19, 2025
d5fec86
fix(protocol-designer): don't crash in volumeTooHigh() if tipRack is …
ddcc4 Sep 19, 2025
3fccb19
fix(api): correctly update and reset flow rates and restore pipette s…
jbleon95 Sep 19, 2025
66ef316
PD 8.5.4 release notes (#19633)
emilyburghardt Sep 19, 2025
8562600
Merge back 'chore_release-pd-8.5.4' into 'chore_release-8.7.0' (#19632)
ddcc4 Sep 19, 2025
b9ef76a
fix(step-generation): delay args should be False when duration is 0 (…
jerader Sep 22, 2025
3d0027f
chore: 8.7.0 alpha release notes (#19638)
emilyburghardt Sep 22, 2025
b1d9ad0
fix(app): refine when changing path to single in QT (#19646)
jerader Sep 22, 2025
6250ebc
fix(protocol-designer): fix logic for null and 0 zOffset values (#19650)
ncdiehl11 Sep 22, 2025
0fbb50c
fix(app): fix blowout flow rate value in generated qt (#19648)
koji Sep 23, 2025
e82e6f2
feat(protocol-designer): add release note for z position updates (#19…
ncdiehl11 Sep 23, 2025
9f0d479
fix(step-generation): ensure disposal + conditioning volume is aspira…
ncdiehl11 Sep 23, 2025
b153405
Merge back 'chore_release-pd-8.5.4' into 'chore_release-8.7.0' (#19659)
ddcc4 Sep 23, 2025
a99a337
feat(app): add DESIGNER_APPLICATION blob to end of QT protocols (#19667)
jerader Sep 24, 2025
d411b09
fix(app): fix disposal volume issues (#19630)
koji Sep 29, 2025
2131cb3
fix(protocol-designer): tiprack dependent field (#19692)
jerader Sep 30, 2025
150a819
fix(protocol-designer): 8.5.5 migration to migrate the wrong tiprack …
jerader Sep 30, 2025
5a6ee4f
fix(app): adding missing manual store and manual retrieve on ER (#19697)
TamarZanzouri Sep 30, 2025
7303417
refactor(protocol-designer): fix copy in 8.5.5 release notes (#19701)
jerader Oct 1, 2025
771c165
Merge remote-tracking branch 'origin/chore_release-pd-8.5.5' into HEAD
jerader Oct 1, 2025
f31cb53
fix(app): ER error details and text copy for shuttle full (#19706)
TamarZanzouri Oct 1, 2025
4a75419
fix(app): fix 96 channel 200ul case (#19700)
koji Oct 1, 2025
1e5b289
fix(api): Fix the absorbance reader lid pickup (#19699)
ryanthecoder Oct 1, 2025
bc9d0aa
fix(api): Use the get_histogram retry mechanism instead of the AsyncR…
vegano1 Oct 1, 2025
35ad527
fix(protocol-designer): fix wastechute and trashbin check for dispens…
koji Oct 2, 2025
f451fe5
chore(protocol-designer): release note for 8.5.6 hotfix (#19711)
ecormany Oct 2, 2025
3872867
Merge remote-tracking branch 'origin/chore_release-pd-8.5.6' into HEAD
jerader Oct 2, 2025
55d437a
fix(api): add stacking_offset_z default None to set_stored_labware_it…
vegano1 Oct 2, 2025
6287318
fix(api): fix my enum comparison (#19715)
ryanthecoder Oct 2, 2025
6ee528b
fix(api): set_stored_labware(): preserve prior behavior for lid/adapt…
SyntaxColoring Oct 3, 2025
95a88f7
fix(shared-data, app): Update millipore labware names (#19727)
rclarke0 Oct 6, 2025
b25c7db
fix(app): fix trash bin in LPC wizard of OT-2 (#19731)
koji Oct 6, 2025
2fbb3c3
fix(robot-server): Tolerate errors when reading individual protocols …
SyntaxColoring Oct 6, 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
33 changes: 25 additions & 8 deletions api/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,32 @@ By installing and using Opentrons software, you agree to the Opentrons End-User

---

## Opentrons Robot Software Changes in 8.7.0

Welcome to the v8.7.0 release of the Opentrons robot software! This release adds improvements and bug fixes.

### New Features

Use Opentrons Tough Universal Lids on compatible well plates and reservoirs.

### Improvements

Transfer aqueous, viscous, or volatile liquids with the Opentrons Flex 96-Channel Pipette (1–200 μL) to apply optimized, liquid class transfer behavior to volumes as low as 1 µL.

### Bug Fixes

- Default pipette flow rates are correctly applied in all liquid transfers. Use `configure_for_volume()` to clear customized pipette flow rates.
- The API no longer raises an error when partial tip pickup occurs in a slot adjacent to a loaded Flex Stacker Module.

## Opentrons Robot Software Changes in 8.6.0

Welcome to the v8.6.0 release of the Opentrons robot software! This release adds support for the Flex Stacker Module, along with other new features and improvements.

### New Features
### New Features

- Automate labware storage with the Flex Stacker Module. Use new commands like `retrieve()` and `store()` to move well plates, reservoirs, or Flex tip racks to and from the Stacker during a protocol.
- This release adds support for the Opentrons Flex 96-Channel Pipette (1–200 μL) to transfer as little as 1 µL in a protocol.
- Control individual robot motors, like the gantry, extension mount, or gripper, with new commands.
- Automate labware storage with the Flex Stacker Module. Use new commands like `retrieve()` and `store()` to move well plates, reservoirs, or Flex tip racks to and from the Stacker during a protocol.
- This release adds support for the Opentrons Flex 96-Channel Pipette (1–200 μL) to transfer as little as 1 µL in a protocol.
- Control individual robot motors, like the gantry, extension mount, or gripper, with new commands.

### Known Limitations

Expand Down Expand Up @@ -69,10 +86,10 @@ Welcome to the v8.4.0 release of the Opentrons robot software! This release incl

### New Features

- Use new ``transfer_liquid``, ``distribute_liquid``, and ``consolidate_liquid`` commands on Flex to optimize liquid handling based on Opentrons-verified liquid classes.
- Stack multiple Opentrons Tough Auto-Sealing Lids on the deck.
- Move Opentrons Tough Auto-Sealing Lids or remove tip rack lids with the Flex Gripper.
- Aspirate or dispense in a well based on the liquid meniscus.
- Use new `transfer_liquid`, `distribute_liquid`, and `consolidate_liquid` commands on Flex to optimize liquid handling based on Opentrons-verified liquid classes.
- Stack multiple Opentrons Tough Auto-Sealing Lids on the deck.
- Move Opentrons Tough Auto-Sealing Lids or remove tip rack lids with the Flex Gripper.
- Aspirate or dispense in a well based on the liquid meniscus.

### Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,10 @@ def __init__(
self._async_error_ack = async_error_ack.lower()

async def send_command(
self, command: CommandBuilder, retries: int = 0, timeout: Optional[float] = None
self,
command: CommandBuilder,
retries: int | None = None,
timeout: float | None = None,
) -> str:
"""
Send a command and return the response.
Expand All @@ -478,12 +481,12 @@ async def send_command(
"""
return await self.send_data(
data=command.build(),
retries=retries or self._number_of_retries,
retries=retries if retries is not None else self._number_of_retries,
timeout=timeout,
)

async def send_data(
self, data: str, retries: int = 0, timeout: Optional[float] = None
self, data: str, retries: int | None = None, timeout: float | None = None
) -> str:
"""
Send data and return the response.
Expand All @@ -501,7 +504,8 @@ async def send_data(
"timeout", timeout
):
return await self._send_data(
data=data, retries=retries or self._number_of_retries
data=data,
retries=retries if retries is not None else self._number_of_retries,
)

async def _send_data(self, data: str, retries: int = 0) -> str:
Expand All @@ -517,7 +521,6 @@ async def _send_data(self, data: str, retries: int = 0) -> str:
Raises: SerialException
"""
data_encode = data.encode()
retries = retries or self._number_of_retries

for retry in range(retries + 1):
log.debug(f"{self._name}: Write -> {data_encode!r}")
Expand Down
7 changes: 6 additions & 1 deletion api/src/opentrons/drivers/flex_stacker/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,12 @@ async def _get_tof_histogram_frame(
command = GCODE.GET_TOF_MEASUREMENT.build_command().add_element(sensor.name)
if resend:
command.add_element("R")
resp = await self._connection.send_command(command)

# Note: We DONT want to auto resend the request if it fails, because the
# firmware will send the next frame id instead of the current one missed.
# So lets set `retries=0` so we only send the frame once and we can
# use the retry mechanism of the `get_tof_histogram` method instead.
resp = await self._connection.send_command(command, retries=0)
return self.parse_get_tof_measurement(resp)

async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ def check_gripper_position_within_bounds(
max_allowed_grip_error: float,
hard_limit_lower: float,
hard_limit_upper: float,
disable_geometry_grip_check: bool = False,
) -> None:
...

Expand Down
38 changes: 25 additions & 13 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,9 +686,9 @@ def _build_move_node_axis_runner(
return (
MoveGroupRunner(
move_groups=[move_group],
ignore_stalls=True
if not self._feature_flags.stall_detection_enabled
else False,
ignore_stalls=(
True if not self._feature_flags.stall_detection_enabled else False
),
),
False,
)
Expand All @@ -712,9 +712,9 @@ def _build_move_gear_axis_runner(
return (
MoveGroupRunner(
move_groups=[tip_motor_move_group],
ignore_stalls=True
if not self._feature_flags.stall_detection_enabled
else False,
ignore_stalls=(
True if not self._feature_flags.stall_detection_enabled else False
),
),
True,
)
Expand Down Expand Up @@ -939,9 +939,9 @@ async def home_tip_motors(

runner = MoveGroupRunner(
move_groups=[move_group],
ignore_stalls=True
if not self._feature_flags.stall_detection_enabled
else False,
ignore_stalls=(
True if not self._feature_flags.stall_detection_enabled else False
),
)
try:
positions = await runner.run(can_messenger=self._messenger)
Expand Down Expand Up @@ -976,9 +976,9 @@ async def tip_action(
move_group = self._build_tip_action_group(origin, targets)
runner = MoveGroupRunner(
move_groups=[move_group],
ignore_stalls=True
if not self._feature_flags.stall_detection_enabled
else False,
ignore_stalls=(
True if not self._feature_flags.stall_detection_enabled else False
),
)
try:
positions = await runner.run(can_messenger=self._messenger)
Expand Down Expand Up @@ -1763,6 +1763,7 @@ def check_gripper_position_within_bounds(
max_allowed_grip_error: float,
hard_limit_lower: float,
hard_limit_upper: float,
disable_geometry_grip_check: bool = False,
) -> None:
"""
Check if the gripper is at the expected location.
Expand All @@ -1777,7 +1778,16 @@ def check_gripper_position_within_bounds(
expected_grip_width + grip_width_uncertainty_wider
)
current_gripper_position = jaw_width
if isclose(current_gripper_position, hard_limit_lower):
log.info(
f"Checking gripper position: current {jaw_width}; max error {max_allowed_grip_error}; hard limits {hard_limit_lower}, {hard_limit_upper}; expected {expected_gripper_position_min}, {expected_grip_width}, {expected_gripper_position_max}; uncertainty {grip_width_uncertainty_narrower}, {grip_width_uncertainty_wider}"
)
if (
isclose(current_gripper_position, hard_limit_lower)
# this odd check handles internal backlash that can lead the position to read as if
# the gripper has overshot its lower bound; this is physically impossible and an
# artifact of the gearing, so it always indicates a hard stop
or current_gripper_position < hard_limit_lower
):
raise FailedGripperPickupError(
message="Failed to grip: jaws all the way closed",
details={
Expand All @@ -1796,6 +1806,7 @@ def check_gripper_position_within_bounds(
if (
current_gripper_position - expected_gripper_position_min
< -max_allowed_grip_error
and not disable_geometry_grip_check
):
raise FailedGripperPickupError(
message="Failed to grip: jaws closed too far",
Expand All @@ -1809,6 +1820,7 @@ def check_gripper_position_within_bounds(
if (
current_gripper_position - expected_gripper_position_max
> max_allowed_grip_error
and not disable_geometry_grip_check
):
raise FailedGripperPickupError(
message="Failed to grip: jaws could not close far enough",
Expand Down
3 changes: 2 additions & 1 deletion api/src/opentrons/hardware_control/backends/ot3simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ def subsystems(self) -> Dict[SubSystem, SubSystemState]:
next_fw_version=1,
fw_update_needed=False,
current_fw_sha="simulated",
pcba_revision="A1",
pcba_revision="A1.0",
update_state=None,
)
for axis in self._present_axes
Expand Down Expand Up @@ -848,6 +848,7 @@ def check_gripper_position_within_bounds(
max_allowed_grip_error: float,
hard_limit_lower: float,
hard_limit_upper: float,
disable_geometry_grip_check: bool = False,
) -> None:
# This is a (pretty bad) simulation of the gripper actually gripping something,
# but it should work.
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 @@ -14,8 +14,9 @@
PipetteModel,
PipetteName,
ChannelCount,
PipetteTipType,
LiquidClasses,
)
from opentrons_shared_data.pipette.types import PipetteTipType
from opentrons_shared_data.pipette.pipette_definition import (
PipetteConfigurations,
SupportedTipsDefinition,
Expand Down Expand Up @@ -104,6 +105,7 @@ class PipetteDict(InstrumentDict):
plunger_positions: Dict[str, float]
shaft_ul_per_mm: float
available_sensors: AvailableSensorDefinition
volume_mode: LiquidClasses # LiquidClasses refer to volume mode in this context


class PipetteStateDict(TypedDict):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict:
"drop_tip": instr.plunger_positions.drop_tip,
}
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
result["volume_mode"] = instr.liquid_class_name
return cast(PipetteDict, result)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict:
}
result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm
result["available_sensors"] = instr.config.available_sensors
result["volume_mode"] = instr.liquid_class_name
return cast(PipetteDict, result)

@property
Expand Down
4 changes: 3 additions & 1 deletion api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,7 @@ def raise_error_if_gripper_pickup_failed(
expected_grip_width: float,
grip_width_uncertainty_wider: float,
grip_width_uncertainty_narrower: float,
disable_geometry_grip_check: bool = False,
) -> None:
"""Ensure that a gripper pickup succeeded.

Expand All @@ -1480,8 +1481,9 @@ def raise_error_if_gripper_pickup_failed(
grip_width_uncertainty_narrower,
gripper.jaw_width,
gripper.max_allowed_grip_error,
gripper.max_jaw_width,
gripper.min_jaw_width,
gripper.max_jaw_width,
disable_geometry_grip_check,
)

def gripper_jaw_can_home(self) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def raise_error_if_gripper_pickup_failed(
expected_grip_width: float,
grip_width_uncertainty_wider: float,
grip_width_uncertainty_narrower: float,
disable_geometry_grip_check: bool = False,
) -> None:
"""Ensure that a gripper pickup succeeded."""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""The versions of standard liquid classes that the Protocol API should load by default."""

from typing import TypeAlias
from opentrons.protocols.api_support.types import APIVersion


DefaultLiquidClassVersions: TypeAlias = dict[APIVersion, dict[str, int]]


# This:
#
# {
# APIVersion(2, 100): {
# "foo_liquid": 3,
# },
# APIVersion(2, 105): {
# "foo_liquid": 7
# }
# }
#
# Means this:
#
# apiLevels name Default liquid class version
# ---------------------------------------------------------------
# <2.100 foo_liquid 1
# >=2.100,<2.105 foo_liquid 3
# >=2.105 foo_liquid 7
# [any] [anything else] 1
DEFAULT_LIQUID_CLASS_VERSIONS: DefaultLiquidClassVersions = {
APIVersion(2, 26): {
"ethanol_80": 2,
"glycerol_50": 2,
"water": 2,
},
}


def get_liquid_class_version(
api_version: APIVersion,
liquid_class_name: str,
) -> int:
"""Return what version of a liquid class the Protocol API should load by default."""
default_lc_versions_newest_to_oldest = sorted(
DEFAULT_LIQUID_CLASS_VERSIONS.items(), key=lambda kv: kv[0], reverse=True
)
for (
breakpoint_api_version,
breakpoint_liquid_class_versions,
) in default_lc_versions_newest_to_oldest:
if (
api_version >= breakpoint_api_version
and liquid_class_name in breakpoint_liquid_class_versions
):
return breakpoint_liquid_class_versions[liquid_class_name]

return 1
Loading
Loading