Skip to content

Commit

Permalink
fix(api): Raise cases for unsupported nozzle layouts (#15009)
Browse files Browse the repository at this point in the history
limit user access to unapproved configurations through the PAPI and raise errors when configuring potentially unsafe layouts
  • Loading branch information
CaseyBatten authored Apr 26, 2024
1 parent 331eddc commit 35e4e10
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 18 deletions.
13 changes: 13 additions & 0 deletions api/src/opentrons/hardware_control/nozzle_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
)
from opentrons_shared_data.errors import ErrorCodes, GeneralError, PythonException

MAXIMUM_NOZZLE_COUNT = 24


def _nozzle_names_by_row(rows: List[PipetteRowDefinition]) -> Iterator[str]:
for row in rows:
Expand Down Expand Up @@ -267,6 +269,17 @@ def build(
(nozzle, physical_nozzles[nozzle]) for nozzle in chain(*rows.values())
)

if (
NozzleConfigurationType.determine_nozzle_configuration(
physical_rows, rows, physical_columns, columns
)
!= NozzleConfigurationType.FULL
):
if len(rows) * len(columns) > MAXIMUM_NOZZLE_COUNT:
raise IncompatibleNozzleConfiguration(
f"Partial Nozzle Layouts may not be configured to contain more than {MAXIMUM_NOZZLE_COUNT} channels."
)

return cls(
starting_nozzle=starting_nozzle,
map_store=map_store,
Expand Down
21 changes: 14 additions & 7 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -1928,13 +1928,6 @@ def configure_nozzle_layout(
should be of the same format used when identifying wells by name.
Required unless setting ``style=ALL``.
.. note::
When using the ``COLUMN`` layout, the only fully supported value is
``start="A12"``. You can use ``start="A1"``, but this will disable tip
tracking and you will have to specify the ``location`` every time you
call :py:meth:`.pick_up_tip`, such that the pipette picks up columns of
tips *from right to left* on the tip rack.
:type start: str or ``None``
:param tip_racks: Behaves the same as setting the ``tip_racks`` parameter of
:py:meth:`.load_instrument`. If not specified, the new configuration resets
Expand All @@ -1947,6 +1940,20 @@ def configure_nozzle_layout(
# :param front_right: The nozzle at the front left of the layout. Only used for
# NozzleLayout.QUADRANT configurations.
# :type front_right: str or ``None``
#
# NOTE: Disabled layouts error case can be removed once desired map configurations
# have appropriate data regarding tip-type to map current values added to the
# pipette definitions.
disabled_layouts = [
NozzleLayout.ROW,
NozzleLayout.SINGLE,
NozzleLayout.QUADRANT,
]
if style in disabled_layouts:
raise ValueError(
f"Nozzle layout configuration of style {style.value} is currently unsupported."
)

if style != NozzleLayout.ALL:
if start is None:
raise ValueError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,31 +414,31 @@ def test_96_config_identification(
)
== nozzle_manager.NozzleConfigurationType.SUBRECT
)
subject.update_nozzle_configuration("A1", "D12")
subject.update_nozzle_configuration("A1", "B12")
assert (
cast(
nozzle_manager.NozzleConfigurationType,
subject.current_configuration.configuration,
)
== nozzle_manager.NozzleConfigurationType.SUBRECT
)
subject.update_nozzle_configuration("E1", "H12")
subject.update_nozzle_configuration("G1", "H12")
assert (
cast(
nozzle_manager.NozzleConfigurationType,
subject.current_configuration.configuration,
)
== nozzle_manager.NozzleConfigurationType.SUBRECT
)
subject.update_nozzle_configuration("A1", "H6")
subject.update_nozzle_configuration("A1", "H3")
assert (
cast(
nozzle_manager.NozzleConfigurationType,
subject.current_configuration.configuration,
)
== nozzle_manager.NozzleConfigurationType.SUBRECT
)
subject.update_nozzle_configuration("A7", "H12")
subject.update_nozzle_configuration("A10", "H12")
assert (
cast(
nozzle_manager.NozzleConfigurationType,
Expand Down
12 changes: 6 additions & 6 deletions api/tests/opentrons/protocol_engine/state/test_tip_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,17 +1121,17 @@ def _reconfigure_nozzle_layout(start: str, back_l: str, front_r: str) -> NozzleM
)
return configure_nozzle_private_result.nozzle_map

map = _reconfigure_nozzle_layout("A1", "A1", "H10")
_assert_and_pickup("A3", map)
map = _reconfigure_nozzle_layout("A1", "A1", "H3")
_assert_and_pickup("A10", map)
map = _reconfigure_nozzle_layout("A1", "A1", "F2")
_assert_and_pickup("C1", map)
_assert_and_pickup("C8", map)

# Configure to single tip pickups
map = _reconfigure_nozzle_layout("H12", "H12", "H12")
_assert_and_pickup("A1", map)
map = _reconfigure_nozzle_layout("H1", "H1", "H1")
_assert_and_pickup("A2", map)
_assert_and_pickup("A9", map)
map = _reconfigure_nozzle_layout("A12", "A12", "A12")
_assert_and_pickup("B1", map)
_assert_and_pickup("H1", map)
map = _reconfigure_nozzle_layout("A1", "A1", "A1")
_assert_and_pickup("B2", map)
_assert_and_pickup("B9", map)
4 changes: 3 additions & 1 deletion hardware-testing/hardware_testing/gravimetric/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,9 @@ def _load_pipette(
# NOTE: 8ch QC testing means testing 1 channel at a time,
# so we need to decrease the pick-up current to work with 1 tip.
if pipette.channels == 8 and not increment and not photometric:
pipette.configure_nozzle_layout(NozzleLayout.SINGLE, "A1")
pipette._core.configure_nozzle_layout(
style=NozzleLayout.SINGLE, primary_nozzle="A1", front_right_nozzle="A1"
)
# override deck conflict checking cause we specially lay out our tipracks
DeckConflit.check_safe_for_pipette_movement = (
_override_check_safe_for_pipette_movement
Expand Down

0 comments on commit 35e4e10

Please sign in to comment.