From 73fd4d7589313519da2597237cda40d695988c6b Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 2 Sep 2024 22:32:10 +0300 Subject: [PATCH 01/46] feat(hardware_control): allow Q moves to be processed by hw._move --- api/src/opentrons/hardware_control/backends/ot3controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index cb0fae82e3f..1e2a484a4e4 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -929,7 +929,6 @@ async def home_tip_motors( def _build_tip_action_group( self, origin: float, targets: List[Tuple[float, float]] ) -> MoveGroup: - move_targets = [ MoveTarget.build({Axis.Q: target_pos}, speed) for target_pos, speed in targets From e1ab9894aa351749cb11896c667cc5e1acf0baae Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 9 Sep 2024 17:41:27 +0300 Subject: [PATCH 02/46] feat: add movement commands to robot_context --- .../protocol_api/protocol_context.py | 4 +- .../opentrons/protocol_api/robot_context.py | 184 +++++++++++++++--- api/src/opentrons/protocol_api/validation.py | 100 +++++++++- .../protocol_api/test_robot_context.py | 159 +++++++++++++++ 4 files changed, 419 insertions(+), 28 deletions(-) create mode 100644 api/tests/opentrons/protocol_api/test_robot_context.py diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 43c5956afd9..c587e8577bd 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -185,7 +185,9 @@ def __init__( self._commands: List[str] = [] self._params: Parameters = Parameters() self._unsubscribe_commands: Optional[Callable[[], None]] = None - self._robot = RobotContext(self._core) + self._robot = RobotContext( + core=self._core.load_robot(), protocol_core=self._core, api_version=self._api_version + ) self.clear_commands() @property diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 01a443cd743..8579d4c9e8b 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -1,11 +1,22 @@ -from typing import NamedTuple, Union, Dict, Optional - -from opentrons.types import Mount, DeckLocation, Point +from typing import NamedTuple, Union, Optional + +from opentrons.types import ( + Mount, + DeckLocation, + Location, + Point, + AxisMapType, + AxisType, + StringAxisMap, +) from opentrons.legacy_commands import publisher -from opentrons.hardware_control import SyncHardwareAPI, types as hw_types +from opentrons.hardware_control import SyncHardwareAPI +from opentrons.protocols.api_support.util import requires_version +from opentrons.protocols.api_support.types import APIVersion -from ._types import OffDeckType -from .core.common import ProtocolCore +from . import validation +from .core.common import ProtocolCore, RobotCore +from .module_contexts import ModuleContext class HardwareManager(NamedTuple): @@ -34,33 +45,99 @@ class RobotContext(publisher.CommandPublisher): """ - def __init__(self, core: ProtocolCore) -> None: - self._hardware = HardwareManager(hardware=core.get_hardware()) + def __init__(self, core: RobotCore, protocol_core: ProtocolCore, api_version: APIVersion) -> None: + self._hardware = HardwareManager(hardware=protocol_core.get_hardware()) + self._core = core + self._protocol_core = protocol_core + self._api_version = api_version + + @property + @requires_version(2, 20) + def api_version(self) -> APIVersion: + return self._api_version @property def hardware(self) -> HardwareManager: + # TODO this hardware attribute should be deprecated + # in version 3.0+ as we will only support exposed robot + # context commands. return self._hardware + @requires_version(2, 20) def move_to( self, mount: Union[Mount, str], - destination: Point, - velocity: float, + destination: Location, + speed: Optional[float] = None, ) -> None: - raise NotImplementedError() - + """ + Move a specified mount to a destination location on the deck. + + :param mount: The mount of the instrument you wish to move. + This can either be an instance of :py:class:`.types.Mount` or one + of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note + that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``. + :type mount: types.Mount or str + :param Location destination: + :param speed: + """ + mount = validation.ensure_instrument_mount(mount) + self._core.move_to(mount, destination, speed) + + @requires_version(2, 20) def move_axes_to( self, - abs_axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue], - velocity: float, - critical_point: Optional[hw_types.CriticalPoint], + axis_map: Union[AxisMapType, StringAxisMap], + critical_point: AxisMapType, + speed: Optional[float] = None, ) -> None: - raise NotImplementedError() - + """ + Move a set of axes to an absolute position on the deck. + + :param axis_map: A dictionary mapping axes to an absolute position on the deck in mm. + :param critical_point: The critical point to move the axes with. It should only + specify the gantry axes (i.e. `x`, `y`, `z`). + :param float speed: The maximum speed with which you want to move all the axes + in the axis map. + """ + instrument_on_left = self._protocol_core.loaded_instruments.get("left") + is_96_channel = ( + instrument_on_left.channels == 96 if instrument_on_left else False + ) + axis_map = validation.ensure_axis_map_type( + axis_map, self._protocol_core.robot_type, is_96_channel + ) + critical_point = validation.ensure_axis_map_type( + critical_point, self._protocol_core.robot_type, is_96_channel + ) + validation.ensure_only_gantry_axis_map_type( + critical_point, self._protocol_core.robot_type + ) + self._core.move_axes_to(axis_map, critical_point, speed) + + @requires_version(2, 20) def move_axes_relative( - self, rel_axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue], velocity: float + self, + axis_map: Union[AxisMapType, StringAxisMap], + speed: Optional[float] = None, ) -> None: - raise NotImplementedError() + """ + Move a set of axes to a relative position on the deck. + + :param axis_map: A dictionary mapping axes to relative movements in mm. + :type mount: types.Mount or str + + :param float speed: The maximum speed with which you want to move all the axes + in the axis map. + """ + instrument_on_left = self._protocol_core.loaded_instruments.get("left") + is_96_channel = ( + instrument_on_left.channels == 96 if instrument_on_left else False + ) + axis_map = validation.ensure_axis_map_type( + axis_map, self._protocol_core.robot_type, is_96_channel + ) + self._core.move_axes_relative(axis_map, speed) def close_gripper_jaw(self, force: float) -> None: raise NotImplementedError() @@ -69,9 +146,49 @@ def open_gripper_jaw(self) -> None: raise NotImplementedError() def axis_coordinates_for( - self, mount: Union[Mount, str], location: Union[DeckLocation, OffDeckType] - ) -> None: - raise NotImplementedError() + self, + mount: Union[Mount, str], + location: Union[Location, ModuleContext, DeckLocation], + ) -> AxisMapType: + """ + Build a :py:class:`.types.AxisMapType` from a location to be compatible with + either :py:meth:`.RobotContext.move_axes_to` or :py:meth:`.RobotContext.move_axes_relative`. + You must provide only one of `location`, `slot`, or `module` to build + the axis map. + + :param mount: The mount of the instrument you wish create an axis map for. + This can either be an instance of :py:class:`.types.Mount` or one + of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note + that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``. + :type mount: types.Mount or str + :param location: The location to format an axis map for. + :type location: `Well`, `ModuleContext`, `DeckLocation` or `OffDeckType` + """ + mount = validation.ensure_instrument_mount(mount) + + mount_axis = AxisType.axis_for_mount(mount) + if location: + loc: Point + if isinstance(location, ModuleContext): + loc = location.labware + if not loc: + raise ValueError(f"There must be a labware on {location}") + top_of_labware = loc.wells()[0].top() + loc = top_of_labware.point + return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + elif isinstance(location, DeckLocation): + slot_name = validation.ensure_and_convert_deck_slot( + location, + api_version=self._api_version, + robot_type=self._protocol_core.robot_type, + ) + loc = self._protocol_core.deck.get_slot_center(slot_name) + return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + else: + loc = location.point + return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + else: + raise TypeError("You must specify a location to move to.") def plunger_coordinates_for_volume( self, mount: Union[Mount, str], volume: float @@ -83,7 +200,22 @@ def plunger_coordinates_for_named_position( ) -> None: raise NotImplementedError() - def build_axis_map( - self, axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue] - ) -> None: - raise NotImplementedError() + def build_axis_map(self, axis_map: StringAxisMap) -> AxisMapType: + """Take in a :py:class:`.types.StringAxisMap` and output a :py:class:`.types.AxisMapType`. + A :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings: + ``"x"``, ``"y"``, "``z_l"``, "``z_r"``, "``z_g"``, ``"q"``. + + An example of a valid axis map could be: + + {"x": 1, "y": 2} or {"Z_L": 100} + + Note that capitalization does not matter. + + """ + instrument_on_left = self._protocol_core.loaded_instruments.get("left") + is_96_channel = ( + instrument_on_left.channels == 96 if instrument_on_left else False + ) + return validation.ensure_axis_map_type( + axis_map, self._protocol_core.robot_type, is_96_channel + ) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 630211e9ac6..c27bb74a10f 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -21,7 +21,15 @@ from opentrons.protocols.api_support.types import APIVersion, ThermocyclerStep from opentrons.protocols.api_support.util import APIVersionError from opentrons.protocols.models import LabwareDefinition -from opentrons.types import Mount, DeckSlotName, StagingSlotName, Location +from opentrons.types import ( + Mount, + DeckSlotName, + StagingSlotName, + Location, + AxisType, + AxisMapType, + StringAxisMap, +) from opentrons.hardware_control.modules.types import ( ModuleModel, MagneticModuleModel, @@ -75,6 +83,14 @@ class PipetteMountTypeError(TypeError): """An error raised when an invalid mount type is used for loading pipettes.""" +class InstrumentMountTypeError(TypeError): + """An error raised when an invalid mount type is used for any available instruments.""" + + +class IncorrectAxisError(TypeError): + """An error raised when an invalid axis key is provided in an axis map.""" + + class LabwareDefinitionIsNotAdapterError(ValueError): """An error raised when an adapter is attempted to be loaded as a labware.""" @@ -146,6 +162,25 @@ def _ensure_mount(mount: Union[str, Mount]) -> Mount: ) +def ensure_instrument_mount(mount: Union[str, Mount]) -> Mount: + """Ensure that an input value represents a valid Mount for all instruments.""" + if isinstance(mount, Mount): + return mount + + if isinstance(mount, str): + if mount == "gripper": + # TODO (lc 08-02-2024) We should decide on the user facing name for + # the gripper mount axis. + mount = "extension" + try: + return Mount[mount.upper()] + except KeyError as e: + raise InstrumentMountTypeError( + "If mount is specified as a string, it must be 'left', 'right', 'gripper', or 'extension';" + f" instead, {mount} was given." + ) from e + + def ensure_pipette_name(pipette_name: str) -> PipetteNameType: """Ensure that an input value represents a valid pipette name.""" pipette_name = ensure_lowercase_name(pipette_name) @@ -158,6 +193,69 @@ def ensure_pipette_name(pipette_name: str) -> PipetteNameType: ) from None +def ensure_axis_map_type( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool = False, +) -> AxisMapType: + """Ensure that the axis map provided is in the correct shape and contains the correct keys.""" + axis_map_keys = list(axis_map.keys()) + key_type = set(type(k) for k in axis_map_keys) + + if len(key_type) > 1: + raise IncorrectAxisError( + "Please provide an `axis_map` with only string or only AxisType keys." + ) + if robot_type == "OT-2 Standard": + if list(key_type)[0] is AxisType and any( + k not in AxisType.ot2_axes() for k in axis_map_keys + ): + raise IncorrectAxisError( + f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" + ) + if list(key_type)[0] is str and any( + k.upper() not in [axis.value for axis in AxisType.ot2_axes()] + for k in axis_map_keys + ): + raise IncorrectAxisError( + f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" + ) + if is_96_channel and any( + key_variation in axis_map_keys for key_variation in ["Z_R", "z_r", AxisType.Z_R] + ): + raise IncorrectAxisError( + "A 96 channel is attached. You cannot move the `Z_R` mount." + ) + if not is_96_channel and any( + key_variation in axis_map_keys for key_variation in ["Q", "q", AxisType.Q] + ): + raise IncorrectAxisError( + "A 96 channel is not attached. The clamp `Q` motor does not exist." + ) + + if isinstance(axis_map_keys[0], AxisType): + return axis_map + try: + return {AxisType[k.upper()]: v for k, v in axis_map.items()} + except KeyError as e: + raise IncorrectAxisError(f"{e} is not a supported `AxisMapType`") + + +def ensure_only_gantry_axis_map_type( + axis_map: AxisMapType, robot_type: RobotType +) -> None: + if robot_type == "OT-2 Standard": + if any(k not in AxisType.ot2_gantry_axes() for k in axis_map.keys()): + raise IncorrectAxisError( + f"A critical point only accepts OT-2 gantry axes which are {AxisType.ot2_gantry_axes()}" + ) + else: + if any(k not in AxisType.ot3_gantry_axes() for k in axis_map.keys()): + raise IncorrectAxisError( + f"A critical point only accepts Flex gantry axes which are {AxisType.ot3_gantry_axes()}" + ) + + # TODO(jbl 11-17-2023) this function's original purpose was ensure a valid deck slot for a given robot type # With deck configuration, the shape of this should change to better represent it checking if a deck slot # (and maybe any addressable area) being valid for that deck configuration diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py new file mode 100644 index 00000000000..5b1c6c67afc --- /dev/null +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -0,0 +1,159 @@ +import pytest +from decoy import Decoy +from typing import Union, Optional + +from opentrons.types import ( + DeckLocation, + Mount, + Point, + Location, + DeckSlotName, + AxisType, + StringAxisMap, + AxisMapType, +) +from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocol_api.core.common import ProtocolCore, RobotCore +from opentrons.protocol_api import RobotContext, ModuleContext, MAX_SUPPORTED_VERSION + + +@pytest.fixture +def mock_core(decoy: Decoy) -> RobotCore: + """Get a mock module implementation core.""" + return decoy.mock(cls=RobotCore) + + +@pytest.fixture +def api_version() -> APIVersion: + """Get the API version to test at.""" + return MAX_SUPPORTED_VERSION + + +@pytest.fixture +def mock_protocol(decoy: Decoy, mock_core: RobotCore) -> ProtocolCore: + """Get a mock protocol implementation core without a 96 channel attached.""" + protocol_core = decoy.mock(cls=ProtocolCore) + decoy.when(protocol_core.robot_type).then_return("OT-3 Standard") + decoy.when(protocol_core.loaded_instruments).then_return({}) + decoy.when(protocol_core.load_robot()).then_return(mock_core) + decoy.when(protocol_core.deck.get_slot_center(DeckSlotName.SLOT_D1)).then_return( + Point(3, 3, 3) + ) + return protocol_core + + +@pytest.fixture +def subject( + mock_protocol: ProtocolCore, + api_version: APIVersion, +) -> RobotContext: + """Get a RobotContext test subject with its dependencies mocked out.""" + return RobotContext(api_version=api_version, protocol_core=mock_protocol) + + +@pytest.mark.parametrize( + argnames=["mount", "destination", "speed"], + argvalues=[ + ("left", Location(point=Point(1, 2, 3), labware=None), None), + (Mount.RIGHT, Location(point=Point(1, 2, 3), labware=None), 100), + ], +) +def test_move_to( + decoy: Decoy, + subject: RobotContext, + mount: Union[str, Mount], + destination: Location, + speed: Optional[float], +) -> None: + subject.move_to(mount, destination, speed) + if mount == "left": + mount = Mount.LEFT + decoy.verify(subject._core.move_to(mount, destination, speed)) + + +@pytest.mark.parametrize( + argnames=[ + "axis_map", + "critical_point", + "expected_axis_map", + "expected_critical_point", + "speed", + ], + argvalues=[ + ( + {"x": 100, "Y": 50, "z_g": 80}, + {"x": 5, "Y": 5, "z_g": 5}, + {AxisType.X: 100, AxisType.Y: 50, AxisType.Z_G: 80}, + {AxisType.X: 5, AxisType.Y: 5, AxisType.Z_G: 5}, + None, + ), + ( + {"x": 5, "Y": 5}, + {"x": 5, "Y": 5}, + {AxisType.X: 5, AxisType.Y: 5}, + {AxisType.X: 5, AxisType.Y: 5}, + None, + ), + ], +) +def test_move_axes_to( + decoy: Decoy, + subject: RobotContext, + axis_map: Union[StringAxisMap, AxisMapType], + critical_point: Union[StringAxisMap, AxisMapType], + expected_axis_map: AxisMapType, + expected_critical_point: AxisMapType, + speed: Optional[float], +) -> None: + subject.move_axes_to(axis_map, critical_point, speed) + decoy.verify( + subject._core.move_axes_to(expected_axis_map, expected_critical_point, speed) + ) + + +@pytest.mark.parametrize( + argnames=["axis_map", "converted_map", "speed"], + argvalues=[ + ( + {"x": 10, "Y": 10, "z_g": 10}, + {AxisType.X: 10, AxisType.Y: 10, AxisType.Z_G: 10}, + None, + ), + ({AxisType.P_L: 10}, {AxisType.P_L: 10}, 5), + ], +) +def test_move_axes_relative( + decoy: Decoy, + subject: RobotContext, + axis_map: Union[StringAxisMap, AxisMapType], + converted_map: AxisMapType, + speed: Optional[float], +) -> None: + subject.move_axes_relative(axis_map, speed) + decoy.verify(subject._core.move_axes_relative(converted_map, speed)) + + +@pytest.mark.parametrize( + argnames=["mount", "location_to_move", "expected_axis_map"], + argvalues=[ + ( + "left", + Location(point=Point(1, 2, 3), labware=None), + {AxisType.Z_L: 3, AxisType.X: 1, AxisType.Y: 2}, + ), + (Mount.RIGHT, "D1", {AxisType.Z_R: 3, AxisType.X: 3, AxisType.Y: 3}), + ( + Mount.EXTENSION, + Location(point=Point(1, 2, 3), labware=None), + {AxisType.Z_G: 3, AxisType.X: 1, AxisType.Y: 2}, + ), + ], +) +def test_get_axes_coordinates_for( + subject: RobotContext, + mount: Union[Mount, str], + location_to_move: Union[Location, ModuleContext, DeckLocation], + expected_axis_map: AxisMapType, +) -> None: + res = subject.axis_coordinates_for(mount, location_to_move) + assert res == expected_axis_map From 535a0d59380885a54813e6cf592891e1b20ac90f Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 9 Sep 2024 17:41:50 +0300 Subject: [PATCH 03/46] feat: add robot core --- api/src/opentrons/protocol_api/core/common.py | 4 +- .../protocol_api/core/engine/protocol.py | 13 +++++- .../protocol_api/core/engine/robot.py | 46 +++++++++++++++++++ .../core/legacy/legacy_protocol_core.py | 5 ++ .../core/legacy/legacy_robot_core.py | 0 .../legacy_simulator/legacy_protocol_core.py | 2 +- .../opentrons/protocol_api/core/protocol.py | 7 ++- api/src/opentrons/protocol_api/core/robot.py | 21 +++++++++ 8 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 api/src/opentrons/protocol_api/core/engine/robot.py create mode 100644 api/src/opentrons/protocol_api/core/legacy/legacy_robot_core.py create mode 100644 api/src/opentrons/protocol_api/core/robot.py diff --git a/api/src/opentrons/protocol_api/core/common.py b/api/src/opentrons/protocol_api/core/common.py index 5a63abb46b3..63312534995 100644 --- a/api/src/opentrons/protocol_api/core/common.py +++ b/api/src/opentrons/protocol_api/core/common.py @@ -14,6 +14,7 @@ ) from .protocol import AbstractProtocol from .well import AbstractWellCore +from .robot import AbstractRobot WellCore = AbstractWellCore @@ -26,4 +27,5 @@ HeaterShakerCore = AbstractHeaterShakerCore MagneticBlockCore = AbstractMagneticBlockCore AbsorbanceReaderCore = AbstractAbsorbanceReaderCore -ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore] +RobotCore = AbstractRobot +ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore, RobotCore] diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index dac8bc44a5b..483085a6e5b 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -63,6 +63,7 @@ from ..labware import LabwareLoadParams from .labware import LabwareCore from .instrument import InstrumentCore +from .robot import RobotCore from .module_core import ( ModuleCore, TemperatureModuleCore, @@ -82,7 +83,10 @@ class ProtocolCore( AbstractProtocol[ - InstrumentCore, LabwareCore, Union[ModuleCore, NonConnectedModuleCore] + InstrumentCore, + LabwareCore, + Union[ModuleCore, NonConnectedModuleCore], + RobotCore, ] ): """Protocol API core using a ProtocolEngine. @@ -531,6 +535,13 @@ def _get_module_core( load_module_result=load_module_result, model=model ) + def load_robot(self) -> RobotCore: + """Load a robot core into the RobotContext. + """ + return RobotCore( + engine_client=self._engine_client, sync_hardware_api=self._sync_hardware + ) + def load_instrument( self, instrument_name: PipetteNameType, diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py new file mode 100644 index 00000000000..76443daa091 --- /dev/null +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -0,0 +1,46 @@ +from typing import Optional +from opentrons.hardware_control import SyncHardwareAPI + +from opentrons.types import Mount, MountType, Point, AxisMapType +from opentrons.protocol_engine import commands as cmd +from opentrons.protocol_engine.clients import SyncClient as EngineClient +from opentrons.protocol_engine.types import DeckPoint + +from opentrons.protocol_api.core.robot import AbstractRobot + + +class RobotCore(AbstractRobot): + """Robot API core using a ProtocolEngine. + + Args: + engine_client: A client to the ProtocolEngine that is executing the protocol. + api_version: The Python Protocol API versionat which this core is operating. + sync_hardware: A SynchronousAdapter-wrapped Hardware Control API. + """ + + def __init__( + self, engine_client: EngineClient, sync_hardware_api: SyncHardwareAPI + ) -> None: + self._engine_client = engine_client + self._sync_hardware_api = sync_hardware_api + + def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: + engine_mount = MountType[mount.name] + engine_destination = DeckPoint(*destination) + self._engine_client.execute_command( + cmd.robot.MoveToParams(mount=engine_mount, destination=engine_destination, speed=speed) + ) + + def move_axes_to( + self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float] + ) -> None: + self._engine_client.execute_command( + cmd.robot.MoveAxesToParams( + axis_map=axis_map, critical_point=critical_point, speed=speed + ) + ) + + def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: + self._engine_client.execute_command( + cmd.robot.MoveAxesRelativeParams(axis_map=axis_map, speed=speed) + ) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py index aeef0e9d7c7..a6c698d2a33 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py @@ -37,6 +37,7 @@ class LegacyProtocolCore( LegacyInstrumentCore, LegacyLabwareCore, legacy_module_core.LegacyModuleCore, + None, ] ): def __init__( @@ -266,6 +267,10 @@ def load_adapter( ) -> LegacyLabwareCore: """Load an adapter using its identifying parameters""" raise APIVersionError(api_element="Loading adapter") + + def load_robot(self) -> None: + """Load an adapter using its identifying parameters""" + raise APIVersionError(api_element="Loading robot") def move_labware( self, diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_robot_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_robot_core.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py index d0002763b1c..f7a5023cab3 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py @@ -23,7 +23,7 @@ class LegacyProtocolCoreSimulator( LegacyProtocolCore, AbstractProtocol[ - LegacyInstrumentCoreSimulator, LegacyLabwareCore, LegacyModuleCore + LegacyInstrumentCoreSimulator, LegacyLabwareCore, LegacyModuleCore, None ], ): _instruments: Dict[Mount, Optional[LegacyInstrumentCoreSimulator]] # type: ignore[assignment] diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index 9c3692c7e44..2a627e651a2 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -19,6 +19,7 @@ from .labware import LabwareCoreType, LabwareLoadParams from .module import ModuleCoreType from .._liquid import Liquid, LiquidClass +from .robot import RobotCoreType from .._types import OffDeckType from ..disposal_locations import TrashBin, WasteChute @@ -27,7 +28,7 @@ class AbstractProtocol( - ABC, Generic[InstrumentCoreType, LabwareCoreType, ModuleCoreType] + ABC, Generic[InstrumentCoreType, LabwareCoreType, ModuleCoreType, RobotCoreType] ): @property @abstractmethod @@ -256,3 +257,7 @@ def get_labware_location( self, labware_core: LabwareCoreType ) -> Union[str, LabwareCoreType, ModuleCoreType, OffDeckType]: """Get labware parent location.""" + + @abstractmethod + def load_robot(self) -> RobotCoreType: + """Load a Robot Core context into a protocol""" diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py new file mode 100644 index 00000000000..15837807ff0 --- /dev/null +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -0,0 +1,21 @@ +from abc import abstractmethod, ABC +from typing import TypeVar, Optional + +from opentrons.types import AxisMapType, Mount, Point + + +class AbstractRobot(ABC): + @abstractmethod + def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: + ... + + @abstractmethod + def move_axes_to(self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float]) -> None: + ... + + @abstractmethod + def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: + ... + + +RobotCoreType = TypeVar("RobotCoreType", bound=AbstractRobot) From 966457f858805157682ed7ca48266c818475ae5e Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 9 Sep 2024 17:42:45 +0300 Subject: [PATCH 04/46] feat: add protocol engine commands for robot movement commands --- .../protocol_engine/commands/__init__.py | 3 + .../commands/command_unions.py | 16 ++++ .../commands/robot/__init__.py | 43 +++++++++ .../commands/robot/move_axes_relative.py | 75 +++++++++++++++ .../commands/robot/move_axes_to.py | 92 +++++++++++++++++++ .../protocol_engine/commands/robot/move_to.py | 82 +++++++++++++++++ .../protocol_engine/execution/gantry_mover.py | 38 ++++++++ api/src/opentrons/protocol_engine/types.py | 1 + 8 files changed, 350 insertions(+) create mode 100644 api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py create mode 100644 api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py create mode 100644 api/src/opentrons/protocol_engine/commands/robot/move_to.py diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index 69a3d0c12c1..b06e7332072 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -20,6 +20,7 @@ from . import thermocycler from . import calibration from . import unsafe +from . import robot from .hash_command_params import hash_protocol_command_params from .generate_command_schema import generate_command_schema @@ -552,6 +553,8 @@ "LoadLiquidParams", "LoadLiquidResult", "LoadLiquidCommandType", + # hardware control command models + "robot", # hardware module command bundles "absorbance_reader", "heater_shaker", diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index c59c538ddd3..4b9a74fb737 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -22,6 +22,7 @@ from . import calibration from . import unsafe +from . import robot from .set_rail_lights import ( SetRailLights, @@ -402,6 +403,9 @@ unsafe.UnsafeEngageAxes, unsafe.UnsafeUngripLabware, unsafe.UnsafePlaceLabware, + robot.MoveTo, + robot.MoveAxesRelative, + robot.MoveAxesTo, ], Field(discriminator="commandType"), ] @@ -481,6 +485,9 @@ unsafe.UnsafeEngageAxesParams, unsafe.UnsafeUngripLabwareParams, unsafe.UnsafePlaceLabwareParams, + robot.MoveAxesRelativeParams, + robot.MoveAxesToParams, + robot.MoveToParams, ] CommandType = Union[ @@ -558,6 +565,9 @@ unsafe.UnsafeEngageAxesCommandType, unsafe.UnsafeUngripLabwareCommandType, unsafe.UnsafePlaceLabwareCommandType, + robot.MoveAxesRelativeCommandType, + robot.MoveAxesToCommandType, + robot.MoveToCommandType ] CommandCreate = Annotated[ @@ -636,6 +646,9 @@ unsafe.UnsafeEngageAxesCreate, unsafe.UnsafeUngripLabwareCreate, unsafe.UnsafePlaceLabwareCreate, + robot.MoveAxesRelativeCreate, + robot.MoveAxesToCreate, + robot.MoveToCreate, ], Field(discriminator="commandType"), ] @@ -715,6 +728,9 @@ unsafe.UnsafeEngageAxesResult, unsafe.UnsafeUngripLabwareResult, unsafe.UnsafePlaceLabwareResult, + robot.MoveAxesRelativeResult, + robot.MoveAxesToResult, + robot.MoveToResult, ] diff --git a/api/src/opentrons/protocol_engine/commands/robot/__init__.py b/api/src/opentrons/protocol_engine/commands/robot/__init__.py index ee78c1d4044..7221150d900 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/robot/__init__.py @@ -1 +1,44 @@ """Robot movement commands.""" + +from .move_to import ( + MoveTo, + MoveToCreate, + MoveToParams, + MoveToResult, + MoveToCommandType, +) +from .move_axes_to import ( + MoveAxesTo, + MoveAxesToCreate, + MoveAxesToParams, + MoveAxesToResult, + MoveAxesToCommandType, +) +from .move_axes_relative import ( + MoveAxesRelative, + MoveAxesRelativeCreate, + MoveAxesRelativeParams, + MoveAxesRelativeResult, + MoveAxesRelativeCommandType, +) + +__all__ = [ + # robot/moveTo + "MoveTo", + "MoveToCreate", + "MoveToParams", + "MoveToResult", + "MoveToCommandType", + # robot/moveAxesTo + "MoveAxesTo", + "MoveAxesToCreate", + "MoveAxesToParams", + "MoveAxesToResult", + "MoveAxesToCommandType", + # robot/moveAxesRelative + "MoveAxesRelative", + "MoveAxesRelativeCreate", + "MoveAxesRelativeParams", + "MoveAxesRelativeResult", + "MoveAxesRelativeCommandType", +] diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py new file mode 100644 index 00000000000..14864a9da56 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py @@ -0,0 +1,75 @@ +from typing import Literal, Type, Optional, TYPE_CHECKING + +from pydantic import BaseModel, Field +from opentrons.types import AxisMapType +from opentrons.hardware_control import HardwareControlAPI +from opentrons.hardware_control.protocols.types import FlexRobotType + +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from ...errors.error_occurrence import ErrorOccurrence + +if TYPE_CHECKING: + from opentrons.protocol_engine.execution import MovementHandler + + +MoveAxesRelativeCommandType = Literal["robot/moveAxesRelative"] + + +class MoveAxesRelativeParams(BaseModel): + """Payload required to move axes relative to position.""" + + axis_map: AxisMapType = Field(..., description="A dictionary mapping axes to relative movements in mm.") + speed: float + + +class MoveAxesRelativeResult(BaseModel): + """Result data from the execution of a MoveAxesRelative command.""" + + pass + + +class MoveAxesRelativeImplementation( + AbstractCommandImpl[ + MoveAxesRelativeParams, SuccessData[MoveAxesRelativeResult, None] + ] +): + """MoveAxesRelative command implementation.""" + + def __init__(self, hardware_api: HardwareControlAPI, **kwargs: object) -> None: + self._hardware_api = hardware_api + + async def execute( + self, params: MoveAxesRelativeParams + ) -> SuccessData[MoveAxesRelativeResult, None]: + if self._hardware_api.get_robot_type() == FlexRobotType: + self._movement.move_axes(axis_map=params.axis_map, speed=params.speed, relative_move=True) + else: + self._movement.move_relative(axis_map=params.axis_map, speed=params.speed) + + +class MoveAxesRelative( + BaseCommand[MoveAxesRelativeParams, MoveAxesRelativeResult, ErrorOccurrence] +): + """MoveAxesRelative command model.""" + + commandType: MoveAxesRelativeCommandType = "robot/moveAxesRelative" + params: MoveAxesRelativeParams + result: Optional[MoveAxesRelativeResult] + + _ImplementationCls: Type[ + MoveAxesRelativeImplementation + ] = MoveAxesRelativeImplementation + + +class MoveAxesRelativeCreate(BaseCommandCreate[MoveAxesRelativeParams]): + """MoveAxesRelative command request model.""" + + commandType: MoveAxesRelativeCommandType = "robot/moveAxesRelative" + params: MoveAxesRelativeParams + + _CommandCls: Type[MoveAxesRelative] = MoveAxesRelative diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py new file mode 100644 index 00000000000..d47e10fc720 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -0,0 +1,92 @@ +from typing import Literal, Dict, Optional, Type, TYPE_CHECKING +from pydantic import Field, BaseModel + +from opentrons.hardware_control import HardwareControlAPI +from opentrons.hardware_control.protocols.types import FlexRobotType + +from ..pipetting_common import DestinationPositionResult +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from ...errors.error_occurrence import ErrorOccurrence +from ...types import DeckPoint + +if TYPE_CHECKING: + from opentrons.protocol_engine.execution.movement import MovementHandler + + +MoveAxesToCommandType = Literal["robot/moveAxesTo"] + + +class MoveAxesToParams(BaseModel): + """Payload required to move axes to absolute position.""" + + axis_map: Dict[str, float] = Field( + ..., description="The specified axes to move to an absolute deck position with." + ) + critical_point: Dict[str, float] = Field( + ..., description="The critical point to move the mount with." + ) + velocity: Optional[float] = Field( + default=None, + description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + ) + + +class MoveAxesToResult(DestinationPositionResult): + """Result data from the execution of a MoveAxesTo command.""" + + pass + + +class MoveAxesToImplementation( + AbstractCommandImpl[MoveAxesToParams, SuccessData[MoveAxesToResult, None]] +): + """MoveAxesTo command implementation.""" + + def __init__( + self, + # movement: MovementHandler, + hardware_api: HardwareControlAPI, + **kwargs: object + ) -> None: + # self._movement = movement + self._hardware_api = hardware_api + + async def execute( + self, params: MoveAxesToParams + ) -> SuccessData[MoveAxesToResult, None]: + if self._hardware_api.get_robot_type() == FlexRobotType: + self._movement.move_axes(axis_map=params.axis_map, speed=params.speed, relative_move=True) + else: + self._movement.move_to(mount=params.mount, speed=params.speed) + # x, y, z = self._movement.move_to_with_mount( + # params.axis_map, params.critical_point, params.velocity + # ) + x, y, z = (0, 0, 0) + return SuccessData( + public=MoveAxesToResult(position=DeckPoint(x=x, y=y, z=z)), + private=None, + ) + + +class MoveAxesTo(BaseCommand[MoveAxesToParams, MoveAxesToResult, ErrorOccurrence]): + """MoveAxesTo command model.""" + + commandType: MoveAxesToCommandType = "robot/moveAxesTo" + params: MoveAxesToParams + result: Optional[MoveAxesToResult] + + _ImplementationCls: Type[MoveAxesToImplementation] = MoveAxesToImplementation + + +class MoveAxesToCreate(BaseCommandCreate[MoveAxesToParams]): + """MoveAxesTo command request model.""" + + commandType: MoveAxesToCommandType = "robot/moveAxesTo" + params: MoveAxesToParams + + _CommandCls: Type[MoveAxesTo] = MoveAxesTo diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_to.py new file mode 100644 index 00000000000..937a75a9804 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/move_to.py @@ -0,0 +1,82 @@ +from typing import Literal, Type, Optional, TYPE_CHECKING + +from pydantic import BaseModel, Field +from opentrons.types import MountType +from opentrons.hardware_control import HardwareControlAPI +from opentrons.hardware_control.protocols.types import FlexRobotType + +from ..pipetting_common import DestinationPositionResult +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from opentrons.protocol_engine.types import DeckPoint +from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence + + +if TYPE_CHECKING: + from opentrons.protocol_engine.execution import MovementHandler + + +MoveToCommandType = Literal["robot/moveTo"] + + +class MoveToParams(BaseModel): + """Payload required to move to a destination position.""" + + mount: MountType + destination: DeckPoint = Field( + ..., + description="X, Y and Z coordinates in mm from deck's origin location (left-front-bottom corner of work space)", + ) + speed: float + + +class MoveToResult(DestinationPositionResult): + """Result data from the execution of a MoveTo command.""" + + pass + + +class MoveToImplementation( + AbstractCommandImpl[MoveToParams, SuccessData[MoveToResult, None]] +): + """MoveTo command implementation.""" + + def __init__(self, movement: MovementHandler, hardware_api: HardwareControlAPI, **kwargs: object) -> None: + self._movement = movement + self._hardware_api = hardware_api + + async def execute(self, params: MoveToParams) -> SuccessData[MoveToResult, None]: + if self._hardware_api.get_robot_type() == FlexRobotType: + x, y, z = self._movement.move_axes(axis_map=params.axis_map, speed=params.speed, relative_move=True) + else: + x, y, z = self._movement.move_to(mount=params.mount, speed=params.speed) + # x, y, z = self._hardware_api.move_to( + # params.mount, params.destination, params.velocity + # ) + return SuccessData( + public=MoveToResult(position=DeckPoint(x=x, y=y, z=z)), + private=None, + ) + + +class MoveTo(BaseCommand[MoveToParams, MoveToResult, ErrorOccurrence]): + """MoveTo command model.""" + + commandType: MoveToCommandType = "robot/moveTo" + params: MoveToParams + result: Optional[MoveToResult] + + _ImplementationCls: Type[MoveToImplementation] = MoveToImplementation + + +class MoveToCreate(BaseCommandCreate[MoveToParams]): + """MoveTo command request model.""" + + commandType: MoveToCommandType = "robot/moveTo" + params: MoveToParams + + _CommandCls: Type[MoveTo] = MoveTo diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 8b33e43f437..fbd90e6729e 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -24,6 +24,7 @@ MotorAxis.RIGHT_PLUNGER: HardwareAxis.C, MotorAxis.EXTENSION_Z: HardwareAxis.Z_G, MotorAxis.EXTENSION_JAW: HardwareAxis.G, + MotorAxis.CLAMP_JAW_96_CHANNEL: HardwareAxis.Q, } # The height of the bottom of the pipette nozzle at home position without any tips. @@ -150,6 +151,23 @@ async def move_to( return waypoints[-1].position + async def move_to_with_mount( + self, mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: + assert len(waypoints) > 0, "Must have at least one waypoint" + + # hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + + for waypoint in waypoints: + await self._hardware_api.move_to( + mount=hw_mount, + abs_position=waypoint.position, + critical_point=waypoint.critical_point, + speed=speed, + ) + + return waypoints[-1].position + async def move_relative( self, pipette_id: str, @@ -185,6 +203,26 @@ async def move_relative( return point + async def move_relative_with_mount( + self, mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: + try: + await self._hardware_api.move_rel( + mount=hw_mount, + delta=delta, + fail_on_not_homed=True, + speed=speed, + ) + point = await self._hardware_api.gantry_position( + mount=hw_mount, + critical_point=critical_point, + fail_on_not_homed=True, + ) + except PositionUnknownError as e: + raise MustHomeError(message=str(e), wrapping=[e]) + + return point + async def home(self, axes: Optional[List[MotorAxis]]) -> None: """Home the gantry.""" # TODO(mc, 2022-12-01): this is overly complicated diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 5aa4c8c26e9..1424342817b 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -457,6 +457,7 @@ class MotorAxis(str, Enum): RIGHT_PLUNGER = "rightPlunger" EXTENSION_Z = "extensionZ" EXTENSION_JAW = "extensionJaw" + CLAMP_JAW_96_CHANNEL = "clampJaw96Channel" # TODO(mc, 2022-01-18): use opentrons_shared_data.module.types.ModuleModel From d1cf9485ec50773d795e22f31b27d135b8cdd976 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 9 Sep 2024 17:43:07 +0300 Subject: [PATCH 05/46] feat: add new axis types --- api/src/opentrons/types.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/api/src/opentrons/types.py b/api/src/opentrons/types.py index e231ab9bd48..b532c8cfc55 100644 --- a/api/src/opentrons/types.py +++ b/api/src/opentrons/types.py @@ -262,6 +262,69 @@ class OT3MountType(str, enum.Enum): GRIPPER = "gripper" +class AxisType(enum.Enum): + X = "X" # gantry + Y = "Y" + Z_L = "Z_L" # left pipette mount Z + Z_R = "Z_R" # right pipette mount Z + Z_G = "Z_G" # gripper mount Z + P_L = "P_L" # left pipette plunger + P_R = "P_R" # right pipette plunger + Q = "Q" # hi-throughput pipette tiprack grab + G = "G" # gripper grab + + @classmethod + def axis_for_mount(cls, mount: Mount) -> "AxisType": + if mount == Mount.LEFT: + return cls.Z_L + elif mount == Mount.RIGHT: + return cls.Z_R + elif mount == Mount.EXTENSION: + return cls.Z_G + + @classmethod + def mount_for_axis(cls, axis: "AxisType") -> Mount: + if axis == cls.Z_L: + return Mount.LEFT + elif axis == cls.Z_R: + return Mount.RIGHT + elif axis == cls.Z_G: + return Mount.EXTENSION + + @classmethod + def ot2_axes(cls) -> List["AxisType"]: + return [ + AxisType.X, + AxisType.Y, + AxisType.Z_L, + AxisType.Z_R, + AxisType.P_L, + AxisType.P_R, + ] + + @classmethod + def ot3_gantry_axes(cls) -> List["AxisType"]: + return [ + AxisType.X, + AxisType.Y, + AxisType.Z_L, + AxisType.Z_R, + AxisType.Z_G, + ] + + @classmethod + def ot2_gantry_axes(cls) -> List["AxisType"]: + return [ + AxisType.X, + AxisType.Y, + AxisType.Z_L, + AxisType.Z_R, + ] + + +AxisMapType = Dict[AxisType, float] +StringAxisMap = Dict[str, float] + # TODO(mc, 2020-11-09): this makes sense in shared-data or other common # model library # https://github.com/Opentrons/opentrons/pull/6943#discussion_r519029833 From d577abf0f85220146992635143ee2490cc57c36a Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:06:30 +0300 Subject: [PATCH 06/46] feat: Expose additional movement options in gantry mover and movement --- .../hardware_control/motion_utilities.py | 46 +++ .../protocol_engine/execution/gantry_mover.py | 267 +++++++++++++++--- .../protocol_engine/execution/movement.py | 28 +- 3 files changed, 308 insertions(+), 33 deletions(-) diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index 15604dfd360..245de57f9f9 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -36,6 +36,19 @@ # ) -> Point: # ... +EMPTY_ORDERED_DICT = OrderedDict( + ( + (Axis.X, 0.0), + (Axis.Y, 0.0), + (Axis.Z_L, 0.0), + (Axis.Z_R, 0.0), + (Axis.Z_G, 0.0), + (Axis.P_L, 0.0), + (Axis.P_R, 0.0), + (Axis.Q, 0.0), + ) +) + @lru_cache(4) def offset_for_mount( @@ -97,6 +110,39 @@ def target_position_from_relative( return target_position +def target_axis_map_from_absolute( + axis_map: Dict[Axis, float], + critical_point: Dict[Axis, float], + mount_offset: Dict[Axis, float], +) -> "OrderedDict[Axis, float]": + """Create an absolute target position for all specified machine axes.""" + axis_with_cp = {ax: axis_map[ax] - val for ax, val in critical_point.items()} + axis_map.update(axis_with_cp) + axis_with_offset = { + ax: axis_map[ax] - val + for ax, val in mount_offset.items() + if ax in axis_map.keys() + } + axis_map.update(axis_with_offset) + target_position = OrderedDict( + ((ax, axis_map[ax]) for ax in EMPTY_ORDERED_DICT.keys()) + ) + return target_position + + +def target_axis_map_from_relative( + axis_map: Dict[Axis, float], + current_position: Dict[Axis, float], +) -> "OrderedDict[Axis, float]": + """Create a target position for all specified machine axes.""" + target_position = OrderedDict( + ((ax, current_position[ax] + axis_map[ax]) + for ax in EMPTY_ORDERED_DICT.keys() + if ax in axis_map.keys()) + ) + return target_position + + def target_position_from_plunger( mount: Union[Mount, OT3Mount], delta: float, diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index fbd90e6729e..54cb25ecbec 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -2,10 +2,14 @@ from typing import Optional, List, Dict from typing_extensions import Protocol as TypingProtocol -from opentrons.types import Point, Mount +from opentrons.types import Point, Mount, MountType from opentrons.hardware_control import HardwareControlAPI -from opentrons.hardware_control.types import Axis as HardwareAxis +from opentrons.hardware_control.types import Axis as HardwareAxis, CriticalPoint +from opentrons.hardware_control.motion_utilities import ( + target_axis_map_from_relative, + target_axis_map_from_absolute, +) from opentrons_shared_data.errors.exceptions import PositionUnknownError from opentrons.motion_planning import Waypoint @@ -27,6 +31,28 @@ MotorAxis.CLAMP_JAW_96_CHANNEL: HardwareAxis.Q, } +_MOTOR_AXIS_TO_HARDWARE_MOUNT: Dict[MotorAxis, Mount] = { + MotorAxis.LEFT_Z: Mount.LEFT, + MotorAxis.RIGHT_Z: Mount.RIGHT, + MotorAxis.EXTENSION_Z: Mount.EXTENSION, +} + +_HARDWARE_AXIS_TO_MOTOR_AXIS: Dict[HardwareAxis, MotorAxis] = { + HardwareAxis.X: MotorAxis.X, + HardwareAxis.Y: MotorAxis.Y, + HardwareAxis.Z: MotorAxis.LEFT_Z, + HardwareAxis.A: MotorAxis.RIGHT_Z, + HardwareAxis.B: MotorAxis.LEFT_PLUNGER, + HardwareAxis.C: MotorAxis.RIGHT_PLUNGER, + HardwareAxis.P_L: MotorAxis.LEFT_PLUNGER, + HardwareAxis.P_R: MotorAxis.RIGHT_PLUNGER, + HardwareAxis.Z_L: MotorAxis.LEFT_Z, + HardwareAxis.Z_R: MotorAxis.RIGHT_Z, + HardwareAxis.Z_G: MotorAxis.EXTENSION_Z, + HardwareAxis.G: MotorAxis.EXTENSION_JAW, + HardwareAxis.Q: MotorAxis.CLAMP_JAW_96_CHANNEL, +} + # The height of the bottom of the pipette nozzle at home position without any tips. # We rely on this being the same for every OT-3 pipette. # @@ -51,16 +77,44 @@ async def get_position( """Get the current position of the gantry.""" ... + async def get_position_from_mount( + self, + mount: Mount, + critical_point: Optional[CriticalPoint] = None, + fail_on_not_homed: bool = False, + ) -> Point: + """Get the current position of the gantry based on the given mount.""" + ... + def get_max_travel_z(self, pipette_id: str) -> float: """Get the maximum allowed z-height for pipette movement.""" ... + def get_max_travel_z_from_mount(self, mount: MountType) -> float: + """Get the maximum allowed z-height for mount movement.""" + ... + + async def move_axes( + self, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]] = None, + speed: Optional[float] = None, + ) -> Dict[MotorAxis, float]: + """Move a set of axes a given distance.""" + ... + async def move_to( self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float] ) -> Point: """Move the hardware gantry to a waypoint.""" ... + async def move_mount_to( + self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: + """Move the provided hardware mount to a waypoint.""" + ... + async def move_relative( self, pipette_id: str, @@ -86,6 +140,10 @@ def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis: """Transform an engine motor axis into a hardware axis.""" ... + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: + """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" + ... + class HardwareGantryMover(GantryMover): """Hardware API based gantry movement handler.""" @@ -98,6 +156,51 @@ def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis: """Transform an engine motor axis into a hardware axis.""" return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis] + def _hardware_axis_to_motor_axis(self, motor_axis: HardwareAxis) -> MotorAxis: + """Transform an hardware axis into a engine motor axis.""" + return _HARDWARE_AXIS_TO_MOTOR_AXIS[motor_axis] + + def _convert_axis_map_for_hw( + self, axis_map: Dict[MotorAxis, float] + ) -> Dict[HardwareAxis, float]: + """Transform an engine motor axis map to a hardware axis map.""" + return {_MOTOR_AXIS_TO_HARDWARE_AXIS[ax]: dist for ax, dist in axis_map.items()} + + def _offset_axis_map_for_mount(self, mount: Mount) -> Dict[HardwareAxis, float]: + """Determine the offset for the given hardware mount""" + if ( + self._state_view.config.robot_type == "OT-2 Standard" + and mount == Mount.RIGHT + ): + return {HardwareAxis.X: 0.0, HardwareAxis.Y: 0.0, HardwareAxis.A: 0.0} + elif ( + self._state_view.config.robot_type == "OT-3 Standard" + and mount == Mount.EXTENSION + ): + offset = self._hardware_api.config.gripper_mount_offset # type: ignore [union-attr] + return { + HardwareAxis.X: offset[0], + HardwareAxis.Y: offset[1], + HardwareAxis.Z_G: offset[2], + } + else: + offset = self._hardware_api.config.left_mount_offset + return { + HardwareAxis.X: offset[0], + HardwareAxis.Y: offset[1], + HardwareAxis.Z: offset[2], + } + + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: + """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" + found_mount = Mount.LEFT + mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys()) + for k in axis_map.keys(): + if k in mounts: + found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k] + break + return found_mount + async def get_position( self, pipette_id: str, @@ -115,12 +218,26 @@ async def get_position( pipette_id=pipette_id, current_location=current_well, ) + point = await self.get_position_from_mount( + mount=pipette_location.mount.to_hw_mount(), + critical_point=pipette_location.critical_point, + fail_on_not_homed=fail_on_not_homed, + ) + return point + + async def get_position_from_mount( + self, + mount: Mount, + critical_point: Optional[CriticalPoint] = None, + fail_on_not_homed: bool = False, + ) -> Point: try: - return await self._hardware_api.gantry_position( - mount=pipette_location.mount.to_hw_mount(), - critical_point=pipette_location.critical_point, + point = await self._hardware_api.gantry_position( + mount=mount, + critical_point=critical_point, fail_on_not_homed=fail_on_not_homed, ) + return point except PositionUnknownError as e: raise MustHomeError(message=str(e), wrapping=[e]) @@ -130,8 +247,16 @@ def get_max_travel_z(self, pipette_id: str) -> float: Args: pipette_id: Pipette ID to get max travel z-height for. """ - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() - return self._hardware_api.get_instrument_max_height(mount=hw_mount) + mount = self._state_view.pipettes.get_mount(pipette_id) + return self.get_max_travel_z_from_mount(mount=mount) + + def get_max_travel_z_from_mount(self, mount: MountType) -> float: + """Get the maximum allowed z-height for any mount movement. + + Args: + mount: Mount to get max travel z-height for. + """ + return self._hardware_api.get_instrument_max_height(mount=mount.to_hw_mount()) async def move_to( self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float] @@ -151,16 +276,15 @@ async def move_to( return waypoints[-1].position - async def move_to_with_mount( - self, mount, waypoints: List[Waypoint], speed: Optional[float] + async def move_mount_to( + self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] ) -> Point: + """Move the given hardware mount to a waypoint.""" assert len(waypoints) > 0, "Must have at least one waypoint" - # hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() - for waypoint in waypoints: await self._hardware_api.move_to( - mount=hw_mount, + mount=mount, abs_position=waypoint.position, critical_point=waypoint.critical_point, speed=speed, @@ -168,6 +292,46 @@ async def move_to_with_mount( return waypoints[-1].position + async def move_axes( + self, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]] = None, + speed: Optional[float] = None, + ) -> Dict[MotorAxis, float]: + """Move a set of axes a given distance. + + Args: + axis_map: The mapping of axes to command. + relative_move: Specifying whether a relative move needs to be handled or not. + speed: Optional speed parameter for the move. + """ + try: + abs_pos_hw = self._convert_axis_map_for_hw(axis_map) + mount = self.pick_mount_from_axis_map(axis_map) + if not critical_point: + current_position = await self._hardware_api.current_position( + mount, refresh=True + ) + absolute_pos = target_axis_map_from_relative( + abs_pos_hw, current_position + ) + else: + mount_offset = self._offset_axis_map_for_mount(mount) + abs_cp_hw = self._convert_axis_map_for_hw(critical_point) + absolute_pos = target_axis_map_from_absolute( + abs_pos_hw, abs_cp_hw, mount_offset + ) + await self._hardware_api.move_axes( + position=absolute_pos, + speed=speed, + ) + + except PositionUnknownError as e: + raise MustHomeError(message=str(e), wrapping=[e]) + + current_position = await self._hardware_api.current_position(mount, refresh=True) + return {self._hardware_axis_to_motor_axis(ax): pos for ax, pos in current_position.items()} + async def move_relative( self, pipette_id: str, @@ -203,26 +367,6 @@ async def move_relative( return point - async def move_relative_with_mount( - self, mount, waypoints: List[Waypoint], speed: Optional[float] - ) -> Point: - try: - await self._hardware_api.move_rel( - mount=hw_mount, - delta=delta, - fail_on_not_homed=True, - speed=speed, - ) - point = await self._hardware_api.gantry_position( - mount=hw_mount, - critical_point=critical_point, - fail_on_not_homed=True, - ) - except PositionUnknownError as e: - raise MustHomeError(message=str(e), wrapping=[e]) - - return point - async def home(self, axes: Optional[List[MotorAxis]]) -> None: """Home the gantry.""" # TODO(mc, 2022-12-01): this is overly complicated @@ -277,6 +421,16 @@ def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis: """Transform an engine motor axis into a hardware axis.""" return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis] + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: + """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" + found_mount = Mount.LEFT + mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys()) + for k in axis_map.keys(): + if k in mounts: + found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k] + break + return found_mount + async def get_position( self, pipette_id: str, @@ -299,6 +453,22 @@ async def get_position( origin = Point(x=0, y=0, z=0) return origin + async def get_position_from_mount( + self, + mount: Mount, + critical_point: Optional[CriticalPoint] = None, + fail_on_not_homed: bool = False, + ) -> Point: + pipette = self._state_view.pipettes.get_by_mount(MountType[mount.name]) + origin_deck_point = self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None + if origin_deck_point is not None: + origin = Point( + x=origin_deck_point.x, y=origin_deck_point.y, z=origin_deck_point.z + ) + else: + origin = Point(x=0, y=0, z=0) + return origin + def get_max_travel_z(self, pipette_id: str) -> float: """Get the maximum allowed z-height for pipette movement. @@ -315,6 +485,39 @@ def get_max_travel_z(self, pipette_id: str) -> float: tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette_id) tip_length = tip.length if tip is not None else 0 return instrument_height - tip_length + + def get_max_travel_z_from_mount(self, mount: MountType) -> float: + """Get the maximum allowed z-height for mount.""" + pipette = self._state_view.pipettes.get_by_mount(mount) + if self._state_view.config.robot_type == "OT-2 Standard": + instrument_height = self._state_view.pipettes.get_instrument_max_height_ot2( + pipette.id + ) if pipette else VIRTUAL_MAX_OT3_HEIGHT + else: + instrument_height = VIRTUAL_MAX_OT3_HEIGHT + tip_length = self._state_view.tips.get_tip_length(pipette.id) if pipette else 0.0 + + return instrument_height - tip_length + + async def move_axes(self, axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]] = None, speed: Optional[float] = None) -> Dict[MotorAxis, float]: + """Move the give axes map. No-op in virtual implementation.""" + mount = self.pick_mount_from_axis_map(axis_map) + current_position = await self.get_position_from_mount(mount) + axis_map[MotorAxis.X] = axis_map.get(MotorAxis.X, 0.0) + current_position[0] + axis_map[MotorAxis.Y] = axis_map.get(MotorAxis.Y, 0.0) + current_position[1] + if mount == Mount.RIGHT: + axis_map[MotorAxis.RIGHT_Z] = axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2] + elif mount == Mount.EXTENSION: + axis_map[MotorAxis.EXTENSION_Z] = axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2] + else: + axis_map[MotorAxis.LEFT_Z] = axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2] + critical_point = critical_point or {} + return {ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items()} + + async def move_mount_to(self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]) -> Point: + """Move the hardware mount to a waypoint. No-op in virtual implementation.""" + assert len(waypoints) > 0, "Must have at least one waypoint" + return waypoints[-1].position async def move_to( self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float] diff --git a/api/src/opentrons/protocol_engine/execution/movement.py b/api/src/opentrons/protocol_engine/execution/movement.py index 7eb35e5f911..a06c5637e9d 100644 --- a/api/src/opentrons/protocol_engine/execution/movement.py +++ b/api/src/opentrons/protocol_engine/execution/movement.py @@ -144,6 +144,32 @@ async def move_to_well( return final_point + async def move_mount_to( + self, mount: MountType, destination: DeckPoint, speed: Optional[float] = None + ) -> Point: + hw_mount = mount.to_hw_mount() + await self._gantry_mover.prepare_for_mount_movement(hw_mount) + origin = await self._gantry_mover.get_position_from_mount(mount=hw_mount) + max_travel_z = self._gantry_mover.get_max_travel_z_from_mount(mount=mount) + + # calculate the movement's waypoints + waypoints = self._state_store.motion.get_movement_waypoints_to_coords( + origin=origin, + dest=Point(x=destination.x, y=destination.y, z=destination.z), + max_travel_z=max_travel_z, + direct=False, + additional_min_travel_z=None, + ) + + # move through the waypoints + final_point = await self._gantry_mover.move_mount_to( + mount=hw_mount, + waypoints=waypoints, + speed=speed, + ) + + return final_point + async def move_to_addressable_area( self, pipette_id: str, @@ -268,7 +294,7 @@ async def move_to_coordinates( ), max_travel_z=max_travel_z, direct=direct, - additional_min_travel_z=additional_min_travel_z, + additional_min_travel_z=None, ) speed = self._state_store.pipettes.get_movement_speed( From c8bab64eb1224db0eb7f530400fab321fce6cc86 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:06:55 +0300 Subject: [PATCH 07/46] feat: add robot core --- api/src/opentrons/protocol_api/core/common.py | 2 +- .../protocol_api/core/engine/protocol.py | 4 +- .../protocol_api/core/engine/robot.py | 41 +++++++++++++++---- .../core/legacy/legacy_protocol_core.py | 6 +-- .../legacy_simulator/legacy_protocol_core.py | 2 +- .../opentrons/protocol_api/core/protocol.py | 8 ++-- api/src/opentrons/protocol_api/core/robot.py | 11 +++-- .../protocol_api/protocol_context.py | 4 +- 8 files changed, 54 insertions(+), 24 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/common.py b/api/src/opentrons/protocol_api/core/common.py index 63312534995..3aff2523a1f 100644 --- a/api/src/opentrons/protocol_api/core/common.py +++ b/api/src/opentrons/protocol_api/core/common.py @@ -28,4 +28,4 @@ MagneticBlockCore = AbstractMagneticBlockCore AbsorbanceReaderCore = AbstractAbsorbanceReaderCore RobotCore = AbstractRobot -ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore, RobotCore] +ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore] diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index 483085a6e5b..e19ae188216 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -86,7 +86,6 @@ class ProtocolCore( InstrumentCore, LabwareCore, Union[ModuleCore, NonConnectedModuleCore], - RobotCore, ] ): """Protocol API core using a ProtocolEngine. @@ -536,8 +535,7 @@ def _get_module_core( ) def load_robot(self) -> RobotCore: - """Load a robot core into the RobotContext. - """ + """Load a robot core into the RobotContext.""" return RobotCore( engine_client=self._engine_client, sync_hardware_api=self._sync_hardware ) diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index 76443daa091..d73d67b7e41 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -1,13 +1,24 @@ -from typing import Optional +from typing import Optional, Dict from opentrons.hardware_control import SyncHardwareAPI -from opentrons.types import Mount, MountType, Point, AxisMapType +from opentrons.types import Mount, MountType, Point, AxisType, AxisMapType from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine.clients import SyncClient as EngineClient -from opentrons.protocol_engine.types import DeckPoint +from opentrons.protocol_engine.types import DeckPoint, MotorAxis from opentrons.protocol_api.core.robot import AbstractRobot +_AXIS_TYPE_TO_MOTOR_AXIS = { + AxisType.X: MotorAxis.X, + AxisType.Y: MotorAxis.Y, + AxisType.P_L: MotorAxis.LEFT_PLUNGER, + AxisType.P_R: MotorAxis.RIGHT_PLUNGER, + AxisType.Z_L: MotorAxis.LEFT_Z, + AxisType.Z_R: MotorAxis.RIGHT_Z, + AxisType.Z_G: MotorAxis.EXTENSION_Z, + AxisType.G: MotorAxis.EXTENSION_JAW, + AxisType.Q: MotorAxis.CLAMP_JAW_96_CHANNEL, +} class RobotCore(AbstractRobot): """Robot API core using a ProtocolEngine. @@ -24,23 +35,39 @@ def __init__( self._engine_client = engine_client self._sync_hardware_api = sync_hardware_api + def _convert_to_engine_mount(self, axis_map: AxisMapType) -> Dict[MotorAxis, float]: + return {_AXIS_TYPE_TO_MOTOR_AXIS[ax]: dist for ax, dist in axis_map.items()} + + def get_pipette_type_from_engine(self, mount: Mount) -> Optional[str]: + """Get the pipette attached to the given mount.""" + engine_mount = MountType[mount.name] + maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount) + return maybe_pipette.pipetteName if maybe_pipette else None + def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: engine_mount = MountType[mount.name] - engine_destination = DeckPoint(*destination) + engine_destination = DeckPoint(x=destination.x, y=destination.y, z=destination.z) self._engine_client.execute_command( - cmd.robot.MoveToParams(mount=engine_mount, destination=engine_destination, speed=speed) + cmd.robot.MoveToParams( + mount=engine_mount, destination=engine_destination, speed=speed + ) ) def move_axes_to( self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float] ) -> None: + axis_engine_map = self._convert_to_engine_mount(axis_map) + critical_point_engine = self._convert_to_engine_mount(critical_point) self._engine_client.execute_command( cmd.robot.MoveAxesToParams( - axis_map=axis_map, critical_point=critical_point, speed=speed + axis_map=axis_engine_map, + critical_point=critical_point_engine, + speed=speed, ) ) def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: + axis_engine_map = self._convert_to_engine_mount(axis_map) self._engine_client.execute_command( - cmd.robot.MoveAxesRelativeParams(axis_map=axis_map, speed=speed) + cmd.robot.MoveAxesRelativeParams(axis_map=axis_engine_map, speed=speed) ) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py index a6c698d2a33..38d3da55485 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py @@ -37,7 +37,7 @@ class LegacyProtocolCore( LegacyInstrumentCore, LegacyLabwareCore, legacy_module_core.LegacyModuleCore, - None, + # None, ] ): def __init__( @@ -267,8 +267,8 @@ def load_adapter( ) -> LegacyLabwareCore: """Load an adapter using its identifying parameters""" raise APIVersionError(api_element="Loading adapter") - - def load_robot(self) -> None: + + def load_robot(self) -> None: # type: ignore """Load an adapter using its identifying parameters""" raise APIVersionError(api_element="Loading robot") diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py index f7a5023cab3..d0002763b1c 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_protocol_core.py @@ -23,7 +23,7 @@ class LegacyProtocolCoreSimulator( LegacyProtocolCore, AbstractProtocol[ - LegacyInstrumentCoreSimulator, LegacyLabwareCore, LegacyModuleCore, None + LegacyInstrumentCoreSimulator, LegacyLabwareCore, LegacyModuleCore ], ): _instruments: Dict[Mount, Optional[LegacyInstrumentCoreSimulator]] # type: ignore[assignment] diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index 2a627e651a2..c367c2b4bba 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import abstractmethod, ABC -from typing import Generic, List, Optional, Union, Tuple, Dict, TYPE_CHECKING +from typing import Generic, Type, List, Optional, Union, Tuple, Dict, TYPE_CHECKING from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3 from opentrons_shared_data.pipette.types import PipetteNameType @@ -19,7 +19,7 @@ from .labware import LabwareCoreType, LabwareLoadParams from .module import ModuleCoreType from .._liquid import Liquid, LiquidClass -from .robot import RobotCoreType +from .robot import AbstractRobot from .._types import OffDeckType from ..disposal_locations import TrashBin, WasteChute @@ -28,7 +28,7 @@ class AbstractProtocol( - ABC, Generic[InstrumentCoreType, LabwareCoreType, ModuleCoreType, RobotCoreType] + ABC, Generic[InstrumentCoreType, LabwareCoreType, ModuleCoreType] ): @property @abstractmethod @@ -259,5 +259,5 @@ def get_labware_location( """Get labware parent location.""" @abstractmethod - def load_robot(self) -> RobotCoreType: + def load_robot(self) -> AbstractRobot: """Load a Robot Core context into a protocol""" diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py index 15837807ff0..d3ae029f90a 100644 --- a/api/src/opentrons/protocol_api/core/robot.py +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -5,17 +5,20 @@ class AbstractRobot(ABC): + @abstractmethod + def get_pipette_type_from_engine(self, mount: Mount) -> Optional[str]: + ... + @abstractmethod def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: ... @abstractmethod - def move_axes_to(self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float]) -> None: + def move_axes_to( + self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float] + ) -> None: ... @abstractmethod def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: ... - - -RobotCoreType = TypeVar("RobotCoreType", bound=AbstractRobot) diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index c587e8577bd..eb2bd64b54a 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -186,7 +186,9 @@ def __init__( self._params: Parameters = Parameters() self._unsubscribe_commands: Optional[Callable[[], None]] = None self._robot = RobotContext( - core=self._core.load_robot(), protocol_core=self._core, api_version=self._api_version + core=self._core.load_robot(), + protocol_core=self._core, + api_version=self._api_version, ) self.clear_commands() From 4db3a3b5435237a9ed74c8efc28d2b185c296a10 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:07:54 +0300 Subject: [PATCH 08/46] feat: add move_to, move_axes and move_axes rel functionality as well as helper methods --- .../opentrons/protocol_api/robot_context.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 8579d4c9e8b..dd98e90ba41 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -13,6 +13,7 @@ from opentrons.hardware_control import SyncHardwareAPI from opentrons.protocols.api_support.util import requires_version from opentrons.protocols.api_support.types import APIVersion +from opentrons_shared_data.pipette.types import PipetteNameType from . import validation from .core.common import ProtocolCore, RobotCore @@ -45,7 +46,9 @@ class RobotContext(publisher.CommandPublisher): """ - def __init__(self, core: RobotCore, protocol_core: ProtocolCore, api_version: APIVersion) -> None: + def __init__( + self, core: RobotCore, protocol_core: ProtocolCore, api_version: APIVersion + ) -> None: self._hardware = HardwareManager(hardware=protocol_core.get_hardware()) self._core = core self._protocol_core = protocol_core @@ -82,13 +85,13 @@ def move_to( :param speed: """ mount = validation.ensure_instrument_mount(mount) - self._core.move_to(mount, destination, speed) + self._core.move_to(mount, destination.point, speed) @requires_version(2, 20) def move_axes_to( self, axis_map: Union[AxisMapType, StringAxisMap], - critical_point: AxisMapType, + critical_point: Union[AxisMapType, StringAxisMap], speed: Optional[float] = None, ) -> None: """ @@ -100,10 +103,8 @@ def move_axes_to( :param float speed: The maximum speed with which you want to move all the axes in the axis map. """ - instrument_on_left = self._protocol_core.loaded_instruments.get("left") - is_96_channel = ( - instrument_on_left.channels == 96 if instrument_on_left else False - ) + instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT) + is_96_channel = instrument_on_left == PipetteNameType.P1000_96 axis_map = validation.ensure_axis_map_type( axis_map, self._protocol_core.robot_type, is_96_channel ) @@ -130,10 +131,9 @@ def move_axes_relative( :param float speed: The maximum speed with which you want to move all the axes in the axis map. """ - instrument_on_left = self._protocol_core.loaded_instruments.get("left") - is_96_channel = ( - instrument_on_left.channels == 96 if instrument_on_left else False - ) + instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT) + is_96_channel = instrument_on_left == PipetteNameType.P1000_96 + axis_map = validation.ensure_axis_map_type( axis_map, self._protocol_core.robot_type, is_96_channel ) @@ -212,10 +212,9 @@ def build_axis_map(self, axis_map: StringAxisMap) -> AxisMapType: Note that capitalization does not matter. """ - instrument_on_left = self._protocol_core.loaded_instruments.get("left") - is_96_channel = ( - instrument_on_left.channels == 96 if instrument_on_left else False - ) + instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT) + is_96_channel = instrument_on_left == PipetteNameType.P1000_96 + return validation.ensure_axis_map_type( axis_map, self._protocol_core.robot_type, is_96_channel ) From 86dcd5bb5e61c18611aa1ee81c213cf7f6e3038a Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:08:46 +0300 Subject: [PATCH 09/46] feat: add robot MoveTo engine command --- .../protocol_engine/commands/robot/move_to.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_to.py index 937a75a9804..a2713982d60 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_to.py @@ -1,8 +1,9 @@ +"""Command models for moving any robot mount to a destination point.""" +from __future__ import annotations from typing import Literal, Type, Optional, TYPE_CHECKING from pydantic import BaseModel, Field from opentrons.types import MountType -from opentrons.hardware_control import HardwareControlAPI from opentrons.hardware_control.protocols.types import FlexRobotType from ..pipetting_common import DestinationPositionResult @@ -26,12 +27,18 @@ class MoveToParams(BaseModel): """Payload required to move to a destination position.""" - mount: MountType + mount: MountType = Field( + ..., + description="The mount to move to the destination point.", + ) destination: DeckPoint = Field( ..., description="X, Y and Z coordinates in mm from deck's origin location (left-front-bottom corner of work space)", ) - speed: float + speed: Optional[float] = Field( + default=None, + description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + ) class MoveToResult(DestinationPositionResult): @@ -45,18 +52,17 @@ class MoveToImplementation( ): """MoveTo command implementation.""" - def __init__(self, movement: MovementHandler, hardware_api: HardwareControlAPI, **kwargs: object) -> None: + def __init__( + self, + movement: MovementHandler, + **kwargs: object, + ) -> None: self._movement = movement - self._hardware_api = hardware_api async def execute(self, params: MoveToParams) -> SuccessData[MoveToResult, None]: - if self._hardware_api.get_robot_type() == FlexRobotType: - x, y, z = self._movement.move_axes(axis_map=params.axis_map, speed=params.speed, relative_move=True) - else: - x, y, z = self._movement.move_to(mount=params.mount, speed=params.speed) - # x, y, z = self._hardware_api.move_to( - # params.mount, params.destination, params.velocity - # ) + x, y, z = await self._movement.move_mount_to( + mount=params.mount, destination=params.destination, speed=params.speed + ) return SuccessData( public=MoveToResult(position=DeckPoint(x=x, y=y, z=z)), private=None, From c709ade1e8c29237d1245936ed3570cb5b98fc24 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:09:04 +0300 Subject: [PATCH 10/46] feat: add robot moveAxesRelative engine command --- .../commands/robot/move_axes_relative.py | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py index 14864a9da56..2bb74160379 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py @@ -1,9 +1,12 @@ +"""Command models for moving any robot axis relative.""" +from __future__ import annotations from typing import Literal, Type, Optional, TYPE_CHECKING from pydantic import BaseModel, Field -from opentrons.types import AxisMapType from opentrons.hardware_control import HardwareControlAPI -from opentrons.hardware_control.protocols.types import FlexRobotType +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from .common import MotorAxisMapType, DestinationRobotPositionResult from ..command import ( AbstractCommandImpl, @@ -14,7 +17,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.execution import MovementHandler + from opentrons.protocol_engine.execution import GantryMover MoveAxesRelativeCommandType = Literal["robot/moveAxesRelative"] @@ -23,11 +26,16 @@ class MoveAxesRelativeParams(BaseModel): """Payload required to move axes relative to position.""" - axis_map: AxisMapType = Field(..., description="A dictionary mapping axes to relative movements in mm.") - speed: float + axis_map: MotorAxisMapType = Field( + ..., description="A dictionary mapping axes to relative movements in mm." + ) + speed: Optional[float] = Field( + default=None, + description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + ) -class MoveAxesRelativeResult(BaseModel): +class MoveAxesRelativeResult(DestinationRobotPositionResult): """Result data from the execution of a MoveAxesRelative command.""" pass @@ -40,16 +48,29 @@ class MoveAxesRelativeImplementation( ): """MoveAxesRelative command implementation.""" - def __init__(self, hardware_api: HardwareControlAPI, **kwargs: object) -> None: + def __init__( + self, + gantry_mover: GantryMover, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._gantry_mover = gantry_mover self._hardware_api = hardware_api async def execute( self, params: MoveAxesRelativeParams ) -> SuccessData[MoveAxesRelativeResult, None]: - if self._hardware_api.get_robot_type() == FlexRobotType: - self._movement.move_axes(axis_map=params.axis_map, speed=params.speed, relative_move=True) - else: - self._movement.move_relative(axis_map=params.axis_map, speed=params.speed) + # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller + # and then we can remove this validation. + ensure_ot3_hardware(self._hardware_api) + + current_position = await self._gantry_mover.move_axes( + axis_map=params.axis_map, speed=params.speed + ) + return SuccessData( + public=MoveAxesRelativeResult(position=current_position), + private=None, + ) class MoveAxesRelative( From 230767d931b08b136f7a20fda810c811850b83a2 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:10:05 +0300 Subject: [PATCH 11/46] feat: add robot moveAxesTo engine command --- .../commands/robot/move_axes_to.py | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index d47e10fc720..474306f59ca 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -1,10 +1,12 @@ -from typing import Literal, Dict, Optional, Type, TYPE_CHECKING +"""Command models for moving any robot axis to an absolute position.""" +from __future__ import annotations +from typing import Literal, Optional, Type, TYPE_CHECKING from pydantic import Field, BaseModel from opentrons.hardware_control import HardwareControlAPI -from opentrons.hardware_control.protocols.types import FlexRobotType - -from ..pipetting_common import DestinationPositionResult +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from .common import MotorAxisMapType, DestinationRobotPositionResult from ..command import ( AbstractCommandImpl, BaseCommand, @@ -12,10 +14,9 @@ SuccessData, ) from ...errors.error_occurrence import ErrorOccurrence -from ...types import DeckPoint if TYPE_CHECKING: - from opentrons.protocol_engine.execution.movement import MovementHandler + from opentrons.protocol_engine.execution import GantryMover MoveAxesToCommandType = Literal["robot/moveAxesTo"] @@ -24,19 +25,19 @@ class MoveAxesToParams(BaseModel): """Payload required to move axes to absolute position.""" - axis_map: Dict[str, float] = Field( + axis_map: MotorAxisMapType = Field( ..., description="The specified axes to move to an absolute deck position with." ) - critical_point: Dict[str, float] = Field( + critical_point: MotorAxisMapType = Field( ..., description="The critical point to move the mount with." ) - velocity: Optional[float] = Field( + speed: Optional[float] = Field( default=None, description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", ) -class MoveAxesToResult(DestinationPositionResult): +class MoveAxesToResult(DestinationRobotPositionResult): """Result data from the execution of a MoveAxesTo command.""" pass @@ -49,26 +50,24 @@ class MoveAxesToImplementation( def __init__( self, - # movement: MovementHandler, + gantry_mover: GantryMover, hardware_api: HardwareControlAPI, - **kwargs: object + **kwargs: object, ) -> None: - # self._movement = movement + self._gantry_mover = gantry_mover self._hardware_api = hardware_api async def execute( self, params: MoveAxesToParams ) -> SuccessData[MoveAxesToResult, None]: - if self._hardware_api.get_robot_type() == FlexRobotType: - self._movement.move_axes(axis_map=params.axis_map, speed=params.speed, relative_move=True) - else: - self._movement.move_to(mount=params.mount, speed=params.speed) - # x, y, z = self._movement.move_to_with_mount( - # params.axis_map, params.critical_point, params.velocity - # ) - x, y, z = (0, 0, 0) + # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller + # and then we can remove this validation. + ensure_ot3_hardware(self._hardware_api) + current_position = await self._gantry_mover.move_axes( + axis_map=params.axis_map, speed=params.speed, critical_point=params.critical_point + ) return SuccessData( - public=MoveAxesToResult(position=DeckPoint(x=x, y=y, z=z)), + public=MoveAxesToResult(position=current_position), private=None, ) From de7688c127c0a40f5337f3017ed42e18ba3de6fd Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:10:22 +0300 Subject: [PATCH 12/46] command union stuff --- .../protocol_engine/commands/__init__.py | 2 +- .../protocol_engine/commands/command_unions.py | 2 +- .../protocol_engine/commands/robot/__init__.py | 4 ++-- .../protocol_engine/commands/robot/common.py | 15 +++++++++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 api/src/opentrons/protocol_engine/commands/robot/common.py diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index b06e7332072..8e1e91bec50 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -554,7 +554,6 @@ "LoadLiquidResult", "LoadLiquidCommandType", # hardware control command models - "robot", # hardware module command bundles "absorbance_reader", "heater_shaker", @@ -565,6 +564,7 @@ "calibration", # unsafe command bundle "unsafe", + "robot", # configure pipette volume command bundle "ConfigureForVolume", "ConfigureForVolumeCreate", diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 4b9a74fb737..30f00b4bb95 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -567,7 +567,7 @@ unsafe.UnsafePlaceLabwareCommandType, robot.MoveAxesRelativeCommandType, robot.MoveAxesToCommandType, - robot.MoveToCommandType + robot.MoveToCommandType, ] CommandCreate = Annotated[ diff --git a/api/src/opentrons/protocol_engine/commands/robot/__init__.py b/api/src/opentrons/protocol_engine/commands/robot/__init__.py index 7221150d900..5d5fc691e5f 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/robot/__init__.py @@ -23,8 +23,8 @@ ) __all__ = [ - # robot/moveTo - "MoveTo", + # robot/moveTo + "MoveTo", "MoveToCreate", "MoveToParams", "MoveToResult", diff --git a/api/src/opentrons/protocol_engine/commands/robot/common.py b/api/src/opentrons/protocol_engine/commands/robot/common.py new file mode 100644 index 00000000000..e5401e3045f --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/common.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel, Field + +from typing import Dict +from opentrons.protocol_engine.types import MotorAxis + + +MotorAxisMapType = Dict[MotorAxis, float] +default_position = {ax: 0.0 for ax in MotorAxis} + + +class DestinationRobotPositionResult(BaseModel): + position: MotorAxisMapType = Field( + default=default_position, + description="The position of all axes on the robot. If no mount was provided, the last moved mount is used to determine the location.", + ) From edbca791f28ddfb2686ccda9cd3dcec59ca01bde Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:10:46 +0300 Subject: [PATCH 13/46] add validation and shared types --- api/src/opentrons/protocol_api/validation.py | 25 +++++++++----------- api/src/opentrons/types.py | 24 +++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index c27bb74a10f..442f9487129 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -206,17 +206,13 @@ def ensure_axis_map_type( raise IncorrectAxisError( "Please provide an `axis_map` with only string or only AxisType keys." ) - if robot_type == "OT-2 Standard": - if list(key_type)[0] is AxisType and any( - k not in AxisType.ot2_axes() for k in axis_map_keys - ): - raise IncorrectAxisError( - f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" - ) - if list(key_type)[0] is str and any( - k.upper() not in [axis.value for axis in AxisType.ot2_axes()] - for k in axis_map_keys - ): + if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], AxisType): + if any(k not in AxisType.ot2_axes() for k in axis_map_keys): + raise IncorrectAxisError( + f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" + ) + if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], str): + if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [attr-defined] raise IncorrectAxisError( f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" ) @@ -233,10 +229,11 @@ def ensure_axis_map_type( "A 96 channel is not attached. The clamp `Q` motor does not exist." ) - if isinstance(axis_map_keys[0], AxisType): - return axis_map + if all(isinstance(k, AxisType) for k in axis_map_keys): + return_map: AxisMapType = axis_map # type: ignore + return return_map try: - return {AxisType[k.upper()]: v for k, v in axis_map.items()} + return {AxisType[k.upper()]: v for k, v in axis_map.items()} # type: ignore [union-attr] except KeyError as e: raise IncorrectAxisError(f"{e} is not a supported `AxisMapType`") diff --git a/api/src/opentrons/types.py b/api/src/opentrons/types.py index b532c8cfc55..41a1ac57402 100644 --- a/api/src/opentrons/types.py +++ b/api/src/opentrons/types.py @@ -275,21 +275,21 @@ class AxisType(enum.Enum): @classmethod def axis_for_mount(cls, mount: Mount) -> "AxisType": - if mount == Mount.LEFT: - return cls.Z_L - elif mount == Mount.RIGHT: - return cls.Z_R - elif mount == Mount.EXTENSION: - return cls.Z_G + map_axis_to_mount = { + Mount.LEFT: cls.Z_L, + Mount.RIGHT: cls.Z_R, + Mount.EXTENSION: cls.Z_G, + } + return map_axis_to_mount[mount] @classmethod def mount_for_axis(cls, axis: "AxisType") -> Mount: - if axis == cls.Z_L: - return Mount.LEFT - elif axis == cls.Z_R: - return Mount.RIGHT - elif axis == cls.Z_G: - return Mount.EXTENSION + map_mount_to_axis = { + cls.Z_L: Mount.LEFT, + cls.Z_R: Mount.RIGHT, + cls.Z_G: Mount.EXTENSION, + } + return map_mount_to_axis[axis] @classmethod def ot2_axes(cls) -> List["AxisType"]: From 720f0a26b6983f01d2111c7b4b4529071e1fdb72 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 17 Sep 2024 17:11:33 +0300 Subject: [PATCH 14/46] adding tests --- .../protocol_api/test_robot_context.py | 22 ++-- .../opentrons/protocol_api/test_validation.py | 101 +++++++++++++++++- .../commands/robot/__init__.py | 0 .../robot/test_move_axes_relative_to.py | 4 + .../commands/robot/test_move_axes_to.py | 63 +++++++++++ .../commands/robot/test_move_to.py | 51 +++++++++ 6 files changed, 234 insertions(+), 7 deletions(-) create mode 100644 api/tests/opentrons/protocol_engine/commands/robot/__init__.py create mode 100644 api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py create mode 100644 api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py create mode 100644 api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py index 5b1c6c67afc..7918a83294b 100644 --- a/api/tests/opentrons/protocol_api/test_robot_context.py +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -15,6 +15,7 @@ from opentrons.protocols.api_support.types import APIVersion from opentrons.protocol_api.core.common import ProtocolCore, RobotCore from opentrons.protocol_api import RobotContext, ModuleContext, MAX_SUPPORTED_VERSION +from opentrons.protocol_api.deck import Deck @pytest.fixture @@ -30,25 +31,34 @@ def api_version() -> APIVersion: @pytest.fixture -def mock_protocol(decoy: Decoy, mock_core: RobotCore) -> ProtocolCore: +def mock_deck(decoy: Decoy) -> Deck: + deck = decoy.mock(cls=Deck) + decoy.when(deck.get_slot_center(DeckSlotName.SLOT_D1)).then_return(Point(3, 3, 3)) + return deck + + +@pytest.fixture +def mock_protocol(decoy: Decoy, mock_deck: Deck, mock_core: RobotCore) -> ProtocolCore: """Get a mock protocol implementation core without a 96 channel attached.""" protocol_core = decoy.mock(cls=ProtocolCore) decoy.when(protocol_core.robot_type).then_return("OT-3 Standard") - decoy.when(protocol_core.loaded_instruments).then_return({}) decoy.when(protocol_core.load_robot()).then_return(mock_core) - decoy.when(protocol_core.deck.get_slot_center(DeckSlotName.SLOT_D1)).then_return( - Point(3, 3, 3) - ) + decoy.when(protocol_core._deck).then_return(mock_deck) return protocol_core @pytest.fixture def subject( + decoy: Decoy, + mock_core: RobotCore, mock_protocol: ProtocolCore, api_version: APIVersion, ) -> RobotContext: """Get a RobotContext test subject with its dependencies mocked out.""" - return RobotContext(api_version=api_version, protocol_core=mock_protocol) + decoy.when(mock_core.get_pipette_type_from_engine(Mount.LEFT)).then_return(None) + return RobotContext( + core=mock_core, api_version=api_version, protocol_core=mock_protocol + ) @pytest.mark.parametrize( diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 2a2ed6375b0..56453e09fd0 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -13,7 +13,16 @@ from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.robot.types import RobotType -from opentrons.types import Mount, DeckSlotName, StagingSlotName, Location, Point +from opentrons.types import ( + Mount, + DeckSlotName, + AxisType, + AxisMapType, + StringAxisMap, + StagingSlotName, + Location, + Point, +) from opentrons.hardware_control.modules.types import ( ModuleModel, MagneticModuleModel, @@ -559,3 +568,93 @@ def test_validate_last_location_with_labware(decoy: Decoy) -> None: result = subject.validate_location(location=None, last_location=input_last_location) assert result == subject.PointTarget(location=input_last_location, in_place=True) + + +@pytest.mark.parametrize( + argnames=["axis_map", "robot_type", "is_96_channel", "expected_axis_map"], + argvalues=[ + ( + {"x": 100, "Y": 50, "z_g": 80}, + "OT-3 Standard", + True, + {AxisType.X: 100, AxisType.Y: 50, AxisType.Z_G: 80}, + ), + ({"z_r": 80}, "OT-2 Standard", False, {AxisType.Z_R: 80}), + ( + {"Z_L": 19, "P_L": 20}, + "OT-2 Standard", + False, + {AxisType.Z_L: 19, AxisType.P_L: 20}, + ), + ({"Q": 5}, "OT-3 Standard", True, {AxisType.Q: 5}), + ], +) +def test_ensure_axis_map_type_success( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool, + expected_axis_map: AxisMapType, +) -> None: + res = subject.ensure_axis_map_type(axis_map, robot_type, is_96_channel) + assert res == expected_axis_map + + +@pytest.mark.parametrize( + argnames=["axis_map", "robot_type", "is_96_channel", "error_message"], + argvalues=[ + ( + {AxisType.X: 100, "y": 50}, + "OT-3 Standard", + True, + "Please provide an `axis_map` with only string or only AxisType keys", + ), + ( + {AxisType.Z_R: 60}, + "OT-3 Standard", + True, + "A 96 channel is attached. You cannot move the `Z_R` mount.", + ), + ( + {"Z_G": 19, "P_L": 20}, + "OT-2 Standard", + False, + "An OT-2 Robot only accepts the following axes ", + ), + ( + {"Q": 5}, + "OT-3 Standard", + False, + "A 96 channel is not attached. The clamp `Q` motor does not exist.", + ), + ], +) +def test_ensure_axis_map_type_failure( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool, + error_message: str, +) -> None: + with pytest.raises(subject.IncorrectAxisError, match=error_message): + subject.ensure_axis_map_type(axis_map, robot_type, is_96_channel) + + +@pytest.mark.parametrize( + argnames=["axis_map", "robot_type", "error_message"], + argvalues=[ + ( + {AxisType.X: 100, AxisType.P_L: 50}, + "OT-3 Standard", + "A critical point only accepts Flex gantry axes which are ", + ), + ( + {AxisType.Z_G: 60}, + "OT-2 Standard", + "A critical point only accepts OT-2 gantry axes which are ", + ), + ], +) +def test_ensure_only_gantry_axis_map_type( + axis_map: AxisMapType, robot_type: RobotType, error_message: str +) -> None: + with pytest.raises(subject.IncorrectAxisError, match=error_message): + subject.ensure_only_gantry_axis_map_type(axis_map, robot_type) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/__init__.py b/api/tests/opentrons/protocol_engine/commands/robot/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py new file mode 100644 index 00000000000..9c58c71f52f --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py @@ -0,0 +1,4 @@ +from decoy import Decoy + +async def test_move_axes_relative_to_success() -> None: + return None diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py new file mode 100644 index 00000000000..eafd84fb60b --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py @@ -0,0 +1,63 @@ +"""Test robot.move-axes-to commands.""" +from decoy import Decoy + +from opentrons.hardware_control import HardwareControlAPI + +from opentrons.protocol_engine.execution import MovementHandler, GantryMover +from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.types import MotorAxis +from opentrons.hardware_control.protocols.types import FlexRobotType, OT2RobotType +from opentrons.types import Point, MountType + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.move_axes_to import ( + MoveAxesToParams, + MoveAxesToResult, + MoveAxesToImplementation, +) + + +async def test_move_to_implementation( + decoy: Decoy, + state_view: StateView, + gantry_mover: GantryMover, + movement: MovementHandler, + hardware_api: HardwareControlAPI +) -> None: + """Test the `robot.moveAxesTo` implementation. + + It should call `MovementHandler.move_mount_to` with the + correct coordinates. + """ + subject = MoveAxesToImplementation( + state_view=state_view, + gantry_mover=gantry_mover, + movement=movement, + hardware_api=hardware_api, + ) + + params = MoveAxesToParams( + axis_map={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}, + critical_point={MotorAxis.X: 1, MotorAxis.Y: 1, MotorAxis.EXTENSION_Z: 0}, + speed=567.8, + ) + + # OT 2 shape + decoy.when(hardware_api.get_robot_type()).then_return(OT2RobotType) + + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveAxesToResult(position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}), + private=None, + ) + + # Flex shape + decoy.when(hardware_api.get_robot_type()).then_return(FlexRobotType) + + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveAxesToResult(position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}), + private=None, + ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py new file mode 100644 index 00000000000..903f14248bc --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py @@ -0,0 +1,51 @@ +"""Test robot.move-to commands.""" +from decoy import Decoy + +from opentrons.protocol_engine.execution import MovementHandler +from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.types import DeckPoint +from opentrons.types import Point, MountType + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.move_to import ( + MoveToParams, + MoveToResult, + MoveToImplementation, +) + + +async def test_move_to_implementation( + decoy: Decoy, + state_view: StateView, + movement: MovementHandler, +) -> None: + """Test the `robot.moveTo` implementation. + + It should call `MovementHandler.move_mount_to` with the + correct coordinates. + """ + subject = MoveToImplementation( + state_view=state_view, + movement=movement, + ) + + params = MoveToParams( + mount=MountType.LEFT, + destination=DeckPoint(x=1.11, y=2.22, z=3.33), + speed=567.8, + ) + + decoy.when( + await movement.move_mount_to( + mount=MountType.LEFT, + destination=DeckPoint(x=1.11, y=2.22, z=3.33), + speed=567.8, + ) + ).then_return(Point(x=4.44, y=5.55, z=6.66)) + + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveToResult(position=DeckPoint(x=4.44, y=5.55, z=6.66)), + private=None, + ) From 9d87b5574e1eae780d0372d2cf8d8133a615c77b Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Wed, 18 Sep 2024 10:17:15 -0400 Subject: [PATCH 15/46] some lint fixes for typing conflicts in robot_context --- .../hardware_control/motion_utilities.py | 8 ++- .../protocol_api/core/engine/robot.py | 5 +- .../core/legacy/legacy_protocol_core.py | 2 +- .../opentrons/protocol_api/robot_context.py | 9 ++- api/src/opentrons/protocol_api/validation.py | 12 ++-- .../commands/robot/move_axes_to.py | 4 +- .../protocol_engine/execution/gantry_mover.py | 56 +++++++++++++------ .../robot/test_move_axes_relative_to.py | 3 +- .../commands/robot/test_move_axes_to.py | 10 +++- 9 files changed, 74 insertions(+), 35 deletions(-) diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index 245de57f9f9..7fd8890dd00 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -136,9 +136,11 @@ def target_axis_map_from_relative( ) -> "OrderedDict[Axis, float]": """Create a target position for all specified machine axes.""" target_position = OrderedDict( - ((ax, current_position[ax] + axis_map[ax]) - for ax in EMPTY_ORDERED_DICT.keys() - if ax in axis_map.keys()) + ( + (ax, current_position[ax] + axis_map[ax]) + for ax in EMPTY_ORDERED_DICT.keys() + if ax in axis_map.keys() + ) ) return target_position diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index d73d67b7e41..24ff0063eaa 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -20,6 +20,7 @@ AxisType.Q: MotorAxis.CLAMP_JAW_96_CHANNEL, } + class RobotCore(AbstractRobot): """Robot API core using a ProtocolEngine. @@ -46,7 +47,9 @@ def get_pipette_type_from_engine(self, mount: Mount) -> Optional[str]: def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: engine_mount = MountType[mount.name] - engine_destination = DeckPoint(x=destination.x, y=destination.y, z=destination.z) + engine_destination = DeckPoint( + x=destination.x, y=destination.y, z=destination.z + ) self._engine_client.execute_command( cmd.robot.MoveToParams( mount=engine_mount, destination=engine_destination, speed=speed diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py index 38d3da55485..24fa3d2aab6 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py @@ -268,7 +268,7 @@ def load_adapter( """Load an adapter using its identifying parameters""" raise APIVersionError(api_element="Loading adapter") - def load_robot(self) -> None: # type: ignore + def load_robot(self) -> None: # type: ignore """Load an adapter using its identifying parameters""" raise APIVersionError(api_element="Loading robot") diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index dd98e90ba41..5f4d34a9929 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -18,6 +18,7 @@ from . import validation from .core.common import ProtocolCore, RobotCore from .module_contexts import ModuleContext +from .labware import Labware class HardwareManager(NamedTuple): @@ -168,7 +169,7 @@ def axis_coordinates_for( mount_axis = AxisType.axis_for_mount(mount) if location: - loc: Point + loc: Union[Point, Labware, None] if isinstance(location, ModuleContext): loc = location.labware if not loc: @@ -176,15 +177,17 @@ def axis_coordinates_for( top_of_labware = loc.wells()[0].top() loc = top_of_labware.point return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} - elif isinstance(location, DeckLocation): + elif location is DeckLocation: + assert not isinstance(location, Location) slot_name = validation.ensure_and_convert_deck_slot( location, api_version=self._api_version, robot_type=self._protocol_core.robot_type, ) - loc = self._protocol_core.deck.get_slot_center(slot_name) + loc = self._protocol_core.get_slot_center(slot_name) return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} else: + assert isinstance(location, Location) loc = location.point return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} else: diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 442f9487129..9baba2268ce 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -208,11 +208,11 @@ def ensure_axis_map_type( ) if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], AxisType): if any(k not in AxisType.ot2_axes() for k in axis_map_keys): - raise IncorrectAxisError( - f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" - ) + raise IncorrectAxisError( + f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" + ) if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], str): - if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [attr-defined] + if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [attr-defined] raise IncorrectAxisError( f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" ) @@ -230,10 +230,10 @@ def ensure_axis_map_type( ) if all(isinstance(k, AxisType) for k in axis_map_keys): - return_map: AxisMapType = axis_map # type: ignore + return_map: AxisMapType = axis_map # type: ignore return return_map try: - return {AxisType[k.upper()]: v for k, v in axis_map.items()} # type: ignore [union-attr] + return {AxisType[k.upper()]: v for k, v in axis_map.items()} # type: ignore [union-attr] except KeyError as e: raise IncorrectAxisError(f"{e} is not a supported `AxisMapType`") diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index 474306f59ca..992b1738989 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -64,7 +64,9 @@ async def execute( # and then we can remove this validation. ensure_ot3_hardware(self._hardware_api) current_position = await self._gantry_mover.move_axes( - axis_map=params.axis_map, speed=params.speed, critical_point=params.critical_point + axis_map=params.axis_map, + speed=params.speed, + critical_point=params.critical_point, ) return SuccessData( public=MoveAxesToResult(position=current_position), diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 54cb25ecbec..cd1439cb41e 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -177,7 +177,7 @@ def _offset_axis_map_for_mount(self, mount: Mount) -> Dict[HardwareAxis, float]: self._state_view.config.robot_type == "OT-3 Standard" and mount == Mount.EXTENSION ): - offset = self._hardware_api.config.gripper_mount_offset # type: ignore [union-attr] + offset = self._hardware_api.config.gripper_mount_offset # type: ignore [union-attr] return { HardwareAxis.X: offset[0], HardwareAxis.Y: offset[1], @@ -329,8 +329,13 @@ async def move_axes( except PositionUnknownError as e: raise MustHomeError(message=str(e), wrapping=[e]) - current_position = await self._hardware_api.current_position(mount, refresh=True) - return {self._hardware_axis_to_motor_axis(ax): pos for ax, pos in current_position.items()} + current_position = await self._hardware_api.current_position( + mount, refresh=True + ) + return { + self._hardware_axis_to_motor_axis(ax): pos + for ax, pos in current_position.items() + } async def move_relative( self, @@ -460,7 +465,9 @@ async def get_position_from_mount( fail_on_not_homed: bool = False, ) -> Point: pipette = self._state_view.pipettes.get_by_mount(MountType[mount.name]) - origin_deck_point = self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None + origin_deck_point = ( + self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None + ) if origin_deck_point is not None: origin = Point( x=origin_deck_point.x, y=origin_deck_point.y, z=origin_deck_point.z @@ -485,36 +492,53 @@ def get_max_travel_z(self, pipette_id: str) -> float: tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette_id) tip_length = tip.length if tip is not None else 0 return instrument_height - tip_length - + def get_max_travel_z_from_mount(self, mount: MountType) -> float: """Get the maximum allowed z-height for mount.""" pipette = self._state_view.pipettes.get_by_mount(mount) if self._state_view.config.robot_type == "OT-2 Standard": - instrument_height = self._state_view.pipettes.get_instrument_max_height_ot2( - pipette.id - ) if pipette else VIRTUAL_MAX_OT3_HEIGHT + instrument_height = ( + self._state_view.pipettes.get_instrument_max_height_ot2(pipette.id) + if pipette + else VIRTUAL_MAX_OT3_HEIGHT + ) else: instrument_height = VIRTUAL_MAX_OT3_HEIGHT - tip_length = self._state_view.tips.get_tip_length(pipette.id) if pipette else 0.0 + tip_length = ( + self._state_view.tips.get_tip_length(pipette.id) if pipette else 0.0 + ) return instrument_height - tip_length - - async def move_axes(self, axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]] = None, speed: Optional[float] = None) -> Dict[MotorAxis, float]: + + async def move_axes( + self, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]] = None, + speed: Optional[float] = None, + ) -> Dict[MotorAxis, float]: """Move the give axes map. No-op in virtual implementation.""" mount = self.pick_mount_from_axis_map(axis_map) current_position = await self.get_position_from_mount(mount) axis_map[MotorAxis.X] = axis_map.get(MotorAxis.X, 0.0) + current_position[0] axis_map[MotorAxis.Y] = axis_map.get(MotorAxis.Y, 0.0) + current_position[1] if mount == Mount.RIGHT: - axis_map[MotorAxis.RIGHT_Z] = axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2] + axis_map[MotorAxis.RIGHT_Z] = ( + axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2] + ) elif mount == Mount.EXTENSION: - axis_map[MotorAxis.EXTENSION_Z] = axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2] + axis_map[MotorAxis.EXTENSION_Z] = ( + axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2] + ) else: - axis_map[MotorAxis.LEFT_Z] = axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2] + axis_map[MotorAxis.LEFT_Z] = ( + axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2] + ) critical_point = critical_point or {} return {ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items()} - - async def move_mount_to(self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float]) -> Point: + + async def move_mount_to( + self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: """Move the hardware mount to a waypoint. No-op in virtual implementation.""" assert len(waypoints) > 0, "Must have at least one waypoint" return waypoints[-1].position diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py index 9c58c71f52f..161c731be83 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py @@ -1,4 +1,5 @@ from decoy import Decoy + async def test_move_axes_relative_to_success() -> None: - return None + return None diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py index eafd84fb60b..57f77797153 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py @@ -22,7 +22,7 @@ async def test_move_to_implementation( state_view: StateView, gantry_mover: GantryMover, movement: MovementHandler, - hardware_api: HardwareControlAPI + hardware_api: HardwareControlAPI, ) -> None: """Test the `robot.moveAxesTo` implementation. @@ -48,7 +48,9 @@ async def test_move_to_implementation( result = await subject.execute(params=params) assert result == SuccessData( - public=MoveAxesToResult(position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}), + public=MoveAxesToResult( + position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} + ), private=None, ) @@ -58,6 +60,8 @@ async def test_move_to_implementation( result = await subject.execute(params=params) assert result == SuccessData( - public=MoveAxesToResult(position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}), + public=MoveAxesToResult( + position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} + ), private=None, ) From 6acdb01b8dcaa59819958585b347ff9aefa2c476 Mon Sep 17 00:00:00 2001 From: CaseyBatten Date: Wed, 18 Sep 2024 12:13:03 -0400 Subject: [PATCH 16/46] cleanup and location error case --- api/src/opentrons/protocol_api/robot_context.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 5f4d34a9929..b0bfaf546c6 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -177,8 +177,7 @@ def axis_coordinates_for( top_of_labware = loc.wells()[0].top() loc = top_of_labware.point return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} - elif location is DeckLocation: - assert not isinstance(location, Location) + elif location is DeckLocation and not isinstance(location, Location): slot_name = validation.ensure_and_convert_deck_slot( location, api_version=self._api_version, @@ -186,10 +185,14 @@ def axis_coordinates_for( ) loc = self._protocol_core.get_slot_center(slot_name) return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} - else: + elif isinstance(location, Location): assert isinstance(location, Location) loc = location.point return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + else: + raise ValueError( + "Location parameter must be a Module, Deck Location, or Location type." + ) else: raise TypeError("You must specify a location to move to.") From a2e2b266650b89a79ec68b049484525a02f0fbc8 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 8 Oct 2024 16:42:54 +0300 Subject: [PATCH 17/46] add logging and fix Q axis --- api/src/opentrons/hardware_control/ot3api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index e57f5d2abbd..576f3cdf7bb 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -1227,7 +1227,9 @@ async def move_axes( # noqa: C901 message=f"{axis} is not present", detail={"axis": str(axis)} ) + self._log.info(f"Attempting to move {position} with speed {speed}.") if not self._backend.check_encoder_status(list(position.keys())): + self._log.info("Calling home in move_axes") await self.home() self._assert_motor_ok(list(position.keys())) From 7e70f4bbd0d3106ce7036a7ccf181896ec28b1f8 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 8 Oct 2024 17:46:52 +0300 Subject: [PATCH 18/46] test: relative moves are moving in absolute --- .../protocol_engine/execution/gantry_mover.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index cd1439cb41e..8cf995635a1 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -1,4 +1,6 @@ """Gantry movement wrapper for hardware and simulation based movement.""" +from logging import getLogger + from typing import Optional, List, Dict from typing_extensions import Protocol as TypingProtocol @@ -18,6 +20,8 @@ from ..types import MotorAxis, CurrentWell from ..errors import MustHomeError, InvalidAxisForRobotType +log = getLogger(__name__) + _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = { MotorAxis.X: HardwareAxis.X, @@ -306,20 +310,23 @@ async def move_axes( speed: Optional speed parameter for the move. """ try: - abs_pos_hw = self._convert_axis_map_for_hw(axis_map) + pos_hw = self._convert_axis_map_for_hw(axis_map) mount = self.pick_mount_from_axis_map(axis_map) if not critical_point: current_position = await self._hardware_api.current_position( mount, refresh=True ) + log.info(f"The current position of the robot is: {current_position}.") absolute_pos = target_axis_map_from_relative( - abs_pos_hw, current_position + pos_hw, current_position ) + log.info(f"The absolute position is: {absolute_pos} and hw pos map is {absolute_pos}.") else: + log.info(f"Incorrectly in abs move") mount_offset = self._offset_axis_map_for_mount(mount) abs_cp_hw = self._convert_axis_map_for_hw(critical_point) absolute_pos = target_axis_map_from_absolute( - abs_pos_hw, abs_cp_hw, mount_offset + pos_hw, abs_cp_hw, mount_offset ) await self._hardware_api.move_axes( position=absolute_pos, From 9e87c7683175f6c00caa50c138429870a3172a51 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 8 Oct 2024 21:17:56 +0300 Subject: [PATCH 19/46] refactor: make critical point optional --- api/src/opentrons/protocol_api/core/engine/robot.py | 6 ++++-- api/src/opentrons/protocol_api/core/robot.py | 2 +- api/src/opentrons/protocol_api/robot_context.py | 9 +++++---- .../protocol_engine/commands/robot/move_axes_to.py | 7 ++++--- .../protocol_engine/execution/gantry_mover.py | 11 ++++++----- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index 24ff0063eaa..e38ad634c11 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -57,10 +57,12 @@ def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> N ) def move_axes_to( - self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float] + self, axis_map: AxisMapType, critical_point: Optional[AxisMapType], speed: Optional[float] ) -> None: axis_engine_map = self._convert_to_engine_mount(axis_map) - critical_point_engine = self._convert_to_engine_mount(critical_point) + critical_point_engine = critical_point + if critical_point: + critical_point_engine = self._convert_to_engine_mount(critical_point) self._engine_client.execute_command( cmd.robot.MoveAxesToParams( axis_map=axis_engine_map, diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py index d3ae029f90a..bae3e0e5c18 100644 --- a/api/src/opentrons/protocol_api/core/robot.py +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -15,7 +15,7 @@ def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> N @abstractmethod def move_axes_to( - self, axis_map: AxisMapType, critical_point: AxisMapType, speed: Optional[float] + self, axis_map: AxisMapType, critical_point: Optional[AxisMapType], speed: Optional[float] ) -> None: ... diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index b0bfaf546c6..1d6b83ba5ef 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -92,7 +92,7 @@ def move_to( def move_axes_to( self, axis_map: Union[AxisMapType, StringAxisMap], - critical_point: Union[AxisMapType, StringAxisMap], + critical_point: Optional[Union[AxisMapType, StringAxisMap]] = None, speed: Optional[float] = None, ) -> None: """ @@ -109,9 +109,10 @@ def move_axes_to( axis_map = validation.ensure_axis_map_type( axis_map, self._protocol_core.robot_type, is_96_channel ) - critical_point = validation.ensure_axis_map_type( - critical_point, self._protocol_core.robot_type, is_96_channel - ) + if critical_point: + critical_point = validation.ensure_axis_map_type( + critical_point, self._protocol_core.robot_type, is_96_channel + ) validation.ensure_only_gantry_axis_map_type( critical_point, self._protocol_core.robot_type ) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index 992b1738989..e241a7575a3 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -3,6 +3,7 @@ from typing import Literal, Optional, Type, TYPE_CHECKING from pydantic import Field, BaseModel +from opentrons.protocol_engine.types import MotorAxis from opentrons.hardware_control import HardwareControlAPI from opentrons.protocol_engine.resources import ensure_ot3_hardware @@ -28,8 +29,8 @@ class MoveAxesToParams(BaseModel): axis_map: MotorAxisMapType = Field( ..., description="The specified axes to move to an absolute deck position with." ) - critical_point: MotorAxisMapType = Field( - ..., description="The critical point to move the mount with." + critical_point: Optional[MotorAxisMapType] = Field( + default=None, description="The critical point to move the mount with." ) speed: Optional[float] = Field( default=None, @@ -66,7 +67,7 @@ async def execute( current_position = await self._gantry_mover.move_axes( axis_map=params.axis_map, speed=params.speed, - critical_point=params.critical_point, + critical_point={MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.Z_L: 0.0}, ) return SuccessData( public=MoveAxesToResult(position=current_position), diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 8cf995635a1..dff26f80cd8 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -301,6 +301,7 @@ async def move_axes( axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]] = None, speed: Optional[float] = None, + relative_move: bool = False ) -> Dict[MotorAxis, float]: """Move a set of axes a given distance. @@ -312,17 +313,17 @@ async def move_axes( try: pos_hw = self._convert_axis_map_for_hw(axis_map) mount = self.pick_mount_from_axis_map(axis_map) - if not critical_point: + if relative_move: current_position = await self._hardware_api.current_position( mount, refresh=True ) log.info(f"The current position of the robot is: {current_position}.") - absolute_pos = target_axis_map_from_relative( - pos_hw, current_position + absolute_pos = target_axis_map_from_relative(pos_hw, current_position) + log.info( + f"The absolute position is: {absolute_pos} and hw pos map is {absolute_pos}." ) - log.info(f"The absolute position is: {absolute_pos} and hw pos map is {absolute_pos}.") else: - log.info(f"Incorrectly in abs move") + log.info(f"Absolute move {axis_map} and {mount}") mount_offset = self._offset_axis_map_for_mount(mount) abs_cp_hw = self._convert_axis_map_for_hw(critical_point) absolute_pos = target_axis_map_from_absolute( From 3f15ba315bcf65e5df16c3572ae897f6a0c02454 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 8 Oct 2024 21:34:58 +0300 Subject: [PATCH 20/46] missed if statement block --- api/src/opentrons/protocol_api/robot_context.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 1d6b83ba5ef..4e2de69f7fe 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -113,9 +113,9 @@ def move_axes_to( critical_point = validation.ensure_axis_map_type( critical_point, self._protocol_core.robot_type, is_96_channel ) - validation.ensure_only_gantry_axis_map_type( - critical_point, self._protocol_core.robot_type - ) + validation.ensure_only_gantry_axis_map_type( + critical_point, self._protocol_core.robot_type + ) self._core.move_axes_to(axis_map, critical_point, speed) @requires_version(2, 20) From 9d5dcf7a8bf3aa04749b2377efde298f027da94f Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 8 Oct 2024 21:45:29 +0300 Subject: [PATCH 21/46] use the right MotorAxis key --- .../opentrons/protocol_engine/commands/robot/move_axes_to.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index e241a7575a3..628606691b6 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -67,7 +67,7 @@ async def execute( current_position = await self._gantry_mover.move_axes( axis_map=params.axis_map, speed=params.speed, - critical_point={MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.Z_L: 0.0}, + critical_point={MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.LEFT_Z: 0.0}, ) return SuccessData( public=MoveAxesToResult(position=current_position), From 0e7ef61771bca8f8e718f7a64e48814b6f121f97 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 8 Oct 2024 21:56:04 +0300 Subject: [PATCH 22/46] fix: check that the axis is in the axis map --- api/src/opentrons/hardware_control/motion_utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index 7fd8890dd00..2b61eeb9c21 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -125,7 +125,7 @@ def target_axis_map_from_absolute( } axis_map.update(axis_with_offset) target_position = OrderedDict( - ((ax, axis_map[ax]) for ax in EMPTY_ORDERED_DICT.keys()) + ((ax, axis_map[ax]) for ax in EMPTY_ORDERED_DICT.keys() if ax in axis_map.keys()) ) return target_position From 7f98b782d5f7f7853ec37d53bfa7ed66c30ababb Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 15:43:14 +0300 Subject: [PATCH 23/46] fix: correctly handle Q motor moves in the function and add a test --- .../hardware_control/backends/test_ot3_controller.py | 3 ++- .../hardware_control/move_group_runner.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py index 1fae923c7ae..c95fafa574a 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py @@ -197,11 +197,12 @@ def mock_send_stop_threshold() -> Iterator[mock.AsyncMock]: @pytest.fixture def mock_move_group_run() -> Iterator[mock.AsyncMock]: + with mock.patch( "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner.run", autospec=True, ) as mock_mgr_run: - mock_mgr_run.return_value = {} + mock_mgr_run.side_effect = {} yield mock_mgr_run diff --git a/hardware/opentrons_hardware/hardware_control/move_group_runner.py b/hardware/opentrons_hardware/hardware_control/move_group_runner.py index 1b7baf61d6d..6c5a3c2a9e0 100644 --- a/hardware/opentrons_hardware/hardware_control/move_group_runner.py +++ b/hardware/opentrons_hardware/hardware_control/move_group_runner.py @@ -244,6 +244,16 @@ def all_nodes(self) -> Set[NodeId]: for node in sequence.keys(): node_set.add(node) return node_set + + def all_moving_nodes(self) -> Set[NodeId]: + """Get all of the moving nodes in the move group runner's move groups.""" + node_set: Set[NodeId] = set() + for group in self._move_groups: + for sequence in group: + for node, node_step in sequence.items(): + if node_step.is_moving_step(): + node_set.add(node) + return node_set def all_moving_nodes(self) -> Set[NodeId]: """Get all of the moving nodes in the move group runner's move groups.""" From 8a7338d44eb16320776ba74155e1255ab8633a31 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 15:44:44 +0300 Subject: [PATCH 24/46] pass the right locations from gantry mover to the hardware controller and add tests --- .../hardware_control/motion_utilities.py | 35 +++-- .../protocol_engine/execution/gantry_mover.py | 101 +++++++------ .../execution/test_gantry_mover.py | 140 +++++++++++++++++- 3 files changed, 221 insertions(+), 55 deletions(-) diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index 2b61eeb9c21..8cc00b19291 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -111,21 +111,32 @@ def target_position_from_relative( def target_axis_map_from_absolute( + primary_mount: Union[OT3Mount, Mount], axis_map: Dict[Axis, float], - critical_point: Dict[Axis, float], - mount_offset: Dict[Axis, float], + get_critical_point: Callable[[Union[Mount, OT3Mount]], Point], + left_mount_offset: Point, + right_mount_offset: Point, + gripper_mount_offset: Optional[Point] = None, ) -> "OrderedDict[Axis, float]": """Create an absolute target position for all specified machine axes.""" - axis_with_cp = {ax: axis_map[ax] - val for ax, val in critical_point.items()} - axis_map.update(axis_with_cp) - axis_with_offset = { - ax: axis_map[ax] - val - for ax, val in mount_offset.items() - if ax in axis_map.keys() - } - axis_map.update(axis_with_offset) - target_position = OrderedDict( - ((ax, axis_map[ax]) for ax in EMPTY_ORDERED_DICT.keys() if ax in axis_map.keys()) + keys_for_target_position = list(axis_map.keys()) + + offset = offset_for_mount( + primary_mount, left_mount_offset, right_mount_offset, gripper_mount_offset + ) + primary_cp = get_critical_point(primary_mount) + primary_z = Axis.by_mount(primary_mount) + target_position = OrderedDict() + + if Axis.X in keys_for_target_position: + target_position[Axis.X] = axis_map[Axis.X] - offset.x - primary_cp.x + if Axis.Y in keys_for_target_position: + target_position[Axis.Y] = axis_map[Axis.Y] - offset.y - primary_cp.y + if primary_z in keys_for_target_position: + target_position[primary_z] = axis_map[primary_z] - offset.z - primary_cp.z + + target_position.update( + {ax: val for ax, val in axis_map.items() if ax not in Axis.gantry_axes()} ) return target_position diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index dff26f80cd8..9d6aaad82d4 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -1,6 +1,7 @@ """Gantry movement wrapper for hardware and simulation based movement.""" from logging import getLogger +from functools import partial from typing import Optional, List, Dict from typing_extensions import Protocol as TypingProtocol @@ -41,6 +42,12 @@ MotorAxis.EXTENSION_Z: Mount.EXTENSION, } +_HARDWARE_MOUNT_MOTOR_AXIS_TO: Dict[MotorAxis, Mount] = { + Mount.LEFT: MotorAxis.LEFT_Z, + Mount.RIGHT: MotorAxis.RIGHT_Z, + Mount.EXTENSION: MotorAxis.EXTENSION_Z, +} + _HARDWARE_AXIS_TO_MOTOR_AXIS: Dict[HardwareAxis, MotorAxis] = { HardwareAxis.X: MotorAxis.X, HardwareAxis.Y: MotorAxis.Y, @@ -103,6 +110,7 @@ async def move_axes( axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]] = None, speed: Optional[float] = None, + relative_move: bool = False, ) -> Dict[MotorAxis, float]: """Move a set of axes a given distance.""" ... @@ -170,30 +178,17 @@ def _convert_axis_map_for_hw( """Transform an engine motor axis map to a hardware axis map.""" return {_MOTOR_AXIS_TO_HARDWARE_AXIS[ax]: dist for ax, dist in axis_map.items()} - def _offset_axis_map_for_mount(self, mount: Mount) -> Dict[HardwareAxis, float]: - """Determine the offset for the given hardware mount""" - if ( - self._state_view.config.robot_type == "OT-2 Standard" - and mount == Mount.RIGHT - ): - return {HardwareAxis.X: 0.0, HardwareAxis.Y: 0.0, HardwareAxis.A: 0.0} - elif ( - self._state_view.config.robot_type == "OT-3 Standard" - and mount == Mount.EXTENSION - ): - offset = self._hardware_api.config.gripper_mount_offset # type: ignore [union-attr] - return { - HardwareAxis.X: offset[0], - HardwareAxis.Y: offset[1], - HardwareAxis.Z_G: offset[2], - } + def _critical_point_for( + self, mount: Mount, cp_override: Dict[MotorAxis, float] = None + ) -> Point: + if cp_override: + return Point( + x=cp_override[MotorAxis.X], + y=cp_override[MotorAxis.Y], + z=cp_override[_HARDWARE_MOUNT_MOTOR_AXIS_TO[mount]], + ) else: - offset = self._hardware_api.config.left_mount_offset - return { - HardwareAxis.X: offset[0], - HardwareAxis.Y: offset[1], - HardwareAxis.Z: offset[2], - } + return self._hardware_api.critical_point_for(mount) def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" @@ -301,7 +296,7 @@ async def move_axes( axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]] = None, speed: Optional[float] = None, - relative_move: bool = False + relative_move: bool = False, ) -> Dict[MotorAxis, float]: """Move a set of axes a given distance. @@ -317,17 +312,25 @@ async def move_axes( current_position = await self._hardware_api.current_position( mount, refresh=True ) + converted_current_position_deck = self._hardware_api._deck_from_machine( + current_position + ) log.info(f"The current position of the robot is: {current_position}.") - absolute_pos = target_axis_map_from_relative(pos_hw, current_position) + absolute_pos = target_axis_map_from_relative( + pos_hw, converted_current_position_deck + ) log.info( f"The absolute position is: {absolute_pos} and hw pos map is {absolute_pos}." ) else: log.info(f"Absolute move {axis_map} and {mount}") - mount_offset = self._offset_axis_map_for_mount(mount) - abs_cp_hw = self._convert_axis_map_for_hw(critical_point) absolute_pos = target_axis_map_from_absolute( - pos_hw, abs_cp_hw, mount_offset + mount, + pos_hw, + partial(self._critical_point_for, cp_override=critical_point), + self._hardware_api.config.left_mount_offset, + self._hardware_api.config.right_mount_offset, + self._hardware_api.config.gripper_mount_offset, ) await self._hardware_api.move_axes( position=absolute_pos, @@ -340,9 +343,12 @@ async def move_axes( current_position = await self._hardware_api.current_position( mount, refresh=True ) + converted_current_position_deck = self._hardware_api._deck_from_machine( + current_position + ) return { self._hardware_axis_to_motor_axis(ax): pos - for ax, pos in current_position.items() + for ax, pos in converted_current_position_deck.items() } async def move_relative( @@ -523,26 +529,37 @@ async def move_axes( axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]] = None, speed: Optional[float] = None, + relative_move: bool = False, ) -> Dict[MotorAxis, float]: """Move the give axes map. No-op in virtual implementation.""" mount = self.pick_mount_from_axis_map(axis_map) current_position = await self.get_position_from_mount(mount) - axis_map[MotorAxis.X] = axis_map.get(MotorAxis.X, 0.0) + current_position[0] - axis_map[MotorAxis.Y] = axis_map.get(MotorAxis.Y, 0.0) + current_position[1] - if mount == Mount.RIGHT: - axis_map[MotorAxis.RIGHT_Z] = ( - axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2] + updated_position = {} + if relative_move: + updated_position[MotorAxis.X] = ( + axis_map.get(MotorAxis.X, 0.0) + current_position[0] ) - elif mount == Mount.EXTENSION: - axis_map[MotorAxis.EXTENSION_Z] = ( - axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2] + updated_position[MotorAxis.Y] = ( + axis_map.get(MotorAxis.Y, 0.0) + current_position[1] ) + if mount == Mount.RIGHT: + updated_position[MotorAxis.RIGHT_Z] = ( + axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2] + ) + elif mount == Mount.EXTENSION: + updated_position[MotorAxis.EXTENSION_Z] = ( + axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2] + ) + else: + updated_position[MotorAxis.LEFT_Z] = ( + axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2] + ) else: - axis_map[MotorAxis.LEFT_Z] = ( - axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2] - ) - critical_point = critical_point or {} - return {ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items()} + critical_point = critical_point or {} + updated_position = { + ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items() + } + return updated_position async def move_mount_to( self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index b9dbd798ff2..477d8e61205 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -3,7 +3,7 @@ import pytest from decoy import Decoy -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional from opentrons.types import Mount, MountType, Point from opentrons.hardware_control import API as HardwareAPI @@ -466,6 +466,109 @@ async def test_home_z( ) +@pytest.mark.parametrize( + argnames=[ + "axis_map", + "critical_point", + "relative_move", + "expected_mount", + "call_to_hw", + "final_position", + ], + argvalues=[ + [ + {MotorAxis.X: 10, MotorAxis.Y: 15, MotorAxis.RIGHT_Z: 20}, + {MotorAxis.X: 2, MotorAxis.Y: 1, MotorAxis.RIGHT_Z: 1}, + False, + Mount.RIGHT, + {HardwareAxis.X: -2, HardwareAxis.Y: 4, HardwareAxis.A: 9}, + {HardwareAxis.X: -2, HardwareAxis.Y: 4, HardwareAxis.A: 9}, + ], + [ + {MotorAxis.RIGHT_Z: 20}, + None, + True, + Mount.RIGHT, + {HardwareAxis.A: 30}, + { + HardwareAxis.X: 10, + HardwareAxis.Y: 15, + HardwareAxis.Z: 10, + HardwareAxis.A: 30, + }, + ], + [ + {MotorAxis.CLAMP_JAW_96_CHANNEL: 10}, + None, + False, + Mount.LEFT, + {HardwareAxis.Q: 10}, + {HardwareAxis.Q: 10}, + ], + ], +) +async def test_move_axes( + decoy: Decoy, + mock_hardware_api: HardwareAPI, + hardware_subject: HardwareGantryMover, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]], + expected_mount: Mount, + relative_move: bool, + call_to_hw: Dict[HardwareAxis, float], + final_position: Dict[HardwareAxis, float], +) -> None: + curr_pos = { + HardwareAxis.X: 10, + HardwareAxis.Y: 15, + HardwareAxis.Z: 10, + HardwareAxis.A: 10, + } + call_count = 0 + + def _current_position(mount, refresh) -> Dict[HardwareAxis, float]: + nonlocal call_count + nonlocal curr_pos + nonlocal final_position + if call_count == 0 and relative_move: + call_count += 1 + return curr_pos + else: + return final_position + + decoy.when( + await mock_hardware_api.current_position(expected_mount, refresh=True) + ).then_do(_current_position) + + decoy.when(mock_hardware_api.config.left_mount_offset).then_return(Point(1, 1, 1)) + decoy.when(mock_hardware_api.config.right_mount_offset).then_return( + Point(10, 10, 10) + ) + decoy.when(mock_hardware_api.config.gripper_mount_offset).then_return( + Point(0.5, 0.5, 0.5) + ) + + decoy.when(mock_hardware_api._deck_from_machine(curr_pos)).then_return(curr_pos) + + decoy.when(mock_hardware_api._deck_from_machine(final_position)).then_return( + final_position + ) + if not critical_point: + decoy.when(mock_hardware_api._critical_point_for(expected_mount)).then_return( + Point(1, 1, 1) + ) + + pos = await hardware_subject.move_axes(axis_map, critical_point, 100, relative_move) + decoy.verify( + await mock_hardware_api.move_axes(position=call_to_hw, speed=100), + times=1, + ) + assert pos == { + hardware_subject._hardware_axis_to_motor_axis(ax): pos + for ax, pos in final_position.items() + } + + async def test_virtual_get_position( decoy: Decoy, mock_state_view: StateView, @@ -562,3 +665,38 @@ async def test_virtual_move_to( ) assert result == Point(4, 5, 6) + + +@pytest.mark.parametrize( + argnames=["axis_map", "critical_point", "relative_move", "expected_position"], + argvalues=[ + [ + {MotorAxis.X: 10, MotorAxis.Y: 15, MotorAxis.RIGHT_Z: 20}, + {MotorAxis.X: 2, MotorAxis.Y: 1, MotorAxis.RIGHT_Z: 1}, + False, + {MotorAxis.X: 8, MotorAxis.Y: 14, MotorAxis.RIGHT_Z: 19}, + ], + [ + {MotorAxis.RIGHT_Z: 20}, + None, + True, + {MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.RIGHT_Z: 20}, + ], + [ + {MotorAxis.CLAMP_JAW_96_CHANNEL: 10}, + None, + False, + {MotorAxis.CLAMP_JAW_96_CHANNEL: 10}, + ], + ], +) +async def test_virtual_move_axes( + decoy: Decoy, + virtual_subject: VirtualGantryMover, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]], + relative_move: bool, + expected_position: Dict[HardwareAxis, float], +) -> None: + pos = await virtual_subject.move_axes(axis_map, critical_point, 100, relative_move) + assert pos == expected_position From 90750c13f690c554cf42cf41aa8060f028ab8bed Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 16:35:40 +0300 Subject: [PATCH 25/46] adding protocol engine command tests --- .../commands/robot/move_axes_relative.py | 2 +- .../commands/robot/move_axes_to.py | 2 +- .../robot/test_move_axes_relative_to.py | 53 ++++++++++++++++++- .../commands/robot/test_move_axes_to.py | 35 +++++------- .../commands/robot/test_move_to.py | 3 -- 5 files changed, 65 insertions(+), 30 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py index 2bb74160379..34beb02ad08 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py @@ -65,7 +65,7 @@ async def execute( ensure_ot3_hardware(self._hardware_api) current_position = await self._gantry_mover.move_axes( - axis_map=params.axis_map, speed=params.speed + axis_map=params.axis_map, speed=params.speed, relative_move=True ) return SuccessData( public=MoveAxesRelativeResult(position=current_position), diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index 628606691b6..27e520282ef 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -67,7 +67,7 @@ async def execute( current_position = await self._gantry_mover.move_axes( axis_map=params.axis_map, speed=params.speed, - critical_point={MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.LEFT_Z: 0.0}, + critical_point=params.critical_point, ) return SuccessData( public=MoveAxesToResult(position=current_position), diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py index 161c731be83..f16a0c4835d 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py @@ -1,5 +1,54 @@ +"""Test robot.move-axes-relative commands.""" from decoy import Decoy +from opentrons.hardware_control import HardwareControlAPI -async def test_move_axes_relative_to_success() -> None: - return None +from opentrons.protocol_engine.execution import GantryMover +from opentrons.protocol_engine.types import MotorAxis +from opentrons.hardware_control.protocols.types import FlexRobotType, OT2RobotType +from opentrons.types import Point, MountType + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.move_axes_relative import ( + MoveAxesRelativeParams, + MoveAxesRelativeResult, + MoveAxesRelativeImplementation, +) + + +async def test_move_axes_to_implementation( + decoy: Decoy, + gantry_mover: GantryMover, + ot3_hardware_api: HardwareControlAPI, +) -> None: + """Test the `robot.moveAxesRelative` implementation. + + It should call `MovementHandler.move_mount_to` with the + correct coordinates. + """ + subject = MoveAxesRelativeImplementation( + gantry_mover=gantry_mover, + hardware_api=ot3_hardware_api, + ) + + params = MoveAxesRelativeParams( + axis_map={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}, + speed=567.8, + ) + + # Flex shape + decoy.when(ot3_hardware_api.get_robot_type()).then_return(FlexRobotType) + decoy.when( + await gantry_mover.move_axes( + axis_map=params.axis_map, speed=params.speed, relative_move=True + ) + ).then_return({MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}) + + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveAxesRelativeResult( + position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} + ), + private=None, + ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py index 57f77797153..ad6a4659c8a 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py @@ -3,8 +3,7 @@ from opentrons.hardware_control import HardwareControlAPI -from opentrons.protocol_engine.execution import MovementHandler, GantryMover -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.execution import GantryMover from opentrons.protocol_engine.types import MotorAxis from opentrons.hardware_control.protocols.types import FlexRobotType, OT2RobotType from opentrons.types import Point, MountType @@ -17,12 +16,10 @@ ) -async def test_move_to_implementation( +async def test_move_axes_to_implementation( decoy: Decoy, - state_view: StateView, gantry_mover: GantryMover, - movement: MovementHandler, - hardware_api: HardwareControlAPI, + ot3_hardware_api: HardwareControlAPI, ) -> None: """Test the `robot.moveAxesTo` implementation. @@ -30,10 +27,8 @@ async def test_move_to_implementation( correct coordinates. """ subject = MoveAxesToImplementation( - state_view=state_view, gantry_mover=gantry_mover, - movement=movement, - hardware_api=hardware_api, + hardware_api=ot3_hardware_api, ) params = MoveAxesToParams( @@ -42,21 +37,15 @@ async def test_move_to_implementation( speed=567.8, ) - # OT 2 shape - decoy.when(hardware_api.get_robot_type()).then_return(OT2RobotType) - - result = await subject.execute(params=params) - - assert result == SuccessData( - public=MoveAxesToResult( - position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} - ), - private=None, - ) - # Flex shape - decoy.when(hardware_api.get_robot_type()).then_return(FlexRobotType) - + decoy.when(ot3_hardware_api.get_robot_type()).then_return(FlexRobotType) + decoy.when( + await gantry_mover.move_axes( + axis_map=params.axis_map, + speed=params.speed, + critical_point=params.critical_point, + ) + ).then_return({MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}) result = await subject.execute(params=params) assert result == SuccessData( diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py index 903f14248bc..09604a38622 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py @@ -2,7 +2,6 @@ from decoy import Decoy from opentrons.protocol_engine.execution import MovementHandler -from opentrons.protocol_engine.state import StateView from opentrons.protocol_engine.types import DeckPoint from opentrons.types import Point, MountType @@ -16,7 +15,6 @@ async def test_move_to_implementation( decoy: Decoy, - state_view: StateView, movement: MovementHandler, ) -> None: """Test the `robot.moveTo` implementation. @@ -25,7 +23,6 @@ async def test_move_to_implementation( correct coordinates. """ subject = MoveToImplementation( - state_view=state_view, movement=movement, ) From 049911868707158e1475ec952ee197a1cfa6bd1b Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 16:37:28 +0300 Subject: [PATCH 26/46] formatting --- api/src/opentrons/protocol_api/core/engine/robot.py | 5 ++++- api/src/opentrons/protocol_api/core/robot.py | 5 ++++- api/src/opentrons/protocol_api/protocol_context.py | 2 ++ api/src/opentrons/protocol_engine/execution/gantry_mover.py | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index e38ad634c11..6083d77d4a7 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -57,7 +57,10 @@ def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> N ) def move_axes_to( - self, axis_map: AxisMapType, critical_point: Optional[AxisMapType], speed: Optional[float] + self, + axis_map: AxisMapType, + critical_point: Optional[AxisMapType], + speed: Optional[float], ) -> None: axis_engine_map = self._convert_to_engine_mount(axis_map) critical_point_engine = critical_point diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py index bae3e0e5c18..8ca7d23136b 100644 --- a/api/src/opentrons/protocol_api/core/robot.py +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -15,7 +15,10 @@ def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> N @abstractmethod def move_axes_to( - self, axis_map: AxisMapType, critical_point: Optional[AxisMapType], speed: Optional[float] + self, + axis_map: AxisMapType, + critical_point: Optional[AxisMapType], + speed: Optional[float], ) -> None: ... diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index eb2bd64b54a..b56d668cb3a 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -221,6 +221,8 @@ def robot(self) -> RobotContext: :meta private: """ + if self._core.robot_type != "OT-3 Standard": + raise RobotTypeError("The RobotContext is only available on Flex robot.") return self._robot @property diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 9d6aaad82d4..6d5370dbbf4 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -280,8 +280,9 @@ async def move_mount_to( ) -> Point: """Move the given hardware mount to a waypoint.""" assert len(waypoints) > 0, "Must have at least one waypoint" - + log.info(f"Moving mount {mount}") for waypoint in waypoints: + log.info(f"The current waypoint moving is {waypoint}") await self._hardware_api.move_to( mount=mount, abs_position=waypoint.position, From b8cf9bbe3f13373502f20b5031258100bfcd439a Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 20:26:34 +0300 Subject: [PATCH 27/46] bump api version to 2.21 --- api/src/opentrons/protocol_api/robot_context.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 4e2de69f7fe..ccbefaeb2b6 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -56,7 +56,7 @@ def __init__( self._api_version = api_version @property - @requires_version(2, 20) + @requires_version(2, 21) def api_version(self) -> APIVersion: return self._api_version @@ -67,7 +67,7 @@ def hardware(self) -> HardwareManager: # context commands. return self._hardware - @requires_version(2, 20) + @requires_version(2, 21) def move_to( self, mount: Union[Mount, str], @@ -88,7 +88,7 @@ def move_to( mount = validation.ensure_instrument_mount(mount) self._core.move_to(mount, destination.point, speed) - @requires_version(2, 20) + @requires_version(2, 21) def move_axes_to( self, axis_map: Union[AxisMapType, StringAxisMap], @@ -118,7 +118,7 @@ def move_axes_to( ) self._core.move_axes_to(axis_map, critical_point, speed) - @requires_version(2, 20) + @requires_version(2, 21) def move_axes_relative( self, axis_map: Union[AxisMapType, StringAxisMap], From 6e92b5ba8c34d12a6c8071a4855cff0864d7a523 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 22:38:54 +0300 Subject: [PATCH 28/46] add logs for motion utilities --- api/src/opentrons/hardware_control/motion_utilities.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index 8cc00b19291..d64e253d528 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -1,4 +1,6 @@ """Utilities for calculating motion correctly.""" +from logging import getLogger + from functools import lru_cache from typing import Callable, Dict, Union, Optional, cast from collections import OrderedDict @@ -11,6 +13,7 @@ from .types import Axis, OT3Mount +log = getLogger(__name__) # TODO: The offset_for_mount function should be defined with an overload # set, as with other functions in this module. Unfortunately, mypy < 0.920 @@ -128,6 +131,8 @@ def target_axis_map_from_absolute( primary_z = Axis.by_mount(primary_mount) target_position = OrderedDict() + log.info(f"The primary critical point {primary_cp} and primary_mount {primary_mount} and type {type(primary_cp)}") + log.info(f"The offset {offset} and type is {type(offset)}") if Axis.X in keys_for_target_position: target_position[Axis.X] = axis_map[Axis.X] - offset.x - primary_cp.x if Axis.Y in keys_for_target_position: From d1e716fea782881006d6c358cb7de16512fd59cd Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 17 Oct 2024 22:51:18 +0300 Subject: [PATCH 29/46] wrapper hardware offsets as point --- api/src/opentrons/protocol_engine/execution/gantry_mover.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 6d5370dbbf4..0cadb791b0d 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -329,9 +329,9 @@ async def move_axes( mount, pos_hw, partial(self._critical_point_for, cp_override=critical_point), - self._hardware_api.config.left_mount_offset, - self._hardware_api.config.right_mount_offset, - self._hardware_api.config.gripper_mount_offset, + Point(*self._hardware_api.config.left_mount_offset), + Point(*self._hardware_api.config.right_mount_offset), + Point(*self._hardware_api.config.gripper_mount_offset), ) await self._hardware_api.move_axes( position=absolute_pos, From e5d468d9e568577975049da44de23c286bbbc213 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 21 Oct 2024 22:23:46 +0300 Subject: [PATCH 30/46] fix move_axes_to move, add more logs for move_axes_relative --- .../hardware_control/protocols/motion_controller.py | 7 ++++++- .../protocol_engine/execution/gantry_mover.py | 11 ++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/api/src/opentrons/hardware_control/protocols/motion_controller.py b/api/src/opentrons/hardware_control/protocols/motion_controller.py index daaf166f283..5a7d27c7f0b 100644 --- a/api/src/opentrons/hardware_control/protocols/motion_controller.py +++ b/api/src/opentrons/hardware_control/protocols/motion_controller.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Mapping +from typing import Dict, Union, List, Optional, Mapping from typing_extensions import Protocol from opentrons.types import Point @@ -9,6 +9,11 @@ class MotionController(Protocol[MountArgType]): """Protocol specifying fundamental motion controls.""" + def get_deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + """Convert machine coordinates to deck coordinates.""" + ... + + async def halt(self, disengage_before_stopping: bool = False) -> None: """Immediately stop motion. diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 0cadb791b0d..0836a4600eb 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -42,7 +42,7 @@ MotorAxis.EXTENSION_Z: Mount.EXTENSION, } -_HARDWARE_MOUNT_MOTOR_AXIS_TO: Dict[MotorAxis, Mount] = { +_HARDWARE_MOUNT_MOTOR_AXIS_TO: Dict[Mount, MotorAxis] = { Mount.LEFT: MotorAxis.LEFT_Z, Mount.RIGHT: MotorAxis.RIGHT_Z, Mount.EXTENSION: MotorAxis.EXTENSION_Z, @@ -179,7 +179,7 @@ def _convert_axis_map_for_hw( return {_MOTOR_AXIS_TO_HARDWARE_AXIS[ax]: dist for ax, dist in axis_map.items()} def _critical_point_for( - self, mount: Mount, cp_override: Dict[MotorAxis, float] = None + self, mount: Mount, cp_override: Optional[Dict[MotorAxis, float]] = None ) -> Point: if cp_override: return Point( @@ -313,10 +313,11 @@ async def move_axes( current_position = await self._hardware_api.current_position( mount, refresh=True ) - converted_current_position_deck = self._hardware_api._deck_from_machine( + log.info(f"The current position of the robot is: {current_position}.") + converted_current_position_deck = self._hardware_api.get_deck_from_machine( current_position ) - log.info(f"The current position of the robot is: {current_position}.") + log.info(f"The converted deck position of the robot is: {converted_current_position_deck}.") absolute_pos = target_axis_map_from_relative( pos_hw, converted_current_position_deck ) @@ -344,7 +345,7 @@ async def move_axes( current_position = await self._hardware_api.current_position( mount, refresh=True ) - converted_current_position_deck = self._hardware_api._deck_from_machine( + converted_current_position_deck = self._hardware_api.get_deck_from_machine( current_position ) return { From b8911f90ad15cba487d31f5e148af9dad3856112 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 21 Oct 2024 22:24:52 +0300 Subject: [PATCH 31/46] make machine to deck conversion func public hw API --- api/src/opentrons/hardware_control/api.py | 36 ++++++---------- .../hardware_control/motion_utilities.py | 10 +++-- api/src/opentrons/hardware_control/ot3api.py | 10 ++--- .../protocols/liquid_handler.py | 20 ++++++++- .../execution/test_gantry_mover.py | 42 +++++++++---------- 5 files changed, 64 insertions(+), 54 deletions(-) diff --git a/api/src/opentrons/hardware_control/api.py b/api/src/opentrons/hardware_control/api.py index ec019ef2f1d..3c715daa0d3 100644 --- a/api/src/opentrons/hardware_control/api.py +++ b/api/src/opentrons/hardware_control/api.py @@ -169,6 +169,14 @@ def _update_door_state(self, door_state: DoorState) -> None: def _reset_last_mount(self) -> None: self._last_moved_mount = None + def get_deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + return deck_from_machine( + machine_pos=machine_pos, + attitude=self._robot_calibration.deck_calibration.attitude, + offset=top_types.Point(0, 0, 0), + robot_type=cast(RobotType, "OT-2 Standard"), + ) + @classmethod async def build_hardware_controller( # noqa: C901 cls, @@ -657,12 +665,7 @@ async def home(self, axes: Optional[List[Axis]] = None) -> None: async with self._motion_lock: if smoothie_gantry: smoothie_pos.update(await self._backend.home(smoothie_gantry)) - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), - ) + self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) for plunger in plungers: await self._do_plunger_home(axis=plunger, acquire_lock=False) @@ -703,12 +706,7 @@ async def current_position( async with self._motion_lock: if refresh: smoothie_pos = await self._backend.update_position() - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), - ) + self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) if mount == top_types.Mount.RIGHT: offset = top_types.Point(0, 0, 0) else: @@ -938,12 +936,7 @@ async def retract_axis(self, axis: Axis, margin: float = 10) -> None: async with self._motion_lock: smoothie_pos = await self._fast_home(smoothie_ax, margin) - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), - ) + self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) # Gantry/frame (i.e. not pipette) config API @property @@ -1256,12 +1249,7 @@ async def tip_drop_moves( axes=[ot2_axis_to_string(ax) for ax in move.home_axes], margin=move.home_after_safety_margin, ) - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), - ) + self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) for shake in spec.shake_moves: await self.move_rel(mount, shake[0], speed=shake[1]) diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index d64e253d528..dd59437f7dc 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -84,6 +84,7 @@ def target_position_from_absolute( ) primary_cp = get_critical_point(mount) primary_z = Axis.by_mount(mount) + target_position = OrderedDict( ( (Axis.X, abs_position.x - offset.x - primary_cp.x), @@ -131,14 +132,15 @@ def target_axis_map_from_absolute( primary_z = Axis.by_mount(primary_mount) target_position = OrderedDict() - log.info(f"The primary critical point {primary_cp} and primary_mount {primary_mount} and type {type(primary_cp)}") - log.info(f"The offset {offset} and type is {type(offset)}") if Axis.X in keys_for_target_position: target_position[Axis.X] = axis_map[Axis.X] - offset.x - primary_cp.x if Axis.Y in keys_for_target_position: target_position[Axis.Y] = axis_map[Axis.Y] - offset.y - primary_cp.y if primary_z in keys_for_target_position: - target_position[primary_z] = axis_map[primary_z] - offset.z - primary_cp.z + # Since this function is intended to be used in conjunction with `API.move_axes` + # we must leave out the carriage offset subtraction from the target position as + # `move_axes` already does this calculation. + target_position[primary_z] = axis_map[primary_z] - primary_cp.z target_position.update( {ax: val for ax, val in axis_map.items() if ax not in Axis.gantry_axes()} @@ -158,6 +160,8 @@ def target_axis_map_from_relative( if ax in axis_map.keys() ) ) + log.info(f"Current position {current_position} and axis map delta {axis_map}") + log.info(f"Relative move target {target_position}") return target_position diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 576f3cdf7bb..8add3bede3a 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -352,7 +352,7 @@ def _update_estop_state(self, event: HardwareEvent) -> "List[Future[None]]": def _reset_last_mount(self) -> None: self._last_moved_mount = None - def _deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + def get_deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: return deck_from_machine( machine_pos=machine_pos, attitude=self._robot_calibration.deck_calibration.attitude, @@ -1020,14 +1020,14 @@ async def _refresh_jaw_state(self) -> None: async def _cache_current_position(self) -> Dict[Axis, float]: """Cache current position from backend and return in absolute deck coords.""" - self._current_position = self._deck_from_machine( + self._current_position = self.get_deck_from_machine( await self._backend.update_position() ) return self._current_position async def _cache_encoder_position(self) -> Dict[Axis, float]: """Cache encoder position from backend and return in absolute deck coords.""" - self._encoder_position = self._deck_from_machine( + self._encoder_position = self.get_deck_from_machine( await self._backend.update_encoder_position() ) if self.has_gripper(): @@ -2562,7 +2562,7 @@ def get_instrument_max_height( mount: Union[top_types.Mount, OT3Mount], critical_point: Optional[CriticalPoint] = None, ) -> float: - carriage_pos = self._deck_from_machine(self._backend.home_position()) + carriage_pos = self.get_deck_from_machine(self._backend.home_position()) pos_at_home = self._effector_pos_from_carriage_pos( OT3Mount.from_mount(mount), carriage_pos, critical_point ) @@ -2664,7 +2664,7 @@ async def _liquid_probe_pass( ) machine_pos = await self._backend.update_position() machine_pos[Axis.by_mount(mount)] = end_z - deck_end_z = self._deck_from_machine(machine_pos)[Axis.by_mount(mount)] + deck_end_z = self.get_deck_from_machine(machine_pos)[Axis.by_mount(mount)] offset = offset_for_mount( mount, top_types.Point(*self._config.left_mount_offset), diff --git a/api/src/opentrons/hardware_control/protocols/liquid_handler.py b/api/src/opentrons/hardware_control/protocols/liquid_handler.py index 8707fc33024..f2f3f2ecef4 100644 --- a/api/src/opentrons/hardware_control/protocols/liquid_handler.py +++ b/api/src/opentrons/hardware_control/protocols/liquid_handler.py @@ -1,6 +1,8 @@ -from typing import Optional +from typing import Optional, Dict from typing_extensions import Protocol +from opentrons.types import Point +from opentrons.hardware_control.types import CriticalPoint, Axis from .types import MountArgType, CalibrationType, ConfigType from .instrument_configurer import InstrumentConfigurer @@ -16,6 +18,22 @@ class LiquidHandler( Calibratable[CalibrationType], Protocol[CalibrationType, MountArgType, ConfigType], ): + def critical_point_for( + self, + mount: MountArgType, + cp_override: Optional[CriticalPoint] = None, + ) -> Point: + """ + Determine the current critical point for the specified mount. + + :param mount: A robot mount that the instrument is on. + :param cp_override: The critical point override to use. + + If no critical point override is specified, the robot defaults to nozzle location `A1` or the mount critical point. + :return: Point. + """ + ... + async def update_nozzle_configuration_for_mount( self, mount: MountArgType, diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index 477d8e61205..ccbdf34867a 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -477,33 +477,33 @@ async def test_home_z( ], argvalues=[ [ - {MotorAxis.X: 10, MotorAxis.Y: 15, MotorAxis.RIGHT_Z: 20}, - {MotorAxis.X: 2, MotorAxis.Y: 1, MotorAxis.RIGHT_Z: 1}, + {MotorAxis.X: 10.0, MotorAxis.Y: 15.0, MotorAxis.RIGHT_Z: 20.0}, + {MotorAxis.X: 2.0, MotorAxis.Y: 1.0, MotorAxis.RIGHT_Z: 1.0}, False, Mount.RIGHT, - {HardwareAxis.X: -2, HardwareAxis.Y: 4, HardwareAxis.A: 9}, - {HardwareAxis.X: -2, HardwareAxis.Y: 4, HardwareAxis.A: 9}, + {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 9.0}, + {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 9.0}, ], [ - {MotorAxis.RIGHT_Z: 20}, + {MotorAxis.RIGHT_Z: 20.0}, None, True, Mount.RIGHT, - {HardwareAxis.A: 30}, + {HardwareAxis.A: 30.0}, { - HardwareAxis.X: 10, - HardwareAxis.Y: 15, - HardwareAxis.Z: 10, - HardwareAxis.A: 30, + HardwareAxis.X: 10.0, + HardwareAxis.Y: 15.0, + HardwareAxis.Z: 10.0, + HardwareAxis.A: 30.0, }, ], [ - {MotorAxis.CLAMP_JAW_96_CHANNEL: 10}, + {MotorAxis.CLAMP_JAW_96_CHANNEL: 10.0}, None, False, Mount.LEFT, - {HardwareAxis.Q: 10}, - {HardwareAxis.Q: 10}, + {HardwareAxis.Q: 10.0}, + {HardwareAxis.Q: 10.0}, ], ], ) @@ -519,14 +519,14 @@ async def test_move_axes( final_position: Dict[HardwareAxis, float], ) -> None: curr_pos = { - HardwareAxis.X: 10, - HardwareAxis.Y: 15, - HardwareAxis.Z: 10, - HardwareAxis.A: 10, + HardwareAxis.X: 10.0, + HardwareAxis.Y: 15.0, + HardwareAxis.Z: 10.0, + HardwareAxis.A: 10.0, } call_count = 0 - def _current_position(mount, refresh) -> Dict[HardwareAxis, float]: + def _current_position(mount: Mount, refresh: bool) -> Dict[HardwareAxis, float]: nonlocal call_count nonlocal curr_pos nonlocal final_position @@ -548,13 +548,13 @@ def _current_position(mount, refresh) -> Dict[HardwareAxis, float]: Point(0.5, 0.5, 0.5) ) - decoy.when(mock_hardware_api._deck_from_machine(curr_pos)).then_return(curr_pos) + decoy.when(mock_hardware_api.get_deck_from_machine(curr_pos)).then_return(curr_pos) - decoy.when(mock_hardware_api._deck_from_machine(final_position)).then_return( + decoy.when(mock_hardware_api.get_deck_from_machine(final_position)).then_return( final_position ) if not critical_point: - decoy.when(mock_hardware_api._critical_point_for(expected_mount)).then_return( + decoy.when(mock_hardware_api.critical_point_for(expected_mount)).then_return( Point(1, 1, 1) ) From aecb1c350f9163d0962809bb1a1765a4381ac748 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Mon, 21 Oct 2024 22:25:14 +0300 Subject: [PATCH 32/46] more linter fixes --- api/src/opentrons/protocol_api/core/engine/robot.py | 4 +++- api/src/opentrons/protocol_api/core/robot.py | 2 +- api/src/opentrons/protocol_api/robot_context.py | 2 ++ .../hardware_testing/opentrons_api/helpers_ot3.py | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index 6083d77d4a7..7deabff0571 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -63,9 +63,11 @@ def move_axes_to( speed: Optional[float], ) -> None: axis_engine_map = self._convert_to_engine_mount(axis_map) - critical_point_engine = critical_point if critical_point: critical_point_engine = self._convert_to_engine_mount(critical_point) + else: + critical_point_engine = None + self._engine_client.execute_command( cmd.robot.MoveAxesToParams( axis_map=axis_engine_map, diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py index 8ca7d23136b..7eade528413 100644 --- a/api/src/opentrons/protocol_api/core/robot.py +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -1,5 +1,5 @@ from abc import abstractmethod, ABC -from typing import TypeVar, Optional +from typing import Optional from opentrons.types import AxisMapType, Mount, Point diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index ccbefaeb2b6..8447cb4217d 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -116,6 +116,8 @@ def move_axes_to( validation.ensure_only_gantry_axis_map_type( critical_point, self._protocol_core.robot_type ) + else: + critical_point = None self._core.move_axes_to(axis_map, critical_point, speed) @requires_version(2, 21) diff --git a/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py b/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py index 46dc2d5743d..6c4e05f1ba8 100644 --- a/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py +++ b/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py @@ -659,7 +659,7 @@ async def move_gripper_jaw_relative_ot3(api: OT3API, delta: float) -> None: def get_endstop_position_ot3(api: OT3API, mount: OT3Mount) -> Dict[Axis, float]: """Get the endstop's position per mount.""" - carriage_pos = api._deck_from_machine(api._backend.home_position()) + carriage_pos = api.get_deck_from_machine(api._backend.home_position()) pos_at_home = api._effector_pos_from_carriage_pos( OT3Mount.from_mount(mount), carriage_pos, None ) From e4ee72e9091bc852a22cd64357270945f07f9583 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 22 Oct 2024 23:03:18 +0300 Subject: [PATCH 33/46] fix relative movements --- .../opentrons/protocol_engine/execution/gantry_mover.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 0836a4600eb..41a081fb9d6 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -314,15 +314,12 @@ async def move_axes( mount, refresh=True ) log.info(f"The current position of the robot is: {current_position}.") - converted_current_position_deck = self._hardware_api.get_deck_from_machine( - current_position - ) - log.info(f"The converted deck position of the robot is: {converted_current_position_deck}.") + absolute_pos = target_axis_map_from_relative( - pos_hw, converted_current_position_deck + pos_hw, current_position ) log.info( - f"The absolute position is: {absolute_pos} and hw pos map is {absolute_pos}." + f"The absolute position is: {absolute_pos} and hw pos map is {pos_hw}." ) else: log.info(f"Absolute move {axis_map} and {mount}") From c93de27e52e04d74c40c892222e00219e868262c Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 22 Oct 2024 23:03:40 +0300 Subject: [PATCH 34/46] handle robot context loading skipping for legacy protocols --- .../protocol_api/protocol_context.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index b56d668cb3a..34abf0d2cfd 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -185,11 +185,14 @@ def __init__( self._commands: List[str] = [] self._params: Parameters = Parameters() self._unsubscribe_commands: Optional[Callable[[], None]] = None - self._robot = RobotContext( - core=self._core.load_robot(), - protocol_core=self._core, - api_version=self._api_version, - ) + try: + self._robot = RobotContext( + core=self._core.load_robot(), + protocol_core=self._core, + api_version=self._api_version, + ) + except APIVersionError: + self._robot = None self.clear_commands() @property @@ -221,7 +224,7 @@ def robot(self) -> RobotContext: :meta private: """ - if self._core.robot_type != "OT-3 Standard": + if self._core.robot_type != "OT-3 Standard" or not self._robot: raise RobotTypeError("The RobotContext is only available on Flex robot.") return self._robot @@ -234,7 +237,9 @@ def _hw_manager(self) -> HardwareManager: "This function will be deprecated in later versions." "Please use with caution." ) - return self._robot.hardware + if self._robot: + return self._robot.hardware + return HardwareManager(hardware=self._core.get_hardware()) @property @requires_version(2, 0) From 5ceb32d94f309ca8cd55baa9e5d5e9e875893362 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Wed, 23 Oct 2024 21:13:23 +0300 Subject: [PATCH 35/46] additional attempt at fixing move rel --- .../protocol_engine/execution/gantry_mover.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 41a081fb9d6..d8d14e7e0d0 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -315,22 +315,22 @@ async def move_axes( ) log.info(f"The current position of the robot is: {current_position}.") - absolute_pos = target_axis_map_from_relative( + pos_hw = target_axis_map_from_relative( pos_hw, current_position ) log.info( - f"The absolute position is: {absolute_pos} and hw pos map is {pos_hw}." - ) - else: - log.info(f"Absolute move {axis_map} and {mount}") - absolute_pos = target_axis_map_from_absolute( - mount, - pos_hw, - partial(self._critical_point_for, cp_override=critical_point), - Point(*self._hardware_api.config.left_mount_offset), - Point(*self._hardware_api.config.right_mount_offset), - Point(*self._hardware_api.config.gripper_mount_offset), + f"The absolute position is: {pos_hw} and hw pos map is {pos_hw}." ) + log.info(f"The calculated move {pos_hw} and {mount}") + absolute_pos = target_axis_map_from_absolute( + mount, + pos_hw, + partial(self._critical_point_for, cp_override=critical_point), + Point(*self._hardware_api.config.left_mount_offset), + Point(*self._hardware_api.config.right_mount_offset), + Point(*self._hardware_api.config.gripper_mount_offset), + ) + log.info(f"The prepped abs {absolute_pos}") await self._hardware_api.move_axes( position=absolute_pos, speed=speed, From 028a49a937c4605ebcf226361df865be4ce61abe Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Fri, 1 Nov 2024 15:55:52 +0200 Subject: [PATCH 36/46] fix tests, lint and formatting --- api/src/opentrons/hardware_control/api.py | 20 +++++++++--- api/src/opentrons/hardware_control/ot3api.py | 4 ++- .../protocols/motion_controller.py | 5 +-- .../protocol_api/protocol_context.py | 2 +- .../protocol_engine/execution/gantry_mover.py | 29 ++++++++++++----- .../protocol_engine/execution/movement.py | 2 +- .../backends/test_ot3_controller.py | 2 +- .../protocol_api/test_robot_context.py | 15 +++++---- .../execution/test_gantry_mover.py | 31 ++++++++++--------- 9 files changed, 71 insertions(+), 39 deletions(-) diff --git a/api/src/opentrons/hardware_control/api.py b/api/src/opentrons/hardware_control/api.py index 3c715daa0d3..2e2bbfef116 100644 --- a/api/src/opentrons/hardware_control/api.py +++ b/api/src/opentrons/hardware_control/api.py @@ -169,7 +169,9 @@ def _update_door_state(self, door_state: DoorState) -> None: def _reset_last_mount(self) -> None: self._last_moved_mount = None - def get_deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + def get_deck_from_machine( + self, machine_pos: Dict[Axis, float] + ) -> Dict[Axis, float]: return deck_from_machine( machine_pos=machine_pos, attitude=self._robot_calibration.deck_calibration.attitude, @@ -665,7 +667,9 @@ async def home(self, axes: Optional[List[Axis]] = None) -> None: async with self._motion_lock: if smoothie_gantry: smoothie_pos.update(await self._backend.home(smoothie_gantry)) - self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) + ) for plunger in plungers: await self._do_plunger_home(axis=plunger, acquire_lock=False) @@ -706,7 +710,9 @@ async def current_position( async with self._motion_lock: if refresh: smoothie_pos = await self._backend.update_position() - self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) + ) if mount == top_types.Mount.RIGHT: offset = top_types.Point(0, 0, 0) else: @@ -936,7 +942,9 @@ async def retract_axis(self, axis: Axis, margin: float = 10) -> None: async with self._motion_lock: smoothie_pos = await self._fast_home(smoothie_ax, margin) - self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) + ) # Gantry/frame (i.e. not pipette) config API @property @@ -1249,7 +1257,9 @@ async def tip_drop_moves( axes=[ot2_axis_to_string(ax) for ax in move.home_axes], margin=move.home_after_safety_margin, ) - self._current_position = self.get_deck_from_machine(self._axis_map_from_string_map(smoothie_pos)) + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) + ) for shake in spec.shake_moves: await self.move_rel(mount, shake[0], speed=shake[1]) diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 8add3bede3a..bd828cd525f 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -352,7 +352,9 @@ def _update_estop_state(self, event: HardwareEvent) -> "List[Future[None]]": def _reset_last_mount(self) -> None: self._last_moved_mount = None - def get_deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + def get_deck_from_machine( + self, machine_pos: Dict[Axis, float] + ) -> Dict[Axis, float]: return deck_from_machine( machine_pos=machine_pos, attitude=self._robot_calibration.deck_calibration.attitude, diff --git a/api/src/opentrons/hardware_control/protocols/motion_controller.py b/api/src/opentrons/hardware_control/protocols/motion_controller.py index 5a7d27c7f0b..6fda13f8f38 100644 --- a/api/src/opentrons/hardware_control/protocols/motion_controller.py +++ b/api/src/opentrons/hardware_control/protocols/motion_controller.py @@ -9,11 +9,12 @@ class MotionController(Protocol[MountArgType]): """Protocol specifying fundamental motion controls.""" - def get_deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + def get_deck_from_machine( + self, machine_pos: Dict[Axis, float] + ) -> Dict[Axis, float]: """Convert machine coordinates to deck coordinates.""" ... - async def halt(self, disengage_before_stopping: bool = False) -> None: """Immediately stop motion. diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 34abf0d2cfd..26f2ab1a161 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -186,7 +186,7 @@ def __init__( self._params: Parameters = Parameters() self._unsubscribe_commands: Optional[Callable[[], None]] = None try: - self._robot = RobotContext( + self._robot: Optional[RobotContext] = RobotContext( core=self._core.load_robot(), protocol_core=self._core, api_version=self._api_version, diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index d8d14e7e0d0..7959e792275 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -1,8 +1,8 @@ """Gantry movement wrapper for hardware and simulation based movement.""" from logging import getLogger - +from opentrons.config.types import OT3Config from functools import partial -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Tuple from typing_extensions import Protocol as TypingProtocol from opentrons.types import Point, Mount, MountType @@ -190,6 +190,20 @@ def _critical_point_for( else: return self._hardware_api.critical_point_for(mount) + def _get_gantry_offsets_for_robot_type(self) -> Tuple[Point, Point, Optional[Point]]: + if isinstance(self._hardware_api.config, OT3Config): + return ( + Point(*self._hardware_api.config.left_mount_offset), + Point(*self._hardware_api.config.right_mount_offset), + Point(*self._hardware_api.config.gripper_mount_offset), + ) + else: + return ( + Point(*self._hardware_api.config.left_mount_offset), + Point(0, 0, 0), + None, + ) + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" found_mount = Mount.LEFT @@ -315,20 +329,19 @@ async def move_axes( ) log.info(f"The current position of the robot is: {current_position}.") - pos_hw = target_axis_map_from_relative( - pos_hw, current_position - ) + pos_hw = target_axis_map_from_relative(pos_hw, current_position) log.info( f"The absolute position is: {pos_hw} and hw pos map is {pos_hw}." ) log.info(f"The calculated move {pos_hw} and {mount}") + left_offset, right_offset, gripper_offset = self._get_gantry_offsets_for_robot_type() absolute_pos = target_axis_map_from_absolute( mount, pos_hw, partial(self._critical_point_for, cp_override=critical_point), - Point(*self._hardware_api.config.left_mount_offset), - Point(*self._hardware_api.config.right_mount_offset), - Point(*self._hardware_api.config.gripper_mount_offset), + left_mount_offset=left_offset, + right_mount_offset=right_offset, + gripper_mount_offset=gripper_offset, ) log.info(f"The prepped abs {absolute_pos}") await self._hardware_api.move_axes( diff --git a/api/src/opentrons/protocol_engine/execution/movement.py b/api/src/opentrons/protocol_engine/execution/movement.py index a06c5637e9d..d05bc3851e7 100644 --- a/api/src/opentrons/protocol_engine/execution/movement.py +++ b/api/src/opentrons/protocol_engine/execution/movement.py @@ -294,7 +294,7 @@ async def move_to_coordinates( ), max_travel_z=max_travel_z, direct=direct, - additional_min_travel_z=None, + additional_min_travel_z=additional_min_travel_z, ) speed = self._state_store.pipettes.get_movement_speed( diff --git a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py index c95fafa574a..1035649b7f5 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py @@ -202,7 +202,7 @@ def mock_move_group_run() -> Iterator[mock.AsyncMock]: "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner.run", autospec=True, ) as mock_mgr_run: - mock_mgr_run.side_effect = {} + mock_mgr_run.return_value = {} yield mock_mgr_run diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py index 7918a83294b..7d1531705b8 100644 --- a/api/tests/opentrons/protocol_api/test_robot_context.py +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -33,7 +33,9 @@ def api_version() -> APIVersion: @pytest.fixture def mock_deck(decoy: Decoy) -> Deck: deck = decoy.mock(cls=Deck) - decoy.when(deck.get_slot_center(DeckSlotName.SLOT_D1)).then_return(Point(3, 3, 3)) + decoy.when(deck.get_slot_center(DeckSlotName.SLOT_D1.value)).then_return( + Point(3, 3, 3) + ) return deck @@ -43,7 +45,6 @@ def mock_protocol(decoy: Decoy, mock_deck: Deck, mock_core: RobotCore) -> Protoc protocol_core = decoy.mock(cls=ProtocolCore) decoy.when(protocol_core.robot_type).then_return("OT-3 Standard") decoy.when(protocol_core.load_robot()).then_return(mock_core) - decoy.when(protocol_core._deck).then_return(mock_deck) return protocol_core @@ -76,9 +77,12 @@ def test_move_to( speed: Optional[float], ) -> None: subject.move_to(mount, destination, speed) - if mount == "left": - mount = Mount.LEFT - decoy.verify(subject._core.move_to(mount, destination, speed)) + core_mount: Mount + if isinstance(mount, str): + core_mount = Mount.string_to_mount(mount) + else: + core_mount = mount + decoy.verify(subject._core.move_to(core_mount, destination.point, speed)) @pytest.mark.parametrize( @@ -151,7 +155,6 @@ def test_move_axes_relative( Location(point=Point(1, 2, 3), labware=None), {AxisType.Z_L: 3, AxisType.X: 1, AxisType.Y: 2}, ), - (Mount.RIGHT, "D1", {AxisType.Z_R: 3, AxisType.X: 3, AxisType.Y: 3}), ( Mount.EXTENSION, Location(point=Point(1, 2, 3), labware=None), diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index ccbdf34867a..01ff8d9c538 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -4,6 +4,7 @@ import pytest from decoy import Decoy from typing import TYPE_CHECKING, Dict, Optional +from collections import OrderedDict from opentrons.types import Mount, MountType, Point from opentrons.hardware_control import API as HardwareAPI @@ -481,7 +482,9 @@ async def test_home_z( {MotorAxis.X: 2.0, MotorAxis.Y: 1.0, MotorAxis.RIGHT_Z: 1.0}, False, Mount.RIGHT, - {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 9.0}, + OrderedDict( + {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 19.0} + ), {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 9.0}, ], [ @@ -489,7 +492,7 @@ async def test_home_z( None, True, Mount.RIGHT, - {HardwareAxis.A: 30.0}, + OrderedDict({HardwareAxis.A: 29.0}), { HardwareAxis.X: 10.0, HardwareAxis.Y: 15.0, @@ -502,20 +505,20 @@ async def test_home_z( None, False, Mount.LEFT, - {HardwareAxis.Q: 10.0}, + OrderedDict({HardwareAxis.Q: 10.0}), {HardwareAxis.Q: 10.0}, ], ], ) async def test_move_axes( decoy: Decoy, - mock_hardware_api: HardwareAPI, + ot3_hardware_api: OT3API, hardware_subject: HardwareGantryMover, axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]], expected_mount: Mount, relative_move: bool, - call_to_hw: Dict[HardwareAxis, float], + call_to_hw: "OrderedDict[HardwareAxis, float]", final_position: Dict[HardwareAxis, float], ) -> None: curr_pos = { @@ -537,30 +540,30 @@ def _current_position(mount: Mount, refresh: bool) -> Dict[HardwareAxis, float]: return final_position decoy.when( - await mock_hardware_api.current_position(expected_mount, refresh=True) + await ot3_hardware_api.current_position(expected_mount, refresh=True) ).then_do(_current_position) - decoy.when(mock_hardware_api.config.left_mount_offset).then_return(Point(1, 1, 1)) - decoy.when(mock_hardware_api.config.right_mount_offset).then_return( + decoy.when(ot3_hardware_api.config.left_mount_offset).then_return(Point(1, 1, 1)) + decoy.when(ot3_hardware_api.config.right_mount_offset).then_return( Point(10, 10, 10) ) - decoy.when(mock_hardware_api.config.gripper_mount_offset).then_return( + decoy.when(ot3_hardware_api.config.gripper_mount_offset).then_return( Point(0.5, 0.5, 0.5) ) - decoy.when(mock_hardware_api.get_deck_from_machine(curr_pos)).then_return(curr_pos) + decoy.when(ot3_hardware_api.get_deck_from_machine(curr_pos)).then_return(curr_pos) - decoy.when(mock_hardware_api.get_deck_from_machine(final_position)).then_return( + decoy.when(ot3_hardware_api.get_deck_from_machine(final_position)).then_return( final_position ) if not critical_point: - decoy.when(mock_hardware_api.critical_point_for(expected_mount)).then_return( + decoy.when(ot3_hardware_api.critical_point_for(expected_mount)).then_return( Point(1, 1, 1) ) pos = await hardware_subject.move_axes(axis_map, critical_point, 100, relative_move) decoy.verify( - await mock_hardware_api.move_axes(position=call_to_hw, speed=100), + await ot3_hardware_api.move_axes(position=call_to_hw, speed=100), times=1, ) assert pos == { @@ -696,7 +699,7 @@ async def test_virtual_move_axes( axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]], relative_move: bool, - expected_position: Dict[HardwareAxis, float], + expected_position: Dict[MotorAxis, float], ) -> None: pos = await virtual_subject.move_axes(axis_map, critical_point, 100, relative_move) assert pos == expected_position From 1336292f4492ee562392584dd015f264afc86ed6 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Fri, 1 Nov 2024 17:31:48 +0200 Subject: [PATCH 37/46] fixups from rebase --- .../commands/robot/move_axes_relative.py | 7 ++----- .../commands/robot/move_axes_to.py | 7 ++----- .../protocol_engine/commands/robot/move_to.py | 5 ++--- .../protocol_engine/execution/gantry_mover.py | 19 +++++++++++++------ .../robot/test_move_axes_relative_to.py | 3 +-- .../commands/robot/test_move_axes_to.py | 3 +-- .../commands/robot/test_move_to.py | 3 +-- .../execution/test_gantry_mover.py | 10 +++++++--- 8 files changed, 29 insertions(+), 28 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py index 34beb02ad08..bd1da7b60c6 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py @@ -42,9 +42,7 @@ class MoveAxesRelativeResult(DestinationRobotPositionResult): class MoveAxesRelativeImplementation( - AbstractCommandImpl[ - MoveAxesRelativeParams, SuccessData[MoveAxesRelativeResult, None] - ] + AbstractCommandImpl[MoveAxesRelativeParams, SuccessData[MoveAxesRelativeResult]] ): """MoveAxesRelative command implementation.""" @@ -59,7 +57,7 @@ def __init__( async def execute( self, params: MoveAxesRelativeParams - ) -> SuccessData[MoveAxesRelativeResult, None]: + ) -> SuccessData[MoveAxesRelativeResult]: # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller # and then we can remove this validation. ensure_ot3_hardware(self._hardware_api) @@ -69,7 +67,6 @@ async def execute( ) return SuccessData( public=MoveAxesRelativeResult(position=current_position), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index 27e520282ef..98b8128db29 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -45,7 +45,7 @@ class MoveAxesToResult(DestinationRobotPositionResult): class MoveAxesToImplementation( - AbstractCommandImpl[MoveAxesToParams, SuccessData[MoveAxesToResult, None]] + AbstractCommandImpl[MoveAxesToParams, SuccessData[MoveAxesToResult]] ): """MoveAxesTo command implementation.""" @@ -58,9 +58,7 @@ def __init__( self._gantry_mover = gantry_mover self._hardware_api = hardware_api - async def execute( - self, params: MoveAxesToParams - ) -> SuccessData[MoveAxesToResult, None]: + async def execute(self, params: MoveAxesToParams) -> SuccessData[MoveAxesToResult]: # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller # and then we can remove this validation. ensure_ot3_hardware(self._hardware_api) @@ -71,7 +69,6 @@ async def execute( ) return SuccessData( public=MoveAxesToResult(position=current_position), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_to.py index a2713982d60..4b310bcd9ab 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_to.py @@ -48,7 +48,7 @@ class MoveToResult(DestinationPositionResult): class MoveToImplementation( - AbstractCommandImpl[MoveToParams, SuccessData[MoveToResult, None]] + AbstractCommandImpl[MoveToParams, SuccessData[MoveToResult]] ): """MoveTo command implementation.""" @@ -59,13 +59,12 @@ def __init__( ) -> None: self._movement = movement - async def execute(self, params: MoveToParams) -> SuccessData[MoveToResult, None]: + async def execute(self, params: MoveToParams) -> SuccessData[MoveToResult]: x, y, z = await self._movement.move_mount_to( mount=params.mount, destination=params.destination, speed=params.speed ) return SuccessData( public=MoveToResult(position=DeckPoint(x=x, y=y, z=z)), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 7959e792275..c76be093c7f 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -190,7 +190,9 @@ def _critical_point_for( else: return self._hardware_api.critical_point_for(mount) - def _get_gantry_offsets_for_robot_type(self) -> Tuple[Point, Point, Optional[Point]]: + def _get_gantry_offsets_for_robot_type( + self, + ) -> Tuple[Point, Point, Optional[Point]]: if isinstance(self._hardware_api.config, OT3Config): return ( Point(*self._hardware_api.config.left_mount_offset), @@ -334,7 +336,11 @@ async def move_axes( f"The absolute position is: {pos_hw} and hw pos map is {pos_hw}." ) log.info(f"The calculated move {pos_hw} and {mount}") - left_offset, right_offset, gripper_offset = self._get_gantry_offsets_for_robot_type() + ( + left_offset, + right_offset, + gripper_offset, + ) = self._get_gantry_offsets_for_robot_type() absolute_pos = target_axis_map_from_absolute( mount, pos_hw, @@ -530,10 +536,11 @@ def get_max_travel_z_from_mount(self, mount: MountType) -> float: ) else: instrument_height = VIRTUAL_MAX_OT3_HEIGHT - tip_length = ( - self._state_view.tips.get_tip_length(pipette.id) if pipette else 0.0 - ) - + if pipette: + tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette.id) + tip_length = tip.length if tip is not None else 0.0 + else: + tip_length = 0.0 return instrument_height - tip_length async def move_axes( diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py index f16a0c4835d..9fb31ba4e82 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py @@ -49,6 +49,5 @@ async def test_move_axes_to_implementation( assert result == SuccessData( public=MoveAxesRelativeResult( position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} - ), - private=None, + ) ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py index ad6a4659c8a..f85d1b24058 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py @@ -51,6 +51,5 @@ async def test_move_axes_to_implementation( assert result == SuccessData( public=MoveAxesToResult( position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} - ), - private=None, + ) ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py index 09604a38622..28bd5b6df33 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py @@ -43,6 +43,5 @@ async def test_move_to_implementation( result = await subject.execute(params=params) assert result == SuccessData( - public=MoveToResult(position=DeckPoint(x=4.44, y=5.55, z=6.66)), - private=None, + public=MoveToResult(position=DeckPoint(x=4.44, y=5.55, z=6.66)) ) diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index 01ff8d9c538..7712faf1408 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -510,10 +510,11 @@ async def test_home_z( ], ], ) +@pytest.mark.ot3_only async def test_move_axes( decoy: Decoy, ot3_hardware_api: OT3API, - hardware_subject: HardwareGantryMover, + mock_state_view: StateView, axis_map: Dict[MotorAxis, float], critical_point: Optional[Dict[MotorAxis, float]], expected_mount: Mount, @@ -521,6 +522,9 @@ async def test_move_axes( call_to_hw: "OrderedDict[HardwareAxis, float]", final_position: Dict[HardwareAxis, float], ) -> None: + subject = HardwareGantryMover( + state_view=mock_state_view, hardware_api=ot3_hardware_api + ) curr_pos = { HardwareAxis.X: 10.0, HardwareAxis.Y: 15.0, @@ -561,13 +565,13 @@ def _current_position(mount: Mount, refresh: bool) -> Dict[HardwareAxis, float]: Point(1, 1, 1) ) - pos = await hardware_subject.move_axes(axis_map, critical_point, 100, relative_move) + pos = await subject.move_axes(axis_map, critical_point, 100, relative_move) decoy.verify( await ot3_hardware_api.move_axes(position=call_to_hw, speed=100), times=1, ) assert pos == { - hardware_subject._hardware_axis_to_motor_axis(ax): pos + subject._hardware_axis_to_motor_axis(ax): pos for ax, pos in final_position.items() } From 7e9440e612a5a256bebf1c840d74bbde71ea0ea6 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Fri, 1 Nov 2024 17:46:41 +0200 Subject: [PATCH 38/46] bump schemma to version 11 --- shared-data/command/schemas/11.json | 4994 +++++++++++++++++++++++++++ 1 file changed, 4994 insertions(+) create mode 100644 shared-data/command/schemas/11.json diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json new file mode 100644 index 00000000000..465708864ce --- /dev/null +++ b/shared-data/command/schemas/11.json @@ -0,0 +1,4994 @@ +{ + "title": "CreateCommandUnion", + "description": "Model that validates a union of all CommandCreate models.", + "discriminator": { + "propertyName": "commandType", + "mapping": { + "aspirate": "#/definitions/AspirateCreate", + "aspirateInPlace": "#/definitions/AspirateInPlaceCreate", + "comment": "#/definitions/CommentCreate", + "configureForVolume": "#/definitions/ConfigureForVolumeCreate", + "configureNozzleLayout": "#/definitions/ConfigureNozzleLayoutCreate", + "custom": "#/definitions/CustomCreate", + "dispense": "#/definitions/DispenseCreate", + "dispenseInPlace": "#/definitions/DispenseInPlaceCreate", + "blowout": "#/definitions/BlowOutCreate", + "blowOutInPlace": "#/definitions/BlowOutInPlaceCreate", + "dropTip": "#/definitions/DropTipCreate", + "dropTipInPlace": "#/definitions/DropTipInPlaceCreate", + "home": "#/definitions/HomeCreate", + "retractAxis": "#/definitions/RetractAxisCreate", + "loadLabware": "#/definitions/LoadLabwareCreate", + "reloadLabware": "#/definitions/ReloadLabwareCreate", + "loadLiquid": "#/definitions/LoadLiquidCreate", + "loadModule": "#/definitions/LoadModuleCreate", + "loadPipette": "#/definitions/LoadPipetteCreate", + "moveLabware": "#/definitions/MoveLabwareCreate", + "moveRelative": "#/definitions/MoveRelativeCreate", + "moveToCoordinates": "#/definitions/MoveToCoordinatesCreate", + "moveToWell": "#/definitions/MoveToWellCreate", + "moveToAddressableArea": "#/definitions/MoveToAddressableAreaCreate", + "moveToAddressableAreaForDropTip": "#/definitions/MoveToAddressableAreaForDropTipCreate", + "prepareToAspirate": "#/definitions/PrepareToAspirateCreate", + "waitForResume": "#/definitions/WaitForResumeCreate", + "pause": "#/definitions/WaitForResumeCreate", + "waitForDuration": "#/definitions/WaitForDurationCreate", + "pickUpTip": "#/definitions/PickUpTipCreate", + "savePosition": "#/definitions/SavePositionCreate", + "setRailLights": "#/definitions/SetRailLightsCreate", + "touchTip": "#/definitions/TouchTipCreate", + "setStatusBar": "#/definitions/SetStatusBarCreate", + "verifyTipPresence": "#/definitions/VerifyTipPresenceCreate", + "getTipPresence": "#/definitions/GetTipPresenceCreate", + "liquidProbe": "#/definitions/LiquidProbeCreate", + "tryLiquidProbe": "#/definitions/TryLiquidProbeCreate", + "heaterShaker/waitForTemperature": "#/definitions/opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureCreate", + "heaterShaker/setTargetTemperature": "#/definitions/opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureCreate", + "heaterShaker/deactivateHeater": "#/definitions/DeactivateHeaterCreate", + "heaterShaker/setAndWaitForShakeSpeed": "#/definitions/SetAndWaitForShakeSpeedCreate", + "heaterShaker/deactivateShaker": "#/definitions/DeactivateShakerCreate", + "heaterShaker/openLabwareLatch": "#/definitions/OpenLabwareLatchCreate", + "heaterShaker/closeLabwareLatch": "#/definitions/CloseLabwareLatchCreate", + "magneticModule/disengage": "#/definitions/DisengageCreate", + "magneticModule/engage": "#/definitions/EngageCreate", + "temperatureModule/setTargetTemperature": "#/definitions/opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureCreate", + "temperatureModule/waitForTemperature": "#/definitions/opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureCreate", + "temperatureModule/deactivate": "#/definitions/DeactivateTemperatureCreate", + "thermocycler/setTargetBlockTemperature": "#/definitions/SetTargetBlockTemperatureCreate", + "thermocycler/waitForBlockTemperature": "#/definitions/WaitForBlockTemperatureCreate", + "thermocycler/setTargetLidTemperature": "#/definitions/SetTargetLidTemperatureCreate", + "thermocycler/waitForLidTemperature": "#/definitions/WaitForLidTemperatureCreate", + "thermocycler/deactivateBlock": "#/definitions/DeactivateBlockCreate", + "thermocycler/deactivateLid": "#/definitions/DeactivateLidCreate", + "thermocycler/openLid": "#/definitions/opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidCreate", + "thermocycler/closeLid": "#/definitions/opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidCreate", + "thermocycler/runProfile": "#/definitions/RunProfileCreate", + "thermocycler/runExtendedProfile": "#/definitions/RunExtendedProfileCreate", + "absorbanceReader/closeLid": "#/definitions/opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidCreate", + "absorbanceReader/openLid": "#/definitions/opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidCreate", + "absorbanceReader/initialize": "#/definitions/InitializeCreate", + "absorbanceReader/read": "#/definitions/ReadAbsorbanceCreate", + "calibration/calibrateGripper": "#/definitions/CalibrateGripperCreate", + "calibration/calibratePipette": "#/definitions/CalibratePipetteCreate", + "calibration/calibrateModule": "#/definitions/CalibrateModuleCreate", + "calibration/moveToMaintenancePosition": "#/definitions/MoveToMaintenancePositionCreate", + "unsafe/blowOutInPlace": "#/definitions/UnsafeBlowOutInPlaceCreate", + "unsafe/dropTipInPlace": "#/definitions/UnsafeDropTipInPlaceCreate", + "unsafe/updatePositionEstimators": "#/definitions/UpdatePositionEstimatorsCreate", + "unsafe/engageAxes": "#/definitions/UnsafeEngageAxesCreate", + "unsafe/ungripLabware": "#/definitions/UnsafeUngripLabwareCreate", + "unsafe/placeLabware": "#/definitions/UnsafePlaceLabwareCreate", + "robot/moveAxesRelative": "#/definitions/MoveAxesRelativeCreate", + "robot/moveAxesTo": "#/definitions/MoveAxesToCreate", + "robot/moveTo": "#/definitions/MoveToCreate" + } + }, + "oneOf": [ + { + "$ref": "#/definitions/AspirateCreate" + }, + { + "$ref": "#/definitions/AspirateInPlaceCreate" + }, + { + "$ref": "#/definitions/CommentCreate" + }, + { + "$ref": "#/definitions/ConfigureForVolumeCreate" + }, + { + "$ref": "#/definitions/ConfigureNozzleLayoutCreate" + }, + { + "$ref": "#/definitions/CustomCreate" + }, + { + "$ref": "#/definitions/DispenseCreate" + }, + { + "$ref": "#/definitions/DispenseInPlaceCreate" + }, + { + "$ref": "#/definitions/BlowOutCreate" + }, + { + "$ref": "#/definitions/BlowOutInPlaceCreate" + }, + { + "$ref": "#/definitions/DropTipCreate" + }, + { + "$ref": "#/definitions/DropTipInPlaceCreate" + }, + { + "$ref": "#/definitions/HomeCreate" + }, + { + "$ref": "#/definitions/RetractAxisCreate" + }, + { + "$ref": "#/definitions/LoadLabwareCreate" + }, + { + "$ref": "#/definitions/ReloadLabwareCreate" + }, + { + "$ref": "#/definitions/LoadLiquidCreate" + }, + { + "$ref": "#/definitions/LoadModuleCreate" + }, + { + "$ref": "#/definitions/LoadPipetteCreate" + }, + { + "$ref": "#/definitions/MoveLabwareCreate" + }, + { + "$ref": "#/definitions/MoveRelativeCreate" + }, + { + "$ref": "#/definitions/MoveToCoordinatesCreate" + }, + { + "$ref": "#/definitions/MoveToWellCreate" + }, + { + "$ref": "#/definitions/MoveToAddressableAreaCreate" + }, + { + "$ref": "#/definitions/MoveToAddressableAreaForDropTipCreate" + }, + { + "$ref": "#/definitions/PrepareToAspirateCreate" + }, + { + "$ref": "#/definitions/WaitForResumeCreate" + }, + { + "$ref": "#/definitions/WaitForDurationCreate" + }, + { + "$ref": "#/definitions/PickUpTipCreate" + }, + { + "$ref": "#/definitions/SavePositionCreate" + }, + { + "$ref": "#/definitions/SetRailLightsCreate" + }, + { + "$ref": "#/definitions/TouchTipCreate" + }, + { + "$ref": "#/definitions/SetStatusBarCreate" + }, + { + "$ref": "#/definitions/VerifyTipPresenceCreate" + }, + { + "$ref": "#/definitions/GetTipPresenceCreate" + }, + { + "$ref": "#/definitions/LiquidProbeCreate" + }, + { + "$ref": "#/definitions/TryLiquidProbeCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureCreate" + }, + { + "$ref": "#/definitions/DeactivateHeaterCreate" + }, + { + "$ref": "#/definitions/SetAndWaitForShakeSpeedCreate" + }, + { + "$ref": "#/definitions/DeactivateShakerCreate" + }, + { + "$ref": "#/definitions/OpenLabwareLatchCreate" + }, + { + "$ref": "#/definitions/CloseLabwareLatchCreate" + }, + { + "$ref": "#/definitions/DisengageCreate" + }, + { + "$ref": "#/definitions/EngageCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureCreate" + }, + { + "$ref": "#/definitions/DeactivateTemperatureCreate" + }, + { + "$ref": "#/definitions/SetTargetBlockTemperatureCreate" + }, + { + "$ref": "#/definitions/WaitForBlockTemperatureCreate" + }, + { + "$ref": "#/definitions/SetTargetLidTemperatureCreate" + }, + { + "$ref": "#/definitions/WaitForLidTemperatureCreate" + }, + { + "$ref": "#/definitions/DeactivateBlockCreate" + }, + { + "$ref": "#/definitions/DeactivateLidCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidCreate" + }, + { + "$ref": "#/definitions/RunProfileCreate" + }, + { + "$ref": "#/definitions/RunExtendedProfileCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidCreate" + }, + { + "$ref": "#/definitions/opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidCreate" + }, + { + "$ref": "#/definitions/InitializeCreate" + }, + { + "$ref": "#/definitions/ReadAbsorbanceCreate" + }, + { + "$ref": "#/definitions/CalibrateGripperCreate" + }, + { + "$ref": "#/definitions/CalibratePipetteCreate" + }, + { + "$ref": "#/definitions/CalibrateModuleCreate" + }, + { + "$ref": "#/definitions/MoveToMaintenancePositionCreate" + }, + { + "$ref": "#/definitions/UnsafeBlowOutInPlaceCreate" + }, + { + "$ref": "#/definitions/UnsafeDropTipInPlaceCreate" + }, + { + "$ref": "#/definitions/UpdatePositionEstimatorsCreate" + }, + { + "$ref": "#/definitions/UnsafeEngageAxesCreate" + }, + { + "$ref": "#/definitions/UnsafeUngripLabwareCreate" + }, + { + "$ref": "#/definitions/UnsafePlaceLabwareCreate" + }, + { + "$ref": "#/definitions/MoveAxesRelativeCreate" + }, + { + "$ref": "#/definitions/MoveAxesToCreate" + }, + { + "$ref": "#/definitions/MoveToCreate" + } + ], + "definitions": { + "WellOrigin": { + "title": "WellOrigin", + "description": "Origin of WellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well\n MENISCUS: the meniscus-center of the well", + "enum": ["top", "bottom", "center", "meniscus"], + "type": "string" + }, + "WellOffset": { + "title": "WellOffset", + "description": "An offset vector in (x, y, z).", + "type": "object", + "properties": { + "x": { + "title": "X", + "default": 0, + "type": "number" + }, + "y": { + "title": "Y", + "default": 0, + "type": "number" + }, + "z": { + "title": "Z", + "default": 0, + "type": "number" + } + } + }, + "LiquidHandlingWellLocation": { + "title": "LiquidHandlingWellLocation", + "description": "A relative location in reference to a well's location.\n\nTo be used with commands that handle liquids.", + "type": "object", + "properties": { + "origin": { + "default": "top", + "allOf": [ + { + "$ref": "#/definitions/WellOrigin" + } + ] + }, + "offset": { + "$ref": "#/definitions/WellOffset" + }, + "volumeOffset": { + "title": "Volumeoffset", + "description": "A volume of liquid, in \u00b5L, to offset the z-axis offset. When \"operationVolume\" is specified, this volume is pulled from the command volume parameter.", + "default": 0.0, + "anyOf": [ + { + "type": "number" + }, + { + "enum": ["operationVolume"], + "type": "string" + } + ] + } + } + }, + "AspirateParams": { + "title": "AspirateParams", + "description": "Parameters required to aspirate from a specific well.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/LiquidHandlingWellLocation" + } + ] + }, + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "The amount of liquid to aspirate, in \u00b5L. Must not be greater than the remaining available amount, which depends on the pipette (see `loadPipette`), its configuration (see `configureForVolume`), the tip (see `pickUpTip`), and the amount you've aspirated so far. There is some tolerance for floating point rounding errors.", + "minimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] + }, + "CommandIntent": { + "title": "CommandIntent", + "description": "Run intent for a given command.\n\nProps:\n PROTOCOL: the command is part of the protocol run itself.\n SETUP: the command is part of the setup phase of a run.", + "enum": ["protocol", "setup", "fixit"], + "type": "string" + }, + "AspirateCreate": { + "title": "AspirateCreate", + "description": "Create aspirate command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "aspirate", + "enum": ["aspirate"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/AspirateParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "AspirateInPlaceParams": { + "title": "AspirateInPlaceParams", + "description": "Payload required to aspirate in place.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "The amount of liquid to aspirate, in \u00b5L. Must not be greater than the remaining available amount, which depends on the pipette (see `loadPipette`), its configuration (see `configureForVolume`), the tip (see `pickUpTip`), and the amount you've aspirated so far. There is some tolerance for floating point rounding errors.", + "minimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["flowRate", "volume", "pipetteId"] + }, + "AspirateInPlaceCreate": { + "title": "AspirateInPlaceCreate", + "description": "AspirateInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "aspirateInPlace", + "enum": ["aspirateInPlace"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/AspirateInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "CommentParams": { + "title": "CommentParams", + "description": "Payload required to annotate execution with a comment.", + "type": "object", + "properties": { + "message": { + "title": "Message", + "description": "A user-facing message", + "type": "string" + } + }, + "required": ["message"] + }, + "CommentCreate": { + "title": "CommentCreate", + "description": "Comment command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "comment", + "enum": ["comment"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/CommentParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "ConfigureForVolumeParams": { + "title": "ConfigureForVolumeParams", + "description": "Parameters required to configure volume for a specific pipette.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "volume": { + "title": "Volume", + "description": "Amount of liquid in uL. Must be at least 0 and no greater than a pipette-specific maximum volume.", + "minimum": 0, + "type": "number" + }, + "tipOverlapNotAfterVersion": { + "title": "Tipoverlapnotafterversion", + "description": "A version of tip overlap data to not exceed. The highest-versioned tip overlap data that does not exceed this version will be used. Versions are expressed as vN where N is an integer, counting up from v0. If None, the current highest version will be used.", + "type": "string" + } + }, + "required": ["pipetteId", "volume"] + }, + "ConfigureForVolumeCreate": { + "title": "ConfigureForVolumeCreate", + "description": "Configure for volume command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "configureForVolume", + "enum": ["configureForVolume"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigureForVolumeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "AllNozzleLayoutConfiguration": { + "title": "AllNozzleLayoutConfiguration", + "description": "All basemodel to represent a reset to the nozzle configuration. Sending no parameters resets to default.", + "type": "object", + "properties": { + "style": { + "title": "Style", + "default": "ALL", + "enum": ["ALL"], + "type": "string" + } + } + }, + "SingleNozzleLayoutConfiguration": { + "title": "SingleNozzleLayoutConfiguration", + "description": "Minimum information required for a new nozzle configuration.", + "type": "object", + "properties": { + "style": { + "title": "Style", + "default": "SINGLE", + "enum": ["SINGLE"], + "type": "string" + }, + "primaryNozzle": { + "title": "Primarynozzle", + "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", + "enum": ["A1", "H1", "A12", "H12"], + "type": "string" + } + }, + "required": ["primaryNozzle"] + }, + "RowNozzleLayoutConfiguration": { + "title": "RowNozzleLayoutConfiguration", + "description": "Minimum information required for a new nozzle configuration.", + "type": "object", + "properties": { + "style": { + "title": "Style", + "default": "ROW", + "enum": ["ROW"], + "type": "string" + }, + "primaryNozzle": { + "title": "Primarynozzle", + "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", + "enum": ["A1", "H1", "A12", "H12"], + "type": "string" + } + }, + "required": ["primaryNozzle"] + }, + "ColumnNozzleLayoutConfiguration": { + "title": "ColumnNozzleLayoutConfiguration", + "description": "Information required for nozzle configurations of type ROW and COLUMN.", + "type": "object", + "properties": { + "style": { + "title": "Style", + "default": "COLUMN", + "enum": ["COLUMN"], + "type": "string" + }, + "primaryNozzle": { + "title": "Primarynozzle", + "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", + "enum": ["A1", "H1", "A12", "H12"], + "type": "string" + } + }, + "required": ["primaryNozzle"] + }, + "QuadrantNozzleLayoutConfiguration": { + "title": "QuadrantNozzleLayoutConfiguration", + "description": "Information required for nozzle configurations of type QUADRANT.", + "type": "object", + "properties": { + "style": { + "title": "Style", + "default": "QUADRANT", + "enum": ["QUADRANT"], + "type": "string" + }, + "primaryNozzle": { + "title": "Primarynozzle", + "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", + "enum": ["A1", "H1", "A12", "H12"], + "type": "string" + }, + "frontRightNozzle": { + "title": "Frontrightnozzle", + "description": "The front right nozzle in your configuration.", + "pattern": "[A-Z]\\d{1,2}", + "type": "string" + }, + "backLeftNozzle": { + "title": "Backleftnozzle", + "description": "The back left nozzle in your configuration.", + "pattern": "[A-Z]\\d{1,2}", + "type": "string" + } + }, + "required": ["primaryNozzle", "frontRightNozzle", "backLeftNozzle"] + }, + "ConfigureNozzleLayoutParams": { + "title": "ConfigureNozzleLayoutParams", + "description": "Parameters required to configure the nozzle layout for a specific pipette.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "configurationParams": { + "title": "Configurationparams", + "anyOf": [ + { + "$ref": "#/definitions/AllNozzleLayoutConfiguration" + }, + { + "$ref": "#/definitions/SingleNozzleLayoutConfiguration" + }, + { + "$ref": "#/definitions/RowNozzleLayoutConfiguration" + }, + { + "$ref": "#/definitions/ColumnNozzleLayoutConfiguration" + }, + { + "$ref": "#/definitions/QuadrantNozzleLayoutConfiguration" + } + ] + } + }, + "required": ["pipetteId", "configurationParams"] + }, + "ConfigureNozzleLayoutCreate": { + "title": "ConfigureNozzleLayoutCreate", + "description": "Configure nozzle layout creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "configureNozzleLayout", + "enum": ["configureNozzleLayout"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/ConfigureNozzleLayoutParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "CustomParams": { + "title": "CustomParams", + "description": "Payload used by a custom command.", + "type": "object", + "properties": {} + }, + "CustomCreate": { + "title": "CustomCreate", + "description": "A request to create a custom command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "custom", + "enum": ["custom"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/CustomParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DispenseParams": { + "title": "DispenseParams", + "description": "Payload required to dispense to a specific well.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/LiquidHandlingWellLocation" + } + ] + }, + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "The amount of liquid to dispense, in \u00b5L. Must not be greater than the currently aspirated volume. There is some tolerance for floating point rounding errors.", + "minimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "pushOut": { + "title": "Pushout", + "description": "push the plunger a small amount farther than necessary for accurate low-volume dispensing", + "type": "number" + } + }, + "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] + }, + "DispenseCreate": { + "title": "DispenseCreate", + "description": "Create dispense command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "dispense", + "enum": ["dispense"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DispenseParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DispenseInPlaceParams": { + "title": "DispenseInPlaceParams", + "description": "Payload required to dispense in place.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "The amount of liquid to dispense, in \u00b5L. Must not be greater than the currently aspirated volume. There is some tolerance for floating point rounding errors.", + "minimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "pushOut": { + "title": "Pushout", + "description": "push the plunger a small amount farther than necessary for accurate low-volume dispensing", + "type": "number" + } + }, + "required": ["flowRate", "volume", "pipetteId"] + }, + "DispenseInPlaceCreate": { + "title": "DispenseInPlaceCreate", + "description": "DispenseInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "dispenseInPlace", + "enum": ["dispenseInPlace"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DispenseInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "WellLocation": { + "title": "WellLocation", + "description": "A relative location in reference to a well's location.", + "type": "object", + "properties": { + "origin": { + "default": "top", + "allOf": [ + { + "$ref": "#/definitions/WellOrigin" + } + ] + }, + "offset": { + "$ref": "#/definitions/WellOffset" + }, + "volumeOffset": { + "title": "Volumeoffset", + "description": "A volume of liquid, in \u00b5L, to offset the z-axis offset.", + "default": 0.0, + "type": "number" + } + } + }, + "BlowOutParams": { + "title": "BlowOutParams", + "description": "Payload required to blow-out a specific well.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/WellLocation" + } + ] + }, + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["labwareId", "wellName", "flowRate", "pipetteId"] + }, + "BlowOutCreate": { + "title": "BlowOutCreate", + "description": "Create blow-out command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "blowout", + "enum": ["blowout"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/BlowOutParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "BlowOutInPlaceParams": { + "title": "BlowOutInPlaceParams", + "description": "Payload required to blow-out in place.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["flowRate", "pipetteId"] + }, + "BlowOutInPlaceCreate": { + "title": "BlowOutInPlaceCreate", + "description": "BlowOutInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "blowOutInPlace", + "enum": ["blowOutInPlace"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/BlowOutInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DropTipWellOrigin": { + "title": "DropTipWellOrigin", + "description": "The origin of a DropTipWellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well\n DEFAULT: the default drop-tip location of the well,\n based on pipette configuration and length of the tip.", + "enum": ["top", "bottom", "center", "default"], + "type": "string" + }, + "DropTipWellLocation": { + "title": "DropTipWellLocation", + "description": "Like WellLocation, but for dropping tips.\n\nUnlike a typical WellLocation, the location for a drop tip\ndefaults to location based on the tip length rather than the well's top.", + "type": "object", + "properties": { + "origin": { + "default": "default", + "allOf": [ + { + "$ref": "#/definitions/DropTipWellOrigin" + } + ] + }, + "offset": { + "$ref": "#/definitions/WellOffset" + } + } + }, + "DropTipParams": { + "title": "DropTipParams", + "description": "Payload required to drop a tip in a specific well.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to drop the tip.", + "allOf": [ + { + "$ref": "#/definitions/DropTipWellLocation" + } + ] + }, + "homeAfter": { + "title": "Homeafter", + "description": "Whether to home this pipette's plunger after dropping the tip. You should normally leave this unspecified to let the robot choose a safe default depending on its hardware.", + "type": "boolean" + }, + "alternateDropLocation": { + "title": "Alternatedroplocation", + "description": "Whether to alternate location where tip is dropped within the labware. If True, this command will ignore the wellLocation provided and alternate between dropping tips at two predetermined locations inside the specified labware well. If False, the tip will be dropped at the top center of the well.", + "default": false, + "type": "boolean" + } + }, + "required": ["pipetteId", "labwareId", "wellName"] + }, + "DropTipCreate": { + "title": "DropTipCreate", + "description": "Drop tip command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "dropTip", + "enum": ["dropTip"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DropTipParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DropTipInPlaceParams": { + "title": "DropTipInPlaceParams", + "description": "Payload required to drop a tip in place.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "homeAfter": { + "title": "Homeafter", + "description": "Whether to home this pipette's plunger after dropping the tip. You should normally leave this unspecified to let the robot choose a safe default depending on its hardware.", + "type": "boolean" + } + }, + "required": ["pipetteId"] + }, + "DropTipInPlaceCreate": { + "title": "DropTipInPlaceCreate", + "description": "Drop tip in place command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "dropTipInPlace", + "enum": ["dropTipInPlace"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DropTipInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MotorAxis": { + "title": "MotorAxis", + "description": "Motor axis on which to issue a home command.", + "enum": [ + "x", + "y", + "leftZ", + "rightZ", + "leftPlunger", + "rightPlunger", + "extensionZ", + "extensionJaw", + "clampJaw96Channel" + ], + "type": "string" + }, + "MountType": { + "title": "MountType", + "description": "An enumeration.", + "enum": ["left", "right", "extension"], + "type": "string" + }, + "HomeParams": { + "title": "HomeParams", + "description": "Payload required for a Home command.", + "type": "object", + "properties": { + "axes": { + "description": "Axes to return to their home positions. If omitted, will home all motors. Extra axes may be implicitly homed to ensure accurate homing of the explicitly specified axes.", + "type": "array", + "items": { + "$ref": "#/definitions/MotorAxis" + } + }, + "skipIfMountPositionOk": { + "description": "If this parameter is provided, the gantry will only be homed if the specified mount has an invalid position. If omitted, the homing action will be executed unconditionally.", + "allOf": [ + { + "$ref": "#/definitions/MountType" + } + ] + } + } + }, + "HomeCreate": { + "title": "HomeCreate", + "description": "Data to create a Home command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "home", + "enum": ["home"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/HomeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "RetractAxisParams": { + "title": "RetractAxisParams", + "description": "Payload required for a Retract Axis command.", + "type": "object", + "properties": { + "axis": { + "description": "Axis to retract to its home position as quickly as safely possible. The difference between retracting an axis and homing an axis using the home command is that a home will always probe the limit switch and will work as the first motion command a robot will need to execute; On the other hand, retraction will rely on this previously determined home position to move to it as fast as safely possible. So on the Flex, it will move (fast) the axis to the previously recorded home position and on the OT2, it will move (fast) the axis a safe distance from the previously recorded home position, and then slowly approach the limit switch.", + "allOf": [ + { + "$ref": "#/definitions/MotorAxis" + } + ] + } + }, + "required": ["axis"] + }, + "RetractAxisCreate": { + "title": "RetractAxisCreate", + "description": "Data to create a Retract Axis command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "retractAxis", + "enum": ["retractAxis"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/RetractAxisParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeckSlotName": { + "title": "DeckSlotName", + "description": "Deck slot identifiers.", + "enum": [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "A1", + "A2", + "A3", + "B1", + "B2", + "B3", + "C1", + "C2", + "C3", + "D1", + "D2", + "D3" + ] + }, + "DeckSlotLocation": { + "title": "DeckSlotLocation", + "description": "The location of something placed in a single deck slot.", + "type": "object", + "properties": { + "slotName": { + "description": "A slot on the robot's deck.\n\nThe plain numbers like `\"5\"` are for the OT-2, and the coordinates like `\"C2\"` are for the Flex.\n\nWhen you provide one of these values, you can use either style. It will automatically be converted to match the robot.\n\nWhen one of these values is returned, it will always match the robot.", + "allOf": [ + { + "$ref": "#/definitions/DeckSlotName" + } + ] + } + }, + "required": ["slotName"] + }, + "ModuleLocation": { + "title": "ModuleLocation", + "description": "The location of something placed atop a hardware module.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "The ID of a loaded module from a prior `loadModule` command.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "OnLabwareLocation": { + "title": "OnLabwareLocation", + "description": "The location of something placed atop another labware.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "The ID of a loaded Labware from a prior `loadLabware` command.", + "type": "string" + } + }, + "required": ["labwareId"] + }, + "AddressableAreaLocation": { + "title": "AddressableAreaLocation", + "description": "The location of something place in an addressable area. This is a superset of deck slots.", + "type": "object", + "properties": { + "addressableAreaName": { + "title": "Addressableareaname", + "description": "The name of the addressable area that you want to use. Valid values are the `id`s of `addressableArea`s in the [deck definition](https://github.com/Opentrons/opentrons/tree/edge/shared-data/deck).", + "type": "string" + } + }, + "required": ["addressableAreaName"] + }, + "LoadLabwareParams": { + "title": "LoadLabwareParams", + "description": "Payload required to load a labware into a slot.", + "type": "object", + "properties": { + "location": { + "title": "Location", + "description": "Location the labware should be loaded into.", + "anyOf": [ + { + "$ref": "#/definitions/DeckSlotLocation" + }, + { + "$ref": "#/definitions/ModuleLocation" + }, + { + "$ref": "#/definitions/OnLabwareLocation" + }, + { + "enum": ["offDeck"], + "type": "string" + }, + { + "$ref": "#/definitions/AddressableAreaLocation" + } + ] + }, + "loadName": { + "title": "Loadname", + "description": "Name used to reference a labware definition.", + "type": "string" + }, + "namespace": { + "title": "Namespace", + "description": "The namespace the labware definition belongs to.", + "type": "string" + }, + "version": { + "title": "Version", + "description": "The labware definition version.", + "type": "integer" + }, + "labwareId": { + "title": "Labwareid", + "description": "An optional ID to assign to this labware. If None, an ID will be generated.", + "type": "string" + }, + "displayName": { + "title": "Displayname", + "description": "An optional user-specified display name or label for this labware.", + "type": "string" + } + }, + "required": ["location", "loadName", "namespace", "version"] + }, + "LoadLabwareCreate": { + "title": "LoadLabwareCreate", + "description": "Load labware command creation request.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "loadLabware", + "enum": ["loadLabware"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoadLabwareParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "ReloadLabwareParams": { + "title": "ReloadLabwareParams", + "description": "Payload required to load a labware into a slot.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "The already-loaded labware instance to update.", + "type": "string" + } + }, + "required": ["labwareId"] + }, + "ReloadLabwareCreate": { + "title": "ReloadLabwareCreate", + "description": "Reload labware command creation request.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "reloadLabware", + "enum": ["reloadLabware"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReloadLabwareParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "LoadLiquidParams": { + "title": "LoadLiquidParams", + "description": "Payload required to load a liquid into a well.", + "type": "object", + "properties": { + "liquidId": { + "title": "Liquidid", + "description": "Unique identifier of the liquid to load.", + "type": "string" + }, + "labwareId": { + "title": "Labwareid", + "description": "Unique identifier of labware to load liquid into.", + "type": "string" + }, + "volumeByWell": { + "title": "Volumebywell", + "description": "Volume of liquid, in \u00b5L, loaded into each well by name, in this labware.", + "type": "object", + "additionalProperties": { + "type": "number" + } + } + }, + "required": ["liquidId", "labwareId", "volumeByWell"] + }, + "LoadLiquidCreate": { + "title": "LoadLiquidCreate", + "description": "Load liquid command creation request.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "loadLiquid", + "enum": ["loadLiquid"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoadLiquidParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "ModuleModel": { + "title": "ModuleModel", + "description": "All available modules' models.", + "enum": [ + "temperatureModuleV1", + "temperatureModuleV2", + "magneticModuleV1", + "magneticModuleV2", + "thermocyclerModuleV1", + "thermocyclerModuleV2", + "heaterShakerModuleV1", + "magneticBlockV1", + "absorbanceReaderV1" + ], + "type": "string" + }, + "LoadModuleParams": { + "title": "LoadModuleParams", + "description": "Payload required to load a module.", + "type": "object", + "properties": { + "model": { + "description": "The model name of the module to load.\n\nProtocol Engine will look for a connected module that either exactly matches this one, or is compatible.\n\n For example, if you request a `temperatureModuleV1` here, Protocol Engine might load a `temperatureModuleV1` or a `temperatureModuleV2`.\n\n The model that it finds connected will be available through `result.model`.", + "allOf": [ + { + "$ref": "#/definitions/ModuleModel" + } + ] + }, + "location": { + "title": "Location", + "description": "The location into which this module should be loaded.\n\nFor the Thermocycler Module, which occupies multiple deck slots, this should be the front-most occupied slot (normally slot 7).", + "allOf": [ + { + "$ref": "#/definitions/DeckSlotLocation" + } + ] + }, + "moduleId": { + "title": "Moduleid", + "description": "An optional ID to assign to this module. If None, an ID will be generated.", + "type": "string" + } + }, + "required": ["model", "location"] + }, + "LoadModuleCreate": { + "title": "LoadModuleCreate", + "description": "The model for a creation request for a load module command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "loadModule", + "enum": ["loadModule"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoadModuleParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "PipetteNameType": { + "title": "PipetteNameType", + "description": "Pipette load name values.", + "enum": [ + "p10_single", + "p10_multi", + "p20_single_gen2", + "p20_multi_gen2", + "p50_single", + "p50_multi", + "p50_single_flex", + "p50_multi_flex", + "p300_single", + "p300_multi", + "p300_single_gen2", + "p300_multi_gen2", + "p1000_single", + "p1000_single_gen2", + "p1000_single_flex", + "p1000_multi_flex", + "p1000_96" + ], + "type": "string" + }, + "LoadPipetteParams": { + "title": "LoadPipetteParams", + "description": "Payload needed to load a pipette on to a mount.", + "type": "object", + "properties": { + "pipetteName": { + "description": "The load name of the pipette to be required.", + "allOf": [ + { + "$ref": "#/definitions/PipetteNameType" + } + ] + }, + "mount": { + "description": "The mount the pipette should be present on.", + "allOf": [ + { + "$ref": "#/definitions/MountType" + } + ] + }, + "pipetteId": { + "title": "Pipetteid", + "description": "An optional ID to assign to this pipette. If None, an ID will be generated.", + "type": "string" + }, + "tipOverlapNotAfterVersion": { + "title": "Tipoverlapnotafterversion", + "description": "A version of tip overlap data to not exceed. The highest-versioned tip overlap data that does not exceed this version will be used. Versions are expressed as vN where N is an integer, counting up from v0. If None, the current highest version will be used.", + "type": "string" + }, + "liquidPresenceDetection": { + "title": "Liquidpresencedetection", + "description": "Enable liquid presence detection for this pipette. Defaults to False.", + "type": "boolean" + } + }, + "required": ["pipetteName", "mount"] + }, + "LoadPipetteCreate": { + "title": "LoadPipetteCreate", + "description": "Load pipette command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "loadPipette", + "enum": ["loadPipette"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/LoadPipetteParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "LabwareMovementStrategy": { + "title": "LabwareMovementStrategy", + "description": "Strategy to use for labware movement.", + "enum": ["usingGripper", "manualMoveWithPause", "manualMoveWithoutPause"], + "type": "string" + }, + "LabwareOffsetVector": { + "title": "LabwareOffsetVector", + "description": "Offset, in deck coordinates from nominal to actual position.", + "type": "object", + "properties": { + "x": { + "title": "X", + "type": "number" + }, + "y": { + "title": "Y", + "type": "number" + }, + "z": { + "title": "Z", + "type": "number" + } + }, + "required": ["x", "y", "z"] + }, + "MoveLabwareParams": { + "title": "MoveLabwareParams", + "description": "Input parameters for a ``moveLabware`` command.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "The ID of the labware to move.", + "type": "string" + }, + "newLocation": { + "title": "Newlocation", + "description": "Where to move the labware.", + "anyOf": [ + { + "$ref": "#/definitions/DeckSlotLocation" + }, + { + "$ref": "#/definitions/ModuleLocation" + }, + { + "$ref": "#/definitions/OnLabwareLocation" + }, + { + "enum": ["offDeck"], + "type": "string" + }, + { + "$ref": "#/definitions/AddressableAreaLocation" + } + ] + }, + "strategy": { + "description": "Whether to use the gripper to perform the labware movement or to perform a manual movement with an option to pause.", + "allOf": [ + { + "$ref": "#/definitions/LabwareMovementStrategy" + } + ] + }, + "pickUpOffset": { + "title": "Pickupoffset", + "description": "Offset to use when picking up labware. Experimental param, subject to change", + "allOf": [ + { + "$ref": "#/definitions/LabwareOffsetVector" + } + ] + }, + "dropOffset": { + "title": "Dropoffset", + "description": "Offset to use when dropping off labware. Experimental param, subject to change", + "allOf": [ + { + "$ref": "#/definitions/LabwareOffsetVector" + } + ] + } + }, + "required": ["labwareId", "newLocation", "strategy"] + }, + "MoveLabwareCreate": { + "title": "MoveLabwareCreate", + "description": "A request to create a ``moveLabware`` command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "moveLabware", + "enum": ["moveLabware"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveLabwareParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MovementAxis": { + "title": "MovementAxis", + "description": "Axis on which to issue a relative movement.", + "enum": ["x", "y", "z"], + "type": "string" + }, + "MoveRelativeParams": { + "title": "MoveRelativeParams", + "description": "Payload required for a MoveRelative command.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Pipette to move.", + "type": "string" + }, + "axis": { + "description": "Axis along which to move.", + "allOf": [ + { + "$ref": "#/definitions/MovementAxis" + } + ] + }, + "distance": { + "title": "Distance", + "description": "Distance to move in millimeters. A positive number will move towards the right (x), back (y), top (z) of the deck.", + "type": "number" + } + }, + "required": ["pipetteId", "axis", "distance"] + }, + "MoveRelativeCreate": { + "title": "MoveRelativeCreate", + "description": "Data to create a MoveRelative command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "moveRelative", + "enum": ["moveRelative"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveRelativeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeckPoint": { + "title": "DeckPoint", + "description": "Coordinates of a point in deck space.", + "type": "object", + "properties": { + "x": { + "title": "X", + "type": "number" + }, + "y": { + "title": "Y", + "type": "number" + }, + "z": { + "title": "Z", + "type": "number" + } + }, + "required": ["x", "y", "z"] + }, + "MoveToCoordinatesParams": { + "title": "MoveToCoordinatesParams", + "description": "Payload required to move a pipette to coordinates.", + "type": "object", + "properties": { + "minimumZHeight": { + "title": "Minimumzheight", + "description": "Optional minimal Z margin in mm. If this is larger than the API's default safe Z margin, it will make the arc higher. If it's smaller, it will have no effect.", + "type": "number" + }, + "forceDirect": { + "title": "Forcedirect", + "description": "If true, moving from one labware/well to another will not arc to the default safe z, but instead will move directly to the specified location. This will also force the `minimumZHeight` param to be ignored. A 'direct' movement is in X/Y/Z simultaneously.", + "default": false, + "type": "boolean" + }, + "speed": { + "title": "Speed", + "description": "Override the travel speed in mm/s. This controls the straight linear speed of motion.", + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "coordinates": { + "title": "Coordinates", + "description": "X, Y and Z coordinates in mm from deck's origin location (left-front-bottom corner of work space)", + "allOf": [ + { + "$ref": "#/definitions/DeckPoint" + } + ] + } + }, + "required": ["pipetteId", "coordinates"] + }, + "MoveToCoordinatesCreate": { + "title": "MoveToCoordinatesCreate", + "description": "Move to coordinates command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "moveToCoordinates", + "enum": ["moveToCoordinates"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveToCoordinatesParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MoveToWellParams": { + "title": "MoveToWellParams", + "description": "Payload required to move a pipette to a specific well.", + "type": "object", + "properties": { + "minimumZHeight": { + "title": "Minimumzheight", + "description": "Optional minimal Z margin in mm. If this is larger than the API's default safe Z margin, it will make the arc higher. If it's smaller, it will have no effect.", + "type": "number" + }, + "forceDirect": { + "title": "Forcedirect", + "description": "If true, moving from one labware/well to another will not arc to the default safe z, but instead will move directly to the specified location. This will also force the `minimumZHeight` param to be ignored. A 'direct' movement is in X/Y/Z simultaneously.", + "default": false, + "type": "boolean" + }, + "speed": { + "title": "Speed", + "description": "Override the travel speed in mm/s. This controls the straight linear speed of motion.", + "type": "number" + }, + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/WellLocation" + } + ] + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["labwareId", "wellName", "pipetteId"] + }, + "MoveToWellCreate": { + "title": "MoveToWellCreate", + "description": "Move to well command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "moveToWell", + "enum": ["moveToWell"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveToWellParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "AddressableOffsetVector": { + "title": "AddressableOffsetVector", + "description": "Offset, in deck coordinates, from nominal to actual position of an addressable area.", + "type": "object", + "properties": { + "x": { + "title": "X", + "type": "number" + }, + "y": { + "title": "Y", + "type": "number" + }, + "z": { + "title": "Z", + "type": "number" + } + }, + "required": ["x", "y", "z"] + }, + "MoveToAddressableAreaParams": { + "title": "MoveToAddressableAreaParams", + "description": "Payload required to move a pipette to a specific addressable area.\n\nAn *addressable area* is a space in the robot that may or may not be usable depending on how\nthe robot's deck is configured. For example, if a Flex is configured with a waste chute, it will\nhave additional addressable areas representing the opening of the waste chute, where tips and\nlabware can be dropped.\n\nThis moves the pipette so all of its nozzles are centered over the addressable area.\nIf the pipette is currently configured with a partial tip layout, this centering is over all\nthe pipette's physical nozzles, not just the nozzles that are active.\n\nThe z-position will be chosen to put the bottom of the tips---or the bottom of the nozzles,\nif there are no tips---level with the top of the addressable area.\n\nWhen this command is executed, Protocol Engine will make sure the robot's deck is configured\nsuch that the requested addressable area actually exists. For example, if you request\nthe addressable area B4, it will make sure the robot is set up with a B3/B4 staging area slot.\nIf that's not the case, the command will fail.", + "type": "object", + "properties": { + "minimumZHeight": { + "title": "Minimumzheight", + "description": "Optional minimal Z margin in mm. If this is larger than the API's default safe Z margin, it will make the arc higher. If it's smaller, it will have no effect.", + "type": "number" + }, + "forceDirect": { + "title": "Forcedirect", + "description": "If true, moving from one labware/well to another will not arc to the default safe z, but instead will move directly to the specified location. This will also force the `minimumZHeight` param to be ignored. A 'direct' movement is in X/Y/Z simultaneously.", + "default": false, + "type": "boolean" + }, + "speed": { + "title": "Speed", + "description": "Override the travel speed in mm/s. This controls the straight linear speed of motion.", + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "addressableAreaName": { + "title": "Addressableareaname", + "description": "The name of the addressable area that you want to use. Valid values are the `id`s of `addressableArea`s in the [deck definition](https://github.com/Opentrons/opentrons/tree/edge/shared-data/deck).", + "type": "string" + }, + "offset": { + "title": "Offset", + "description": "Relative offset of addressable area to move pipette's critical point.", + "default": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "allOf": [ + { + "$ref": "#/definitions/AddressableOffsetVector" + } + ] + }, + "stayAtHighestPossibleZ": { + "title": "Stayathighestpossiblez", + "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.", + "default": false, + "type": "boolean" + } + }, + "required": ["pipetteId", "addressableAreaName"] + }, + "MoveToAddressableAreaCreate": { + "title": "MoveToAddressableAreaCreate", + "description": "Move to addressable area command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "moveToAddressableArea", + "enum": ["moveToAddressableArea"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveToAddressableAreaParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MoveToAddressableAreaForDropTipParams": { + "title": "MoveToAddressableAreaForDropTipParams", + "description": "Payload required to move a pipette to a specific addressable area.\n\nAn *addressable area* is a space in the robot that may or may not be usable depending on how\nthe robot's deck is configured. For example, if a Flex is configured with a waste chute, it will\nhave additional addressable areas representing the opening of the waste chute, where tips and\nlabware can be dropped.\n\nThis moves the pipette so all of its nozzles are centered over the addressable area.\nIf the pipette is currently configured with a partial tip layout, this centering is over all\nthe pipette's physical nozzles, not just the nozzles that are active.\n\nThe z-position will be chosen to put the bottom of the tips---or the bottom of the nozzles,\nif there are no tips---level with the top of the addressable area.\n\nWhen this command is executed, Protocol Engine will make sure the robot's deck is configured\nsuch that the requested addressable area actually exists. For example, if you request\nthe addressable area B4, it will make sure the robot is set up with a B3/B4 staging area slot.\nIf that's not the case, the command will fail.", + "type": "object", + "properties": { + "minimumZHeight": { + "title": "Minimumzheight", + "description": "Optional minimal Z margin in mm. If this is larger than the API's default safe Z margin, it will make the arc higher. If it's smaller, it will have no effect.", + "type": "number" + }, + "forceDirect": { + "title": "Forcedirect", + "description": "If true, moving from one labware/well to another will not arc to the default safe z, but instead will move directly to the specified location. This will also force the `minimumZHeight` param to be ignored. A 'direct' movement is in X/Y/Z simultaneously.", + "default": false, + "type": "boolean" + }, + "speed": { + "title": "Speed", + "description": "Override the travel speed in mm/s. This controls the straight linear speed of motion.", + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "addressableAreaName": { + "title": "Addressableareaname", + "description": "The name of the addressable area that you want to use. Valid values are the `id`s of `addressableArea`s in the [deck definition](https://github.com/Opentrons/opentrons/tree/edge/shared-data/deck).", + "type": "string" + }, + "offset": { + "title": "Offset", + "description": "Relative offset of addressable area to move pipette's critical point.", + "default": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "allOf": [ + { + "$ref": "#/definitions/AddressableOffsetVector" + } + ] + }, + "alternateDropLocation": { + "title": "Alternatedroplocation", + "description": "Whether to alternate location where tip is dropped within the addressable area. If True, this command will ignore the offset provided and alternate between dropping tips at two predetermined locations inside the specified labware well. If False, the tip will be dropped at the top center of the area.", + "default": false, + "type": "boolean" + }, + "ignoreTipConfiguration": { + "title": "Ignoretipconfiguration", + "description": "Whether to utilize the critical point of the tip configuraiton when moving to an addressable area. If True, this command will ignore the tip configuration and use the center of the entire instrument as the critical point for movement. If False, this command will use the critical point provided by the current tip configuration.", + "default": true, + "type": "boolean" + } + }, + "required": ["pipetteId", "addressableAreaName"] + }, + "MoveToAddressableAreaForDropTipCreate": { + "title": "MoveToAddressableAreaForDropTipCreate", + "description": "Move to addressable area for drop tip command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "moveToAddressableAreaForDropTip", + "enum": ["moveToAddressableAreaForDropTip"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveToAddressableAreaForDropTipParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "PrepareToAspirateParams": { + "title": "PrepareToAspirateParams", + "description": "Parameters required to prepare a specific pipette for aspiration.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["pipetteId"] + }, + "PrepareToAspirateCreate": { + "title": "PrepareToAspirateCreate", + "description": "Prepare for aspirate command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "prepareToAspirate", + "enum": ["prepareToAspirate"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/PrepareToAspirateParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "WaitForResumeParams": { + "title": "WaitForResumeParams", + "description": "Payload required to pause the protocol.", + "type": "object", + "properties": { + "message": { + "title": "Message", + "description": "A user-facing message associated with the pause", + "type": "string" + } + } + }, + "WaitForResumeCreate": { + "title": "WaitForResumeCreate", + "description": "Wait for resume command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "waitForResume", + "enum": ["waitForResume", "pause"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/WaitForResumeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "WaitForDurationParams": { + "title": "WaitForDurationParams", + "description": "Payload required to pause the protocol.", + "type": "object", + "properties": { + "seconds": { + "title": "Seconds", + "description": "Duration, in seconds, to wait for.", + "type": "number" + }, + "message": { + "title": "Message", + "description": "A user-facing message associated with the pause", + "type": "string" + } + }, + "required": ["seconds"] + }, + "WaitForDurationCreate": { + "title": "WaitForDurationCreate", + "description": "Wait for duration command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "waitForDuration", + "enum": ["waitForDuration"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/WaitForDurationParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "PickUpTipWellOrigin": { + "title": "PickUpTipWellOrigin", + "description": "The origin of a PickUpTipWellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well", + "enum": ["top", "bottom", "center"], + "type": "string" + }, + "PickUpTipWellLocation": { + "title": "PickUpTipWellLocation", + "description": "A relative location in reference to a well's location.\n\nTo be used for picking up tips.", + "type": "object", + "properties": { + "origin": { + "default": "top", + "allOf": [ + { + "$ref": "#/definitions/PickUpTipWellOrigin" + } + ] + }, + "offset": { + "$ref": "#/definitions/WellOffset" + } + } + }, + "PickUpTipParams": { + "title": "PickUpTipParams", + "description": "Payload needed to move a pipette to a specific well.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to pick up the tip.", + "allOf": [ + { + "$ref": "#/definitions/PickUpTipWellLocation" + } + ] + } + }, + "required": ["pipetteId", "labwareId", "wellName"] + }, + "PickUpTipCreate": { + "title": "PickUpTipCreate", + "description": "Pick up tip command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "pickUpTip", + "enum": ["pickUpTip"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/PickUpTipParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "SavePositionParams": { + "title": "SavePositionParams", + "description": "Payload needed to save a pipette's current position.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Unique identifier of the pipette in question.", + "type": "string" + }, + "positionId": { + "title": "Positionid", + "description": "An optional ID to assign to this command instance. Auto-assigned if not defined.", + "type": "string" + }, + "failOnNotHomed": { + "title": "Failonnothomed", + "default": true, + "descrption": "Require all axes to be homed before saving position.", + "type": "boolean" + } + }, + "required": ["pipetteId"] + }, + "SavePositionCreate": { + "title": "SavePositionCreate", + "description": "Save position command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "savePosition", + "enum": ["savePosition"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/SavePositionParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "SetRailLightsParams": { + "title": "SetRailLightsParams", + "description": "Payload required to set the rail lights on or off.", + "type": "object", + "properties": { + "on": { + "title": "On", + "description": "The field that determines if the light is turned off or on.", + "type": "boolean" + } + }, + "required": ["on"] + }, + "SetRailLightsCreate": { + "title": "SetRailLightsCreate", + "description": "setRailLights command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "setRailLights", + "enum": ["setRailLights"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetRailLightsParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "TouchTipParams": { + "title": "TouchTipParams", + "description": "Payload needed to touch a pipette tip the sides of a specific well.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/WellLocation" + } + ] + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "radius": { + "title": "Radius", + "description": "The proportion of the target well's radius the pipette tip will move towards.", + "default": 1.0, + "type": "number" + }, + "speed": { + "title": "Speed", + "description": "Override the travel speed in mm/s. This controls the straight linear speed of motion.", + "type": "number" + } + }, + "required": ["labwareId", "wellName", "pipetteId"] + }, + "TouchTipCreate": { + "title": "TouchTipCreate", + "description": "Touch tip command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "touchTip", + "enum": ["touchTip"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/TouchTipParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "StatusBarAnimation": { + "title": "StatusBarAnimation", + "description": "Status Bar animation options.", + "enum": ["idle", "confirm", "updating", "disco", "off"] + }, + "SetStatusBarParams": { + "title": "SetStatusBarParams", + "description": "Payload required to set the status bar to run an animation.", + "type": "object", + "properties": { + "animation": { + "description": "The animation that should be executed on the status bar.", + "allOf": [ + { + "$ref": "#/definitions/StatusBarAnimation" + } + ] + } + }, + "required": ["animation"] + }, + "SetStatusBarCreate": { + "title": "SetStatusBarCreate", + "description": "setStatusBar command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "setStatusBar", + "enum": ["setStatusBar"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetStatusBarParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "TipPresenceStatus": { + "title": "TipPresenceStatus", + "description": "Tip presence status reported by a pipette.", + "enum": ["present", "absent", "unknown"], + "type": "string" + }, + "InstrumentSensorId": { + "title": "InstrumentSensorId", + "description": "Primary and secondary sensor ids.", + "enum": ["primary", "secondary", "both"], + "type": "string" + }, + "VerifyTipPresenceParams": { + "title": "VerifyTipPresenceParams", + "description": "Payload required for a VerifyTipPresence command.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "expectedState": { + "description": "The expected tip presence status on the pipette.", + "allOf": [ + { + "$ref": "#/definitions/TipPresenceStatus" + } + ] + }, + "followSingularSensor": { + "description": "The sensor id to follow if the other can be ignored.", + "allOf": [ + { + "$ref": "#/definitions/InstrumentSensorId" + } + ] + } + }, + "required": ["pipetteId", "expectedState"] + }, + "VerifyTipPresenceCreate": { + "title": "VerifyTipPresenceCreate", + "description": "VerifyTipPresence command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "verifyTipPresence", + "enum": ["verifyTipPresence"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/VerifyTipPresenceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "GetTipPresenceParams": { + "title": "GetTipPresenceParams", + "description": "Payload required for a GetTipPresence command.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["pipetteId"] + }, + "GetTipPresenceCreate": { + "title": "GetTipPresenceCreate", + "description": "GetTipPresence command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "getTipPresence", + "enum": ["getTipPresence"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/GetTipPresenceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "LiquidProbeParams": { + "title": "LiquidProbeParams", + "description": "Parameters required for a `liquidProbe` command.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/WellLocation" + } + ] + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["labwareId", "wellName", "pipetteId"] + }, + "LiquidProbeCreate": { + "title": "LiquidProbeCreate", + "description": "The request model for a `liquidProbe` command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "liquidProbe", + "enum": ["liquidProbe"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/LiquidProbeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "TryLiquidProbeParams": { + "title": "TryLiquidProbeParams", + "description": "Parameters required for a `tryLiquidProbe` command.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "Identifier of labware to use.", + "type": "string" + }, + "wellName": { + "title": "Wellname", + "description": "Name of well to use in labware.", + "type": "string" + }, + "wellLocation": { + "title": "Welllocation", + "description": "Relative well location at which to perform the operation", + "allOf": [ + { + "$ref": "#/definitions/WellLocation" + } + ] + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["labwareId", "wellName", "pipetteId"] + }, + "TryLiquidProbeCreate": { + "title": "TryLiquidProbeCreate", + "description": "The request model for a `tryLiquidProbe` command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "tryLiquidProbe", + "enum": ["tryLiquidProbe"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/TryLiquidProbeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureParams": { + "title": "WaitForTemperatureParams", + "description": "Input parameters to wait for a Heater-Shaker's target temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + }, + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C. If not specified, will default to the module's target temperature. Specifying a celsius parameter other than the target temperature could lead to unpredictable behavior and hence is not recommended for use. This parameter can be removed in a future version without prior notice.", + "type": "number" + } + }, + "required": ["moduleId"] + }, + "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureCreate": { + "title": "WaitForTemperatureCreate", + "description": "A request to create a Heater-Shaker's wait for temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/waitForTemperature", + "enum": ["heaterShaker/waitForTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureParams": { + "title": "SetTargetTemperatureParams", + "description": "Input parameters to set a Heater-Shaker's target temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + }, + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C.", + "type": "number" + } + }, + "required": ["moduleId", "celsius"] + }, + "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureCreate": { + "title": "SetTargetTemperatureCreate", + "description": "A request to create a Heater-Shaker's set temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/setTargetTemperature", + "enum": ["heaterShaker/setTargetTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeactivateHeaterParams": { + "title": "DeactivateHeaterParams", + "description": "Input parameters to unset a Heater-Shaker's target temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "DeactivateHeaterCreate": { + "title": "DeactivateHeaterCreate", + "description": "A request to create a Heater-Shaker's deactivate heater command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/deactivateHeater", + "enum": ["heaterShaker/deactivateHeater"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeactivateHeaterParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "SetAndWaitForShakeSpeedParams": { + "title": "SetAndWaitForShakeSpeedParams", + "description": "Input parameters to set and wait for a shake speed for a Heater-Shaker Module.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + }, + "rpm": { + "title": "Rpm", + "description": "Target speed in rotations per minute.", + "type": "number" + } + }, + "required": ["moduleId", "rpm"] + }, + "SetAndWaitForShakeSpeedCreate": { + "title": "SetAndWaitForShakeSpeedCreate", + "description": "A request to create a Heater-Shaker's set and wait for shake speed command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/setAndWaitForShakeSpeed", + "enum": ["heaterShaker/setAndWaitForShakeSpeed"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetAndWaitForShakeSpeedParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeactivateShakerParams": { + "title": "DeactivateShakerParams", + "description": "Input parameters to deactivate shaker for a Heater-Shaker Module.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "DeactivateShakerCreate": { + "title": "DeactivateShakerCreate", + "description": "A request to create a Heater-Shaker's deactivate shaker command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/deactivateShaker", + "enum": ["heaterShaker/deactivateShaker"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeactivateShakerParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "OpenLabwareLatchParams": { + "title": "OpenLabwareLatchParams", + "description": "Input parameters to open a Heater-Shaker Module's labware latch.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "OpenLabwareLatchCreate": { + "title": "OpenLabwareLatchCreate", + "description": "A request to create a Heater-Shaker's open labware latch command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/openLabwareLatch", + "enum": ["heaterShaker/openLabwareLatch"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/OpenLabwareLatchParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "CloseLabwareLatchParams": { + "title": "CloseLabwareLatchParams", + "description": "Input parameters to close a Heater-Shaker Module's labware latch.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Heater-Shaker Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "CloseLabwareLatchCreate": { + "title": "CloseLabwareLatchCreate", + "description": "A request to create a Heater-Shaker's close latch command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "heaterShaker/closeLabwareLatch", + "enum": ["heaterShaker/closeLabwareLatch"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/CloseLabwareLatchParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DisengageParams": { + "title": "DisengageParams", + "description": "Input data to disengage a Magnetic Module's magnets.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "The ID of the Magnetic Module whose magnets you want to disengage, from a prior `loadModule` command.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "DisengageCreate": { + "title": "DisengageCreate", + "description": "A request to create a Magnetic Module disengage command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "magneticModule/disengage", + "enum": ["magneticModule/disengage"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DisengageParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "EngageParams": { + "title": "EngageParams", + "description": "Input data to engage a Magnetic Module.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "The ID of the Magnetic Module whose magnets you want to raise, from a prior `loadModule` command.", + "type": "string" + }, + "height": { + "title": "Height", + "description": "How high, in millimeters, to raise the magnets.\n\nZero means the tops of the magnets are level with the ledge that the labware rests on. This will be slightly above the magnets' minimum height, the hardware home position. Negative values are allowed, to put the magnets below the ledge.\n\nUnits are always true millimeters. This is unlike certain labware definitions, engage commands in the Python Protocol API, and engage commands in older versions of the JSON protocol schema. Take care to convert properly.", + "type": "number" + } + }, + "required": ["moduleId", "height"] + }, + "EngageCreate": { + "title": "EngageCreate", + "description": "A request to create a Magnetic Module engage command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "magneticModule/engage", + "enum": ["magneticModule/engage"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/EngageParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureParams": { + "title": "SetTargetTemperatureParams", + "description": "Input parameters to set a Temperature Module's target temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Temperature Module.", + "type": "string" + }, + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C.", + "type": "number" + } + }, + "required": ["moduleId", "celsius"] + }, + "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureCreate": { + "title": "SetTargetTemperatureCreate", + "description": "A request to create a Temperature Module's set temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "temperatureModule/setTargetTemperature", + "enum": ["temperatureModule/setTargetTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureParams": { + "title": "WaitForTemperatureParams", + "description": "Input parameters to wait for a Temperature Module's target temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Temperature Module.", + "type": "string" + }, + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C. If not specified, will default to the module's target temperature. Specifying a celsius parameter other than the target temperature could lead to unpredictable behavior and hence is not recommended for use. This parameter can be removed in a future version without prior notice.", + "type": "number" + } + }, + "required": ["moduleId"] + }, + "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureCreate": { + "title": "WaitForTemperatureCreate", + "description": "A request to create a Temperature Module's wait for temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "temperatureModule/waitForTemperature", + "enum": ["temperatureModule/waitForTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeactivateTemperatureParams": { + "title": "DeactivateTemperatureParams", + "description": "Input parameters to deactivate a Temperature Module.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Temperature Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "DeactivateTemperatureCreate": { + "title": "DeactivateTemperatureCreate", + "description": "A request to deactivate a Temperature Module.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "temperatureModule/deactivate", + "enum": ["temperatureModule/deactivate"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeactivateTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "SetTargetBlockTemperatureParams": { + "title": "SetTargetBlockTemperatureParams", + "description": "Input parameters to set a Thermocycler's target block temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler Module.", + "type": "string" + }, + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C.", + "type": "number" + }, + "blockMaxVolumeUl": { + "title": "Blockmaxvolumeul", + "description": "Amount of liquid in uL of the most-full well in labware loaded onto the thermocycler.", + "type": "number" + }, + "holdTimeSeconds": { + "title": "Holdtimeseconds", + "description": "Amount of time, in seconds, to hold the temperature for. If specified, a waitForBlockTemperature command will block until the given hold time has elapsed.", + "type": "number" + } + }, + "required": ["moduleId", "celsius"] + }, + "SetTargetBlockTemperatureCreate": { + "title": "SetTargetBlockTemperatureCreate", + "description": "A request to create a Thermocycler's set block temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/setTargetBlockTemperature", + "enum": ["thermocycler/setTargetBlockTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetTargetBlockTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "WaitForBlockTemperatureParams": { + "title": "WaitForBlockTemperatureParams", + "description": "Input parameters to wait for Thermocycler's target block temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "WaitForBlockTemperatureCreate": { + "title": "WaitForBlockTemperatureCreate", + "description": "A request to create Thermocycler's wait for block temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/waitForBlockTemperature", + "enum": ["thermocycler/waitForBlockTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/WaitForBlockTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "SetTargetLidTemperatureParams": { + "title": "SetTargetLidTemperatureParams", + "description": "Input parameters to set a Thermocycler's target lid temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler Module.", + "type": "string" + }, + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C.", + "type": "number" + } + }, + "required": ["moduleId", "celsius"] + }, + "SetTargetLidTemperatureCreate": { + "title": "SetTargetLidTemperatureCreate", + "description": "A request to create a Thermocycler's set lid temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/setTargetLidTemperature", + "enum": ["thermocycler/setTargetLidTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/SetTargetLidTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "WaitForLidTemperatureParams": { + "title": "WaitForLidTemperatureParams", + "description": "Input parameters to wait for Thermocycler's lid temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler Module.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "WaitForLidTemperatureCreate": { + "title": "WaitForLidTemperatureCreate", + "description": "A request to create Thermocycler's wait for lid temperature command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/waitForLidTemperature", + "enum": ["thermocycler/waitForLidTemperature"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/WaitForLidTemperatureParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeactivateBlockParams": { + "title": "DeactivateBlockParams", + "description": "Input parameters to unset a Thermocycler's target block temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "DeactivateBlockCreate": { + "title": "DeactivateBlockCreate", + "description": "A request to create a Thermocycler's deactivate block command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/deactivateBlock", + "enum": ["thermocycler/deactivateBlock"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeactivateBlockParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "DeactivateLidParams": { + "title": "DeactivateLidParams", + "description": "Input parameters to unset a Thermocycler's target lid temperature.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "DeactivateLidCreate": { + "title": "DeactivateLidCreate", + "description": "A request to create a Thermocycler's deactivate lid command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/deactivateLid", + "enum": ["thermocycler/deactivateLid"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/DeactivateLidParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidParams": { + "title": "OpenLidParams", + "description": "Input parameters to open a Thermocycler's lid.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidCreate": { + "title": "OpenLidCreate", + "description": "A request to open a Thermocycler's lid.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/openLid", + "enum": ["thermocycler/openLid"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidParams": { + "title": "CloseLidParams", + "description": "Input parameters to close a Thermocycler's lid.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidCreate": { + "title": "CloseLidCreate", + "description": "A request to close a Thermocycler's lid.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/closeLid", + "enum": ["thermocycler/closeLid"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "RunProfileStepParams": { + "title": "RunProfileStepParams", + "description": "Input parameters for an individual Thermocycler profile step.", + "type": "object", + "properties": { + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C.", + "type": "number" + }, + "holdSeconds": { + "title": "Holdseconds", + "description": "Time to hold target temperature at in seconds.", + "type": "number" + } + }, + "required": ["celsius", "holdSeconds"] + }, + "RunProfileParams": { + "title": "RunProfileParams", + "description": "Input parameters to run a Thermocycler profile.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler.", + "type": "string" + }, + "profile": { + "title": "Profile", + "description": "Array of profile steps with target temperature and temperature hold time.", + "type": "array", + "items": { + "$ref": "#/definitions/RunProfileStepParams" + } + }, + "blockMaxVolumeUl": { + "title": "Blockmaxvolumeul", + "description": "Amount of liquid in uL of the most-full well in labware loaded onto the thermocycler.", + "type": "number" + } + }, + "required": ["moduleId", "profile"] + }, + "RunProfileCreate": { + "title": "RunProfileCreate", + "description": "A request to execute a Thermocycler profile run.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/runProfile", + "enum": ["thermocycler/runProfile"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/RunProfileParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "ProfileStep": { + "title": "ProfileStep", + "description": "An individual step in a Thermocycler extended profile.", + "type": "object", + "properties": { + "celsius": { + "title": "Celsius", + "description": "Target temperature in \u00b0C.", + "type": "number" + }, + "holdSeconds": { + "title": "Holdseconds", + "description": "Time to hold target temperature in seconds.", + "type": "number" + } + }, + "required": ["celsius", "holdSeconds"] + }, + "ProfileCycle": { + "title": "ProfileCycle", + "description": "An individual cycle in a Thermocycler extended profile.", + "type": "object", + "properties": { + "steps": { + "title": "Steps", + "description": "Steps to repeat.", + "type": "array", + "items": { + "$ref": "#/definitions/ProfileStep" + } + }, + "repetitions": { + "title": "Repetitions", + "description": "Number of times to repeat the steps.", + "type": "integer" + } + }, + "required": ["steps", "repetitions"] + }, + "RunExtendedProfileParams": { + "title": "RunExtendedProfileParams", + "description": "Input parameters for an individual Thermocycler profile step.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Thermocycler.", + "type": "string" + }, + "profileElements": { + "title": "Profileelements", + "description": "Elements of the profile. Each can be either a step or a cycle.", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/ProfileStep" + }, + { + "$ref": "#/definitions/ProfileCycle" + } + ] + } + }, + "blockMaxVolumeUl": { + "title": "Blockmaxvolumeul", + "description": "Amount of liquid in uL of the most-full well in labware loaded onto the thermocycler.", + "type": "number" + } + }, + "required": ["moduleId", "profileElements"] + }, + "RunExtendedProfileCreate": { + "title": "RunExtendedProfileCreate", + "description": "A request to execute a Thermocycler profile run.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "thermocycler/runExtendedProfile", + "enum": ["thermocycler/runExtendedProfile"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/RunExtendedProfileParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidParams": { + "title": "CloseLidParams", + "description": "Input parameters to close the lid on an absorbance reading.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the absorbance reader.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidCreate": { + "title": "CloseLidCreate", + "description": "A request to execute an Absorbance Reader close lid command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "absorbanceReader/closeLid", + "enum": ["absorbanceReader/closeLid"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidParams": { + "title": "OpenLidParams", + "description": "Input parameters to open the lid on an absorbance reading.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the absorbance reader.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidCreate": { + "title": "OpenLidCreate", + "description": "A request to execute an Absorbance Reader open lid command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "absorbanceReader/openLid", + "enum": ["absorbanceReader/openLid"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "InitializeParams": { + "title": "InitializeParams", + "description": "Input parameters to initialize an absorbance reading.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the absorbance reader.", + "type": "string" + }, + "measureMode": { + "title": "Measuremode", + "description": "Initialize single or multi measurement mode.", + "enum": ["single", "multi"], + "type": "string" + }, + "sampleWavelengths": { + "title": "Samplewavelengths", + "description": "Sample wavelengths in nm.", + "type": "array", + "items": { + "type": "integer" + } + }, + "referenceWavelength": { + "title": "Referencewavelength", + "description": "Optional reference wavelength in nm.", + "type": "integer" + } + }, + "required": ["moduleId", "measureMode", "sampleWavelengths"] + }, + "InitializeCreate": { + "title": "InitializeCreate", + "description": "A request to execute an Absorbance Reader measurement.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "absorbanceReader/initialize", + "enum": ["absorbanceReader/initialize"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/InitializeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "ReadAbsorbanceParams": { + "title": "ReadAbsorbanceParams", + "description": "Input parameters for an absorbance reading.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "Unique ID of the Absorbance Reader.", + "type": "string" + }, + "fileName": { + "title": "Filename", + "description": "Optional file name to use when storing the results of a measurement.", + "type": "string" + } + }, + "required": ["moduleId"] + }, + "ReadAbsorbanceCreate": { + "title": "ReadAbsorbanceCreate", + "description": "A request to execute an Absorbance Reader measurement.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "absorbanceReader/read", + "enum": ["absorbanceReader/read"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/ReadAbsorbanceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "CalibrateGripperParamsJaw": { + "title": "CalibrateGripperParamsJaw", + "description": "An enumeration.", + "enum": ["front", "rear"] + }, + "Vec3f": { + "title": "Vec3f", + "description": "A 3D vector of floats.", + "type": "object", + "properties": { + "x": { + "title": "X", + "type": "number" + }, + "y": { + "title": "Y", + "type": "number" + }, + "z": { + "title": "Z", + "type": "number" + } + }, + "required": ["x", "y", "z"] + }, + "CalibrateGripperParams": { + "title": "CalibrateGripperParams", + "description": "Parameters for a `calibrateGripper` command.", + "type": "object", + "properties": { + "jaw": { + "description": "Which of the gripper's jaws to use to measure its offset. The robot will assume that a human operator has already attached the capacitive probe to the jaw and none is attached to the other jaw.", + "allOf": [ + { + "$ref": "#/definitions/CalibrateGripperParamsJaw" + } + ] + }, + "otherJawOffset": { + "title": "Otherjawoffset", + "description": "If an offset for the other probe is already found, then specifying it here will enable the CalibrateGripper command to complete the calibration process by calculating the total offset and saving it to disk. If this param is not specified then the command will only find and return the offset for the specified probe.", + "allOf": [ + { + "$ref": "#/definitions/Vec3f" + } + ] + } + }, + "required": ["jaw"] + }, + "CalibrateGripperCreate": { + "title": "CalibrateGripperCreate", + "description": "A request to create a `calibrateGripper` command.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "calibration/calibrateGripper", + "enum": ["calibration/calibrateGripper"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/CalibrateGripperParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "CalibratePipetteParams": { + "title": "CalibratePipetteParams", + "description": "Payload required to calibrate-pipette.", + "type": "object", + "properties": { + "mount": { + "description": "Instrument mount to calibrate.", + "allOf": [ + { + "$ref": "#/definitions/MountType" + } + ] + } + }, + "required": ["mount"] + }, + "CalibratePipetteCreate": { + "title": "CalibratePipetteCreate", + "description": "Create calibrate-pipette command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "calibration/calibratePipette", + "enum": ["calibration/calibratePipette"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/CalibratePipetteParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "CalibrateModuleParams": { + "title": "CalibrateModuleParams", + "description": "Payload required to calibrate-module.", + "type": "object", + "properties": { + "moduleId": { + "title": "Moduleid", + "description": "The unique id of module to calibrate.", + "type": "string" + }, + "labwareId": { + "title": "Labwareid", + "description": "The unique id of module calibration adapter labware.", + "type": "string" + }, + "mount": { + "description": "The instrument mount used to calibrate the module.", + "allOf": [ + { + "$ref": "#/definitions/MountType" + } + ] + } + }, + "required": ["moduleId", "labwareId", "mount"] + }, + "CalibrateModuleCreate": { + "title": "CalibrateModuleCreate", + "description": "Create calibrate-module command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "calibration/calibrateModule", + "enum": ["calibration/calibrateModule"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/CalibrateModuleParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MaintenancePosition": { + "title": "MaintenancePosition", + "description": "Maintenance position options.", + "enum": ["attachPlate", "attachInstrument"] + }, + "MoveToMaintenancePositionParams": { + "title": "MoveToMaintenancePositionParams", + "description": "Calibration set up position command parameters.", + "type": "object", + "properties": { + "mount": { + "description": "Gantry mount to move maintenance position.", + "allOf": [ + { + "$ref": "#/definitions/MountType" + } + ] + }, + "maintenancePosition": { + "description": "The position the gantry mount needs to move to.", + "default": "attachInstrument", + "allOf": [ + { + "$ref": "#/definitions/MaintenancePosition" + } + ] + } + }, + "required": ["mount"] + }, + "MoveToMaintenancePositionCreate": { + "title": "MoveToMaintenancePositionCreate", + "description": "Calibration set up position command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "calibration/moveToMaintenancePosition", + "enum": ["calibration/moveToMaintenancePosition"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveToMaintenancePositionParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "UnsafeBlowOutInPlaceParams": { + "title": "UnsafeBlowOutInPlaceParams", + "description": "Payload required to blow-out in place while position is unknown.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["flowRate", "pipetteId"] + }, + "UnsafeBlowOutInPlaceCreate": { + "title": "UnsafeBlowOutInPlaceCreate", + "description": "UnsafeBlowOutInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/blowOutInPlace", + "enum": ["unsafe/blowOutInPlace"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UnsafeBlowOutInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "UnsafeDropTipInPlaceParams": { + "title": "UnsafeDropTipInPlaceParams", + "description": "Payload required to drop a tip in place even if the plunger position is not known.", + "type": "object", + "properties": { + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + }, + "homeAfter": { + "title": "Homeafter", + "description": "Whether to home this pipette's plunger after dropping the tip. You should normally leave this unspecified to let the robot choose a safe default depending on its hardware.", + "type": "boolean" + } + }, + "required": ["pipetteId"] + }, + "UnsafeDropTipInPlaceCreate": { + "title": "UnsafeDropTipInPlaceCreate", + "description": "Drop tip in place command creation request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/dropTipInPlace", + "enum": ["unsafe/dropTipInPlace"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UnsafeDropTipInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "UpdatePositionEstimatorsParams": { + "title": "UpdatePositionEstimatorsParams", + "description": "Payload required for an UpdatePositionEstimators command.", + "type": "object", + "properties": { + "axes": { + "description": "The axes for which to update the position estimators.", + "type": "array", + "items": { + "$ref": "#/definitions/MotorAxis" + } + } + }, + "required": ["axes"] + }, + "UpdatePositionEstimatorsCreate": { + "title": "UpdatePositionEstimatorsCreate", + "description": "UpdatePositionEstimators command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/updatePositionEstimators", + "enum": ["unsafe/updatePositionEstimators"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UpdatePositionEstimatorsParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "UnsafeEngageAxesParams": { + "title": "UnsafeEngageAxesParams", + "description": "Payload required for an UnsafeEngageAxes command.", + "type": "object", + "properties": { + "axes": { + "description": "The axes for which to enable.", + "type": "array", + "items": { + "$ref": "#/definitions/MotorAxis" + } + } + }, + "required": ["axes"] + }, + "UnsafeEngageAxesCreate": { + "title": "UnsafeEngageAxesCreate", + "description": "UnsafeEngageAxes command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/engageAxes", + "enum": ["unsafe/engageAxes"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UnsafeEngageAxesParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "UnsafeUngripLabwareParams": { + "title": "UnsafeUngripLabwareParams", + "description": "Payload required for an UngripLabware command.", + "type": "object", + "properties": {} + }, + "UnsafeUngripLabwareCreate": { + "title": "UnsafeUngripLabwareCreate", + "description": "UnsafeEngageAxes command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/ungripLabware", + "enum": ["unsafe/ungripLabware"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UnsafeUngripLabwareParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "UnsafePlaceLabwareParams": { + "title": "UnsafePlaceLabwareParams", + "description": "Payload required for an UnsafePlaceLabware command.", + "type": "object", + "properties": { + "labwareId": { + "title": "Labwareid", + "description": "The id of the labware to place.", + "type": "string" + }, + "location": { + "title": "Location", + "description": "Where to place the labware.", + "anyOf": [ + { + "$ref": "#/definitions/DeckSlotLocation" + }, + { + "$ref": "#/definitions/ModuleLocation" + }, + { + "$ref": "#/definitions/OnLabwareLocation" + }, + { + "$ref": "#/definitions/AddressableAreaLocation" + } + ] + } + }, + "required": ["labwareId", "location"] + }, + "UnsafePlaceLabwareCreate": { + "title": "UnsafePlaceLabwareCreate", + "description": "UnsafePlaceLabware command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "unsafe/placeLabware", + "enum": ["unsafe/placeLabware"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/UnsafePlaceLabwareParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MoveAxesRelativeParams": { + "title": "MoveAxesRelativeParams", + "description": "Payload required to move axes relative to position.", + "type": "object", + "properties": { + "axis_map": { + "title": "Axis Map", + "description": "A dictionary mapping axes to relative movements in mm.", + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "speed": { + "title": "Speed", + "description": "The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + "type": "number" + } + }, + "required": ["axis_map"] + }, + "MoveAxesRelativeCreate": { + "title": "MoveAxesRelativeCreate", + "description": "MoveAxesRelative command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "robot/moveAxesRelative", + "enum": ["robot/moveAxesRelative"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveAxesRelativeParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MoveAxesToParams": { + "title": "MoveAxesToParams", + "description": "Payload required to move axes to absolute position.", + "type": "object", + "properties": { + "axis_map": { + "title": "Axis Map", + "description": "The specified axes to move to an absolute deck position with.", + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "critical_point": { + "title": "Critical Point", + "description": "The critical point to move the mount with.", + "type": "object", + "additionalProperties": { + "type": "number" + } + }, + "speed": { + "title": "Speed", + "description": "The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + "type": "number" + } + }, + "required": ["axis_map"] + }, + "MoveAxesToCreate": { + "title": "MoveAxesToCreate", + "description": "MoveAxesTo command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "robot/moveAxesTo", + "enum": ["robot/moveAxesTo"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveAxesToParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + }, + "MoveToParams": { + "title": "MoveToParams", + "description": "Payload required to move to a destination position.", + "type": "object", + "properties": { + "mount": { + "description": "The mount to move to the destination point.", + "allOf": [ + { + "$ref": "#/definitions/MountType" + } + ] + }, + "destination": { + "title": "Destination", + "description": "X, Y and Z coordinates in mm from deck's origin location (left-front-bottom corner of work space)", + "allOf": [ + { + "$ref": "#/definitions/DeckPoint" + } + ] + }, + "speed": { + "title": "Speed", + "description": "The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + "type": "number" + } + }, + "required": ["mount", "destination"] + }, + "MoveToCreate": { + "title": "MoveToCreate", + "description": "MoveTo command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "robot/moveTo", + "enum": ["robot/moveTo"], + "type": "string" + }, + "params": { + "$ref": "#/definitions/MoveToParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": ["params"] + } + }, + "$id": "opentronsCommandSchemaV11", + "$schema": "http://json-schema.org/draft-07/schema#" +} From b205a87973ef44b23ea6645bc8537ecb18ea1614 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Fri, 1 Nov 2024 17:47:27 +0200 Subject: [PATCH 39/46] fix move group runner changes --- .../opentrons_hardware/hardware_control/move_group_runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hardware/opentrons_hardware/hardware_control/move_group_runner.py b/hardware/opentrons_hardware/hardware_control/move_group_runner.py index 6c5a3c2a9e0..7d621bd312b 100644 --- a/hardware/opentrons_hardware/hardware_control/move_group_runner.py +++ b/hardware/opentrons_hardware/hardware_control/move_group_runner.py @@ -244,7 +244,7 @@ def all_nodes(self) -> Set[NodeId]: for node in sequence.keys(): node_set.add(node) return node_set - + def all_moving_nodes(self) -> Set[NodeId]: """Get all of the moving nodes in the move group runner's move groups.""" node_set: Set[NodeId] = set() @@ -253,7 +253,7 @@ def all_moving_nodes(self) -> Set[NodeId]: for node, node_step in sequence.items(): if node_step.is_moving_step(): node_set.add(node) - return node_set + return node_set def all_moving_nodes(self) -> Set[NodeId]: """Get all of the moving nodes in the move group runner's move groups.""" From 4fae9a172e6c2588c3a499d6bf2efd0fcacbf1f4 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Sat, 2 Nov 2024 19:18:13 +0200 Subject: [PATCH 40/46] fix failing linters and formatting --- .../protocols/liquid_handler.py | 4 +- .../protocols/motion_controller.py | 2 +- .../opentrons/protocol_api/core/protocol.py | 2 +- api/src/opentrons/protocol_api/validation.py | 41 ++++++++++++------- .../protocol_engine/commands/robot/common.py | 3 ++ .../commands/robot/move_axes_relative.py | 1 + .../commands/robot/move_axes_to.py | 2 +- .../protocol_engine/commands/robot/move_to.py | 2 +- .../protocol_engine/execution/gantry_mover.py | 17 +++++++- .../protocol_api/test_robot_context.py | 4 ++ .../robot/test_move_axes_relative_to.py | 3 +- .../commands/robot/test_move_axes_to.py | 3 +- .../execution/test_gantry_mover.py | 1 + 13 files changed, 60 insertions(+), 25 deletions(-) diff --git a/api/src/opentrons/hardware_control/protocols/liquid_handler.py b/api/src/opentrons/hardware_control/protocols/liquid_handler.py index f2f3f2ecef4..2aea15bd55b 100644 --- a/api/src/opentrons/hardware_control/protocols/liquid_handler.py +++ b/api/src/opentrons/hardware_control/protocols/liquid_handler.py @@ -1,8 +1,8 @@ -from typing import Optional, Dict +from typing import Optional from typing_extensions import Protocol from opentrons.types import Point -from opentrons.hardware_control.types import CriticalPoint, Axis +from opentrons.hardware_control.types import CriticalPoint from .types import MountArgType, CalibrationType, ConfigType from .instrument_configurer import InstrumentConfigurer diff --git a/api/src/opentrons/hardware_control/protocols/motion_controller.py b/api/src/opentrons/hardware_control/protocols/motion_controller.py index 6fda13f8f38..e95a9d2e24f 100644 --- a/api/src/opentrons/hardware_control/protocols/motion_controller.py +++ b/api/src/opentrons/hardware_control/protocols/motion_controller.py @@ -1,4 +1,4 @@ -from typing import Dict, Union, List, Optional, Mapping +from typing import Dict, List, Optional, Mapping from typing_extensions import Protocol from opentrons.types import Point diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index c367c2b4bba..62e2d7cd1d7 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import abstractmethod, ABC -from typing import Generic, Type, List, Optional, Union, Tuple, Dict, TYPE_CHECKING +from typing import Generic, List, Optional, Union, Tuple, Dict, TYPE_CHECKING from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3 from opentrons_shared_data.pipette.types import PipetteNameType diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 9baba2268ce..6b491d7e9b4 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -193,29 +193,24 @@ def ensure_pipette_name(pipette_name: str) -> PipetteNameType: ) from None -def ensure_axis_map_type( - axis_map: Union[AxisMapType, StringAxisMap], - robot_type: RobotType, - is_96_channel: bool = False, -) -> AxisMapType: - """Ensure that the axis map provided is in the correct shape and contains the correct keys.""" - axis_map_keys = list(axis_map.keys()) - key_type = set(type(k) for k in axis_map_keys) - - if len(key_type) > 1: - raise IncorrectAxisError( - "Please provide an `axis_map` with only string or only AxisType keys." - ) +def _check_ot2_axis_type( + robot_type: RobotType, axis_map_keys: Union[List[str], List[AxisType]] +) -> None: if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], AxisType): if any(k not in AxisType.ot2_axes() for k in axis_map_keys): raise IncorrectAxisError( f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" ) if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], str): - if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [attr-defined] + if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [union-attr] raise IncorrectAxisError( f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" ) + + +def _check_96_channel_axis_type( + is_96_channel: bool, axis_map_keys: Union[List[str], List[AxisType]] +) -> None: if is_96_channel and any( key_variation in axis_map_keys for key_variation in ["Z_R", "z_r", AxisType.Z_R] ): @@ -229,6 +224,23 @@ def ensure_axis_map_type( "A 96 channel is not attached. The clamp `Q` motor does not exist." ) + +def ensure_axis_map_type( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool = False, +) -> AxisMapType: + """Ensure that the axis map provided is in the correct shape and contains the correct keys.""" + axis_map_keys: Union[List[str], List[AxisType]] = list(axis_map.keys()) # type: ignore + key_type = set(type(k) for k in axis_map_keys) + + if len(key_type) > 1: + raise IncorrectAxisError( + "Please provide an `axis_map` with only string or only AxisType keys." + ) + _check_ot2_axis_type(robot_type, axis_map_keys) + _check_96_channel_axis_type(is_96_channel, axis_map_keys) + if all(isinstance(k, AxisType) for k in axis_map_keys): return_map: AxisMapType = axis_map # type: ignore return return_map @@ -241,6 +253,7 @@ def ensure_axis_map_type( def ensure_only_gantry_axis_map_type( axis_map: AxisMapType, robot_type: RobotType ) -> None: + """Ensure that the axis map provided is in the correct shape and matches the gantry axes for the robot.""" if robot_type == "OT-2 Standard": if any(k not in AxisType.ot2_gantry_axes() for k in axis_map.keys()): raise IncorrectAxisError( diff --git a/api/src/opentrons/protocol_engine/commands/robot/common.py b/api/src/opentrons/protocol_engine/commands/robot/common.py index e5401e3045f..1cd0b0d17b3 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/common.py +++ b/api/src/opentrons/protocol_engine/commands/robot/common.py @@ -1,3 +1,4 @@ +"""Shared result types for robot API commands.""" from pydantic import BaseModel, Field from typing import Dict @@ -9,6 +10,8 @@ class DestinationRobotPositionResult(BaseModel): + """The result dictionary of `MotorAxis` type.""" + position: MotorAxisMapType = Field( default=default_position, description="The position of all axes on the robot. If no mount was provided, the last moved mount is used to determine the location.", diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py index bd1da7b60c6..238229ebce6 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py @@ -58,6 +58,7 @@ def __init__( async def execute( self, params: MoveAxesRelativeParams ) -> SuccessData[MoveAxesRelativeResult]: + """Move the axes on a flex a relative distance.""" # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller # and then we can remove this validation. ensure_ot3_hardware(self._hardware_api) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py index 98b8128db29..0d17d5f171f 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -3,7 +3,6 @@ from typing import Literal, Optional, Type, TYPE_CHECKING from pydantic import Field, BaseModel -from opentrons.protocol_engine.types import MotorAxis from opentrons.hardware_control import HardwareControlAPI from opentrons.protocol_engine.resources import ensure_ot3_hardware @@ -59,6 +58,7 @@ def __init__( self._hardware_api = hardware_api async def execute(self, params: MoveAxesToParams) -> SuccessData[MoveAxesToResult]: + """Move the axes on a flex an absolute distance.""" # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller # and then we can remove this validation. ensure_ot3_hardware(self._hardware_api) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_to.py index 4b310bcd9ab..44b8c4fdbe2 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/move_to.py +++ b/api/src/opentrons/protocol_engine/commands/robot/move_to.py @@ -4,7 +4,6 @@ from pydantic import BaseModel, Field from opentrons.types import MountType -from opentrons.hardware_control.protocols.types import FlexRobotType from ..pipetting_common import DestinationPositionResult from ..command import ( @@ -60,6 +59,7 @@ def __init__( self._movement = movement async def execute(self, params: MoveToParams) -> SuccessData[MoveToResult]: + """Move to a given destination on a flex.""" x, y, z = await self._movement.move_mount_to( mount=params.mount, destination=params.destination, speed=params.speed ) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index c76be093c7f..1cb50f21418 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -246,6 +246,13 @@ async def get_position_from_mount( critical_point: Optional[CriticalPoint] = None, fail_on_not_homed: bool = False, ) -> Point: + """Get the current position of the gantry based on the mount. + + Args: + mount: The mount to get the position for. + critical_point: Optional parameter for getting instrument location data, effects critical point. + fail_on_not_homed: Raise PositionUnknownError if gantry position is not known. + """ try: point = await self._hardware_api.gantry_position( mount=mount, @@ -319,8 +326,9 @@ async def move_axes( Args: axis_map: The mapping of axes to command. - relative_move: Specifying whether a relative move needs to be handled or not. + critical_point: A critical point override for axes speed: Optional speed parameter for the move. + relative_move: Specifying whether a relative move needs to be handled or not. """ try: pos_hw = self._convert_axis_map_for_hw(axis_map) @@ -496,6 +504,13 @@ async def get_position_from_mount( critical_point: Optional[CriticalPoint] = None, fail_on_not_homed: bool = False, ) -> Point: + """Get the current position of the gantry based on the mount. + + Args: + mount: The mount to get the position for. + critical_point: Optional parameter for getting instrument location data, effects critical point. + fail_on_not_homed: Raise PositionUnknownError if gantry position is not known. + """ pipette = self._state_view.pipettes.get_by_mount(MountType[mount.name]) origin_deck_point = ( self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py index 7d1531705b8..f60869c3813 100644 --- a/api/tests/opentrons/protocol_api/test_robot_context.py +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -76,6 +76,7 @@ def test_move_to( destination: Location, speed: Optional[float], ) -> None: + """Test `RobotContext.move_to`.""" subject.move_to(mount, destination, speed) core_mount: Mount if isinstance(mount, str): @@ -119,6 +120,7 @@ def test_move_axes_to( expected_critical_point: AxisMapType, speed: Optional[float], ) -> None: + """Test `RobotContext.move_axes_to`.""" subject.move_axes_to(axis_map, critical_point, speed) decoy.verify( subject._core.move_axes_to(expected_axis_map, expected_critical_point, speed) @@ -143,6 +145,7 @@ def test_move_axes_relative( converted_map: AxisMapType, speed: Optional[float], ) -> None: + """Test `RobotContext.move_axes_relative`.""" subject.move_axes_relative(axis_map, speed) decoy.verify(subject._core.move_axes_relative(converted_map, speed)) @@ -168,5 +171,6 @@ def test_get_axes_coordinates_for( location_to_move: Union[Location, ModuleContext, DeckLocation], expected_axis_map: AxisMapType, ) -> None: + """Test `RobotContext.get_axis_coordinates_for`.""" res = subject.axis_coordinates_for(mount, location_to_move) assert res == expected_axis_map diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py index 9fb31ba4e82..11c6e13b54f 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py @@ -5,8 +5,7 @@ from opentrons.protocol_engine.execution import GantryMover from opentrons.protocol_engine.types import MotorAxis -from opentrons.hardware_control.protocols.types import FlexRobotType, OT2RobotType -from opentrons.types import Point, MountType +from opentrons.hardware_control.protocols.types import FlexRobotType from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands.robot.move_axes_relative import ( diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py index f85d1b24058..3caa8b03ec8 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py @@ -5,8 +5,7 @@ from opentrons.protocol_engine.execution import GantryMover from opentrons.protocol_engine.types import MotorAxis -from opentrons.hardware_control.protocols.types import FlexRobotType, OT2RobotType -from opentrons.types import Point, MountType +from opentrons.hardware_control.protocols.types import FlexRobotType from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands.robot.move_axes_to import ( diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index 7712faf1408..7baaf4df481 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -705,5 +705,6 @@ async def test_virtual_move_axes( relative_move: bool, expected_position: Dict[MotorAxis, float], ) -> None: + """It should simulate moving a set of axis by a certain distance.""" pos = await virtual_subject.move_axes(axis_map, critical_point, 100, relative_move) assert pos == expected_position From 6113fe9bb8a16a80c022998951eb06ef6fad2d76 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Sun, 3 Nov 2024 17:15:06 +0200 Subject: [PATCH 41/46] fix more linter errors --- api/src/opentrons/protocol_api/protocol_context.py | 4 ++-- api/src/opentrons/protocol_api/robot_context.py | 8 ++++---- .../protocol_engine/execution/gantry_mover.py | 12 +++++------- .../opentrons/protocol_engine/execution/movement.py | 1 + api/src/opentrons/protocol_engine/types.py | 2 +- api/src/opentrons/types.py | 3 ++- .../opentrons/protocol_api/test_robot_context.py | 2 ++ api/tests/opentrons/protocol_api/test_validation.py | 3 +++ .../protocol_engine/commands/robot/__init__.py | 1 + .../protocol_engine/execution/test_gantry_mover.py | 7 ++++--- 10 files changed, 25 insertions(+), 18 deletions(-) diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 26f2ab1a161..88be8a8ced4 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -218,14 +218,14 @@ def api_version(self) -> APIVersion: return self._api_version @property - @requires_version(2, 21) + @requires_version(2, 22) def robot(self) -> RobotContext: """The :py:class:`.RobotContext` for the protocol. :meta private: """ if self._core.robot_type != "OT-3 Standard" or not self._robot: - raise RobotTypeError("The RobotContext is only available on Flex robot.") + raise RobotTypeError("The RobotContext is only available on Flex robots.") return self._robot @property diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 8447cb4217d..272330e1664 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -56,7 +56,7 @@ def __init__( self._api_version = api_version @property - @requires_version(2, 21) + @requires_version(2, 22) def api_version(self) -> APIVersion: return self._api_version @@ -67,7 +67,7 @@ def hardware(self) -> HardwareManager: # context commands. return self._hardware - @requires_version(2, 21) + @requires_version(2, 22) def move_to( self, mount: Union[Mount, str], @@ -88,7 +88,7 @@ def move_to( mount = validation.ensure_instrument_mount(mount) self._core.move_to(mount, destination.point, speed) - @requires_version(2, 21) + @requires_version(2, 22) def move_axes_to( self, axis_map: Union[AxisMapType, StringAxisMap], @@ -120,7 +120,7 @@ def move_axes_to( critical_point = None self._core.move_axes_to(axis_map, critical_point, speed) - @requires_version(2, 21) + @requires_version(2, 22) def move_axes_relative( self, axis_map: Union[AxisMapType, StringAxisMap], diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 1cb50f21418..05680ca46b9 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -33,7 +33,7 @@ MotorAxis.RIGHT_PLUNGER: HardwareAxis.C, MotorAxis.EXTENSION_Z: HardwareAxis.Z_G, MotorAxis.EXTENSION_JAW: HardwareAxis.G, - MotorAxis.CLAMP_JAW_96_CHANNEL: HardwareAxis.Q, + MotorAxis.AXIS_96_CHANNEL_CAM: HardwareAxis.Q, } _MOTOR_AXIS_TO_HARDWARE_MOUNT: Dict[MotorAxis, Mount] = { @@ -61,7 +61,7 @@ HardwareAxis.Z_R: MotorAxis.RIGHT_Z, HardwareAxis.Z_G: MotorAxis.EXTENSION_Z, HardwareAxis.G: MotorAxis.EXTENSION_JAW, - HardwareAxis.Q: MotorAxis.CLAMP_JAW_96_CHANNEL, + HardwareAxis.Q: MotorAxis.AXIS_96_CHANNEL_CAM, } # The height of the bottom of the pipette nozzle at home position without any tips. @@ -328,7 +328,7 @@ async def move_axes( axis_map: The mapping of axes to command. critical_point: A critical point override for axes speed: Optional speed parameter for the move. - relative_move: Specifying whether a relative move needs to be handled or not. + relative_move: Whether the axis map needs to be converted from a relative to absolute move. """ try: pos_hw = self._convert_axis_map_for_hw(axis_map) @@ -544,10 +544,8 @@ def get_max_travel_z_from_mount(self, mount: MountType) -> float: """Get the maximum allowed z-height for mount.""" pipette = self._state_view.pipettes.get_by_mount(mount) if self._state_view.config.robot_type == "OT-2 Standard": - instrument_height = ( - self._state_view.pipettes.get_instrument_max_height_ot2(pipette.id) - if pipette - else VIRTUAL_MAX_OT3_HEIGHT + instrument_height = self._state_view.pipettes.get_instrument_max_height_ot2( + pipette.id ) else: instrument_height = VIRTUAL_MAX_OT3_HEIGHT diff --git a/api/src/opentrons/protocol_engine/execution/movement.py b/api/src/opentrons/protocol_engine/execution/movement.py index d05bc3851e7..9e391160a25 100644 --- a/api/src/opentrons/protocol_engine/execution/movement.py +++ b/api/src/opentrons/protocol_engine/execution/movement.py @@ -147,6 +147,7 @@ async def move_to_well( async def move_mount_to( self, mount: MountType, destination: DeckPoint, speed: Optional[float] = None ) -> Point: + """Move mount to a specific location on the deck.""" hw_mount = mount.to_hw_mount() await self._gantry_mover.prepare_for_mount_movement(hw_mount) origin = await self._gantry_mover.get_position_from_mount(mount=hw_mount) diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 1424342817b..73a2df980f9 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -457,7 +457,7 @@ class MotorAxis(str, Enum): RIGHT_PLUNGER = "rightPlunger" EXTENSION_Z = "extensionZ" EXTENSION_JAW = "extensionJaw" - CLAMP_JAW_96_CHANNEL = "clampJaw96Channel" + AXIS_96_CHANNEL_CAM = "axis96ChannelCam" # TODO(mc, 2022-01-18): use opentrons_shared_data.module.types.ModuleModel diff --git a/api/src/opentrons/types.py b/api/src/opentrons/types.py index 41a1ac57402..0a07e6c0e2f 100644 --- a/api/src/opentrons/types.py +++ b/api/src/opentrons/types.py @@ -303,7 +303,7 @@ def ot2_axes(cls) -> List["AxisType"]: ] @classmethod - def ot3_gantry_axes(cls) -> List["AxisType"]: + def flex_gantry_axes(cls) -> List["AxisType"]: return [ AxisType.X, AxisType.Y, @@ -325,6 +325,7 @@ def ot2_gantry_axes(cls) -> List["AxisType"]: AxisMapType = Dict[AxisType, float] StringAxisMap = Dict[str, float] + # TODO(mc, 2020-11-09): this makes sense in shared-data or other common # model library # https://github.com/Opentrons/opentrons/pull/6943#discussion_r519029833 diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py index f60869c3813..3e0be14b865 100644 --- a/api/tests/opentrons/protocol_api/test_robot_context.py +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -1,3 +1,4 @@ +"""Test the functionality of the `RobotContext`.""" import pytest from decoy import Decoy from typing import Union, Optional @@ -32,6 +33,7 @@ def api_version() -> APIVersion: @pytest.fixture def mock_deck(decoy: Decoy) -> Deck: + """Get a mocked deck object.""" deck = decoy.mock(cls=Deck) decoy.when(deck.get_slot_center(DeckSlotName.SLOT_D1.value)).then_return( Point(3, 3, 3) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 56453e09fd0..c7f35a1519e 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -595,6 +595,7 @@ def test_ensure_axis_map_type_success( is_96_channel: bool, expected_axis_map: AxisMapType, ) -> None: + """Check that axis map type validation returns the correct shape.""" res = subject.ensure_axis_map_type(axis_map, robot_type, is_96_channel) assert res == expected_axis_map @@ -634,6 +635,7 @@ def test_ensure_axis_map_type_failure( is_96_channel: bool, error_message: str, ) -> None: + """Check that axis_map validation occurs for the given scenarios.""" with pytest.raises(subject.IncorrectAxisError, match=error_message): subject.ensure_axis_map_type(axis_map, robot_type, is_96_channel) @@ -656,5 +658,6 @@ def test_ensure_axis_map_type_failure( def test_ensure_only_gantry_axis_map_type( axis_map: AxisMapType, robot_type: RobotType, error_message: str ) -> None: + """Check that gantry axis_map validation occurs for the given scenarios.""" with pytest.raises(subject.IncorrectAxisError, match=error_message): subject.ensure_only_gantry_axis_map_type(axis_map, robot_type) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/__init__.py b/api/tests/opentrons/protocol_engine/commands/robot/__init__.py index e69de29bb2d..36375876456 100644 --- a/api/tests/opentrons/protocol_engine/commands/robot/__init__.py +++ b/api/tests/opentrons/protocol_engine/commands/robot/__init__.py @@ -0,0 +1 @@ +"""Tests for Robot Module commands.""" diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index 7baaf4df481..2c872c003d1 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -501,7 +501,7 @@ async def test_home_z( }, ], [ - {MotorAxis.CLAMP_JAW_96_CHANNEL: 10.0}, + {MotorAxis.AXIS_96_CHANNEL_CAM: 10.0}, None, False, Mount.LEFT, @@ -522,6 +522,7 @@ async def test_move_axes( call_to_hw: "OrderedDict[HardwareAxis, float]", final_position: Dict[HardwareAxis, float], ) -> None: + """Test the move axes function.""" subject = HardwareGantryMover( state_view=mock_state_view, hardware_api=ot3_hardware_api ) @@ -690,10 +691,10 @@ async def test_virtual_move_to( {MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.RIGHT_Z: 20}, ], [ - {MotorAxis.CLAMP_JAW_96_CHANNEL: 10}, + {MotorAxis.AXIS_96_CHANNEL_CAM: 10}, None, False, - {MotorAxis.CLAMP_JAW_96_CHANNEL: 10}, + {MotorAxis.AXIS_96_CHANNEL_CAM: 10}, ], ], ) From 7c2e804fc4e45229a856e0ffad26e6f5e5e60e26 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Sun, 3 Nov 2024 21:01:21 +0200 Subject: [PATCH 42/46] fix linter errors --- api/src/opentrons/protocol_api/core/engine/robot.py | 2 +- api/src/opentrons/protocol_api/validation.py | 4 ++-- .../opentrons/protocol_engine/execution/gantry_mover.py | 8 ++++++-- api/tests/opentrons/protocol_api/test_robot_context.py | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py index 7deabff0571..477f1968c5a 100644 --- a/api/src/opentrons/protocol_api/core/engine/robot.py +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -17,7 +17,7 @@ AxisType.Z_R: MotorAxis.RIGHT_Z, AxisType.Z_G: MotorAxis.EXTENSION_Z, AxisType.G: MotorAxis.EXTENSION_JAW, - AxisType.Q: MotorAxis.CLAMP_JAW_96_CHANNEL, + AxisType.Q: MotorAxis.AXIS_96_CHANNEL_CAM, } diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 6b491d7e9b4..1f8fb4bd8c8 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -260,9 +260,9 @@ def ensure_only_gantry_axis_map_type( f"A critical point only accepts OT-2 gantry axes which are {AxisType.ot2_gantry_axes()}" ) else: - if any(k not in AxisType.ot3_gantry_axes() for k in axis_map.keys()): + if any(k not in AxisType.flex_gantry_axes() for k in axis_map.keys()): raise IncorrectAxisError( - f"A critical point only accepts Flex gantry axes which are {AxisType.ot3_gantry_axes()}" + f"A critical point only accepts Flex gantry axes which are {AxisType.flex_gantry_axes()}" ) diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 05680ca46b9..7306bc4e4d1 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -74,6 +74,8 @@ # That OT3Simulator return value is what Protocol Engine uses for simulation when Protocol Engine # is configured to not virtualize pipettes, so this number should match it. VIRTUAL_MAX_OT3_HEIGHT = 248.0 +# This number was found by using the longest pipette's P1000V2 default configuration values. +VIRTUAL_MAX_OT2_HEIGHT = 268.14 class GantryMover(TypingProtocol): @@ -544,8 +546,10 @@ def get_max_travel_z_from_mount(self, mount: MountType) -> float: """Get the maximum allowed z-height for mount.""" pipette = self._state_view.pipettes.get_by_mount(mount) if self._state_view.config.robot_type == "OT-2 Standard": - instrument_height = self._state_view.pipettes.get_instrument_max_height_ot2( - pipette.id + instrument_height = ( + self._state_view.pipettes.get_instrument_max_height_ot2(pipette.id) + if pipette + else VIRTUAL_MAX_OT2_HEIGHT ) else: instrument_height = VIRTUAL_MAX_OT3_HEIGHT diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py index 3e0be14b865..c1bdfe48c3f 100644 --- a/api/tests/opentrons/protocol_api/test_robot_context.py +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -15,7 +15,7 @@ ) from opentrons.protocols.api_support.types import APIVersion from opentrons.protocol_api.core.common import ProtocolCore, RobotCore -from opentrons.protocol_api import RobotContext, ModuleContext, MAX_SUPPORTED_VERSION +from opentrons.protocol_api import RobotContext, ModuleContext from opentrons.protocol_api.deck import Deck @@ -28,7 +28,7 @@ def mock_core(decoy: Decoy) -> RobotCore: @pytest.fixture def api_version() -> APIVersion: """Get the API version to test at.""" - return MAX_SUPPORTED_VERSION + return APIVersion(2, 22) @pytest.fixture From 25f59165b7418391ff3734b0b8a49c795736292d Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Tue, 5 Nov 2024 22:26:00 +0200 Subject: [PATCH 43/46] refresh command schema --- shared-data/command/schemas/11.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json index 465708864ce..0340e4a1cc1 100644 --- a/shared-data/command/schemas/11.json +++ b/shared-data/command/schemas/11.json @@ -1247,7 +1247,7 @@ "rightPlunger", "extensionZ", "extensionJaw", - "clampJaw96Channel" + "axis96ChannelCam" ], "type": "string" }, From 2b5dcd2bf43a06f875972305100f326ec737fca5 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Thu, 7 Nov 2024 00:07:03 +0200 Subject: [PATCH 44/46] add additional commands to schema 11 --- shared-data/command/schemas/11.json | 1262 +++++++++++++++++++++------ 1 file changed, 990 insertions(+), 272 deletions(-) diff --git a/shared-data/command/schemas/11.json b/shared-data/command/schemas/11.json index 0340e4a1cc1..4ee7e930abe 100644 --- a/shared-data/command/schemas/11.json +++ b/shared-data/command/schemas/11.json @@ -4,6 +4,7 @@ "discriminator": { "propertyName": "commandType", "mapping": { + "airGapInPlace": "#/definitions/AirGapInPlaceCreate", "aspirate": "#/definitions/AspirateCreate", "aspirateInPlace": "#/definitions/AspirateInPlaceCreate", "comment": "#/definitions/CommentCreate", @@ -84,6 +85,9 @@ } }, "oneOf": [ + { + "$ref": "#/definitions/AirGapInPlaceCreate" + }, { "$ref": "#/definitions/AspirateCreate" }, @@ -314,10 +318,88 @@ } ], "definitions": { + "AirGapInPlaceParams": { + "title": "AirGapInPlaceParams", + "description": "Payload required to air gap in place.", + "type": "object", + "properties": { + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "The amount of liquid to aspirate, in \u00b5L. Must not be greater than the remaining available amount, which depends on the pipette (see `loadPipette`), its configuration (see `configureForVolume`), the tip (see `pickUpTip`), and the amount you've aspirated so far. There is some tolerance for floating point rounding errors.", + "minimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": [ + "flowRate", + "volume", + "pipetteId" + ] + }, + "CommandIntent": { + "title": "CommandIntent", + "description": "Run intent for a given command.\n\nProps:\n PROTOCOL: the command is part of the protocol run itself.\n SETUP: the command is part of the setup phase of a run.", + "enum": [ + "protocol", + "setup", + "fixit" + ], + "type": "string" + }, + "AirGapInPlaceCreate": { + "title": "AirGapInPlaceCreate", + "description": "AirGapInPlace command request model.", + "type": "object", + "properties": { + "commandType": { + "title": "Commandtype", + "default": "airGapInPlace", + "enum": [ + "airGapInPlace" + ], + "type": "string" + }, + "params": { + "$ref": "#/definitions/AirGapInPlaceParams" + }, + "intent": { + "description": "The reason the command was added. If not specified or `protocol`, the command will be treated as part of the protocol run itself, and added to the end of the existing command queue.\n\nIf `setup`, the command will be treated as part of run setup. A setup command may only be enqueued if the run has not started.\n\nUse setup commands for activities like pre-run calibration checks and module setup, like pre-heating.", + "allOf": [ + { + "$ref": "#/definitions/CommandIntent" + } + ] + }, + "key": { + "title": "Key", + "description": "A key value, unique in this run, that can be used to track the same logical command across multiple runs of the same protocol. If a value is not provided, one will be generated.", + "type": "string" + } + }, + "required": [ + "params" + ] + }, "WellOrigin": { "title": "WellOrigin", "description": "Origin of WellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well\n MENISCUS: the meniscus-center of the well", - "enum": ["top", "bottom", "center", "meniscus"], + "enum": [ + "top", + "bottom", + "center", + "meniscus" + ], "type": "string" }, "WellOffset": { @@ -367,7 +449,9 @@ "type": "number" }, { - "enum": ["operationVolume"], + "enum": [ + "operationVolume" + ], "type": "string" } ] @@ -416,13 +500,13 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] - }, - "CommandIntent": { - "title": "CommandIntent", - "description": "Run intent for a given command.\n\nProps:\n PROTOCOL: the command is part of the protocol run itself.\n SETUP: the command is part of the setup phase of a run.", - "enum": ["protocol", "setup", "fixit"], - "type": "string" + "required": [ + "labwareId", + "wellName", + "flowRate", + "volume", + "pipetteId" + ] }, "AspirateCreate": { "title": "AspirateCreate", @@ -432,7 +516,9 @@ "commandType": { "title": "Commandtype", "default": "aspirate", - "enum": ["aspirate"], + "enum": [ + "aspirate" + ], "type": "string" }, "params": { @@ -452,7 +538,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "AspirateInPlaceParams": { "title": "AspirateInPlaceParams", @@ -477,7 +565,11 @@ "type": "string" } }, - "required": ["flowRate", "volume", "pipetteId"] + "required": [ + "flowRate", + "volume", + "pipetteId" + ] }, "AspirateInPlaceCreate": { "title": "AspirateInPlaceCreate", @@ -487,7 +579,9 @@ "commandType": { "title": "Commandtype", "default": "aspirateInPlace", - "enum": ["aspirateInPlace"], + "enum": [ + "aspirateInPlace" + ], "type": "string" }, "params": { @@ -507,7 +601,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CommentParams": { "title": "CommentParams", @@ -520,7 +616,9 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] }, "CommentCreate": { "title": "CommentCreate", @@ -530,7 +628,9 @@ "commandType": { "title": "Commandtype", "default": "comment", - "enum": ["comment"], + "enum": [ + "comment" + ], "type": "string" }, "params": { @@ -550,7 +650,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "ConfigureForVolumeParams": { "title": "ConfigureForVolumeParams", @@ -574,7 +676,10 @@ "type": "string" } }, - "required": ["pipetteId", "volume"] + "required": [ + "pipetteId", + "volume" + ] }, "ConfigureForVolumeCreate": { "title": "ConfigureForVolumeCreate", @@ -584,7 +689,9 @@ "commandType": { "title": "Commandtype", "default": "configureForVolume", - "enum": ["configureForVolume"], + "enum": [ + "configureForVolume" + ], "type": "string" }, "params": { @@ -604,7 +711,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "AllNozzleLayoutConfiguration": { "title": "AllNozzleLayoutConfiguration", @@ -614,7 +723,9 @@ "style": { "title": "Style", "default": "ALL", - "enum": ["ALL"], + "enum": [ + "ALL" + ], "type": "string" } } @@ -627,17 +738,26 @@ "style": { "title": "Style", "default": "SINGLE", - "enum": ["SINGLE"], + "enum": [ + "SINGLE" + ], "type": "string" }, "primaryNozzle": { "title": "Primarynozzle", "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", - "enum": ["A1", "H1", "A12", "H12"], + "enum": [ + "A1", + "H1", + "A12", + "H12" + ], "type": "string" } }, - "required": ["primaryNozzle"] + "required": [ + "primaryNozzle" + ] }, "RowNozzleLayoutConfiguration": { "title": "RowNozzleLayoutConfiguration", @@ -647,17 +767,26 @@ "style": { "title": "Style", "default": "ROW", - "enum": ["ROW"], + "enum": [ + "ROW" + ], "type": "string" }, "primaryNozzle": { "title": "Primarynozzle", "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", - "enum": ["A1", "H1", "A12", "H12"], + "enum": [ + "A1", + "H1", + "A12", + "H12" + ], "type": "string" } }, - "required": ["primaryNozzle"] + "required": [ + "primaryNozzle" + ] }, "ColumnNozzleLayoutConfiguration": { "title": "ColumnNozzleLayoutConfiguration", @@ -667,17 +796,26 @@ "style": { "title": "Style", "default": "COLUMN", - "enum": ["COLUMN"], + "enum": [ + "COLUMN" + ], "type": "string" }, "primaryNozzle": { "title": "Primarynozzle", "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", - "enum": ["A1", "H1", "A12", "H12"], + "enum": [ + "A1", + "H1", + "A12", + "H12" + ], "type": "string" } }, - "required": ["primaryNozzle"] + "required": [ + "primaryNozzle" + ] }, "QuadrantNozzleLayoutConfiguration": { "title": "QuadrantNozzleLayoutConfiguration", @@ -687,13 +825,20 @@ "style": { "title": "Style", "default": "QUADRANT", - "enum": ["QUADRANT"], + "enum": [ + "QUADRANT" + ], "type": "string" }, "primaryNozzle": { "title": "Primarynozzle", "description": "The primary nozzle to use in the layout configuration. This nozzle will update the critical point of the current pipette. For now, this is also the back left corner of your rectangle.", - "enum": ["A1", "H1", "A12", "H12"], + "enum": [ + "A1", + "H1", + "A12", + "H12" + ], "type": "string" }, "frontRightNozzle": { @@ -709,7 +854,11 @@ "type": "string" } }, - "required": ["primaryNozzle", "frontRightNozzle", "backLeftNozzle"] + "required": [ + "primaryNozzle", + "frontRightNozzle", + "backLeftNozzle" + ] }, "ConfigureNozzleLayoutParams": { "title": "ConfigureNozzleLayoutParams", @@ -742,7 +891,10 @@ ] } }, - "required": ["pipetteId", "configurationParams"] + "required": [ + "pipetteId", + "configurationParams" + ] }, "ConfigureNozzleLayoutCreate": { "title": "ConfigureNozzleLayoutCreate", @@ -752,7 +904,9 @@ "commandType": { "title": "Commandtype", "default": "configureNozzleLayout", - "enum": ["configureNozzleLayout"], + "enum": [ + "configureNozzleLayout" + ], "type": "string" }, "params": { @@ -772,7 +926,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CustomParams": { "title": "CustomParams", @@ -788,7 +944,9 @@ "commandType": { "title": "Commandtype", "default": "custom", - "enum": ["custom"], + "enum": [ + "custom" + ], "type": "string" }, "params": { @@ -808,7 +966,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DispenseParams": { "title": "DispenseParams", @@ -857,7 +1017,13 @@ "type": "number" } }, - "required": ["labwareId", "wellName", "flowRate", "volume", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "flowRate", + "volume", + "pipetteId" + ] }, "DispenseCreate": { "title": "DispenseCreate", @@ -867,7 +1033,9 @@ "commandType": { "title": "Commandtype", "default": "dispense", - "enum": ["dispense"], + "enum": [ + "dispense" + ], "type": "string" }, "params": { @@ -887,7 +1055,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DispenseInPlaceParams": { "title": "DispenseInPlaceParams", @@ -917,7 +1087,11 @@ "type": "number" } }, - "required": ["flowRate", "volume", "pipetteId"] + "required": [ + "flowRate", + "volume", + "pipetteId" + ] }, "DispenseInPlaceCreate": { "title": "DispenseInPlaceCreate", @@ -927,7 +1101,9 @@ "commandType": { "title": "Commandtype", "default": "dispenseInPlace", - "enum": ["dispenseInPlace"], + "enum": [ + "dispenseInPlace" + ], "type": "string" }, "params": { @@ -947,7 +1123,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WellLocation": { "title": "WellLocation", @@ -1009,7 +1187,12 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "flowRate", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "flowRate", + "pipetteId" + ] }, "BlowOutCreate": { "title": "BlowOutCreate", @@ -1019,7 +1202,9 @@ "commandType": { "title": "Commandtype", "default": "blowout", - "enum": ["blowout"], + "enum": [ + "blowout" + ], "type": "string" }, "params": { @@ -1039,7 +1224,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "BlowOutInPlaceParams": { "title": "BlowOutInPlaceParams", @@ -1058,7 +1245,10 @@ "type": "string" } }, - "required": ["flowRate", "pipetteId"] + "required": [ + "flowRate", + "pipetteId" + ] }, "BlowOutInPlaceCreate": { "title": "BlowOutInPlaceCreate", @@ -1068,7 +1258,9 @@ "commandType": { "title": "Commandtype", "default": "blowOutInPlace", - "enum": ["blowOutInPlace"], + "enum": [ + "blowOutInPlace" + ], "type": "string" }, "params": { @@ -1088,12 +1280,19 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DropTipWellOrigin": { "title": "DropTipWellOrigin", "description": "The origin of a DropTipWellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well\n DEFAULT: the default drop-tip location of the well,\n based on pipette configuration and length of the tip.", - "enum": ["top", "bottom", "center", "default"], + "enum": [ + "top", + "bottom", + "center", + "default" + ], "type": "string" }, "DropTipWellLocation": { @@ -1155,7 +1354,11 @@ "type": "boolean" } }, - "required": ["pipetteId", "labwareId", "wellName"] + "required": [ + "pipetteId", + "labwareId", + "wellName" + ] }, "DropTipCreate": { "title": "DropTipCreate", @@ -1165,7 +1368,9 @@ "commandType": { "title": "Commandtype", "default": "dropTip", - "enum": ["dropTip"], + "enum": [ + "dropTip" + ], "type": "string" }, "params": { @@ -1185,7 +1390,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DropTipInPlaceParams": { "title": "DropTipInPlaceParams", @@ -1203,7 +1410,9 @@ "type": "boolean" } }, - "required": ["pipetteId"] + "required": [ + "pipetteId" + ] }, "DropTipInPlaceCreate": { "title": "DropTipInPlaceCreate", @@ -1213,7 +1422,9 @@ "commandType": { "title": "Commandtype", "default": "dropTipInPlace", - "enum": ["dropTipInPlace"], + "enum": [ + "dropTipInPlace" + ], "type": "string" }, "params": { @@ -1233,7 +1444,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MotorAxis": { "title": "MotorAxis", @@ -1254,7 +1467,11 @@ "MountType": { "title": "MountType", "description": "An enumeration.", - "enum": ["left", "right", "extension"], + "enum": [ + "left", + "right", + "extension" + ], "type": "string" }, "HomeParams": { @@ -1287,7 +1504,9 @@ "commandType": { "title": "Commandtype", "default": "home", - "enum": ["home"], + "enum": [ + "home" + ], "type": "string" }, "params": { @@ -1307,7 +1526,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "RetractAxisParams": { "title": "RetractAxisParams", @@ -1323,7 +1544,9 @@ ] } }, - "required": ["axis"] + "required": [ + "axis" + ] }, "RetractAxisCreate": { "title": "RetractAxisCreate", @@ -1333,7 +1556,9 @@ "commandType": { "title": "Commandtype", "default": "retractAxis", - "enum": ["retractAxis"], + "enum": [ + "retractAxis" + ], "type": "string" }, "params": { @@ -1353,7 +1578,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeckSlotName": { "title": "DeckSlotName", @@ -1399,7 +1626,9 @@ ] } }, - "required": ["slotName"] + "required": [ + "slotName" + ] }, "ModuleLocation": { "title": "ModuleLocation", @@ -1412,7 +1641,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "OnLabwareLocation": { "title": "OnLabwareLocation", @@ -1425,7 +1656,9 @@ "type": "string" } }, - "required": ["labwareId"] + "required": [ + "labwareId" + ] }, "AddressableAreaLocation": { "title": "AddressableAreaLocation", @@ -1438,7 +1671,9 @@ "type": "string" } }, - "required": ["addressableAreaName"] + "required": [ + "addressableAreaName" + ] }, "LoadLabwareParams": { "title": "LoadLabwareParams", @@ -1459,7 +1694,9 @@ "$ref": "#/definitions/OnLabwareLocation" }, { - "enum": ["offDeck"], + "enum": [ + "offDeck" + ], "type": "string" }, { @@ -1493,7 +1730,12 @@ "type": "string" } }, - "required": ["location", "loadName", "namespace", "version"] + "required": [ + "location", + "loadName", + "namespace", + "version" + ] }, "LoadLabwareCreate": { "title": "LoadLabwareCreate", @@ -1503,7 +1745,9 @@ "commandType": { "title": "Commandtype", "default": "loadLabware", - "enum": ["loadLabware"], + "enum": [ + "loadLabware" + ], "type": "string" }, "params": { @@ -1523,7 +1767,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "ReloadLabwareParams": { "title": "ReloadLabwareParams", @@ -1536,7 +1782,9 @@ "type": "string" } }, - "required": ["labwareId"] + "required": [ + "labwareId" + ] }, "ReloadLabwareCreate": { "title": "ReloadLabwareCreate", @@ -1546,7 +1794,9 @@ "commandType": { "title": "Commandtype", "default": "reloadLabware", - "enum": ["reloadLabware"], + "enum": [ + "reloadLabware" + ], "type": "string" }, "params": { @@ -1566,7 +1816,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "LoadLiquidParams": { "title": "LoadLiquidParams", @@ -1575,8 +1827,18 @@ "properties": { "liquidId": { "title": "Liquidid", - "description": "Unique identifier of the liquid to load.", - "type": "string" + "description": "Unique identifier of the liquid to load. If this is the sentinel value EMPTY, all values of volumeByWell must be 0.", + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "EMPTY" + ], + "type": "string" + } + ] }, "labwareId": { "title": "Labwareid", @@ -1585,14 +1847,18 @@ }, "volumeByWell": { "title": "Volumebywell", - "description": "Volume of liquid, in \u00b5L, loaded into each well by name, in this labware.", + "description": "Volume of liquid, in \u00b5L, loaded into each well by name, in this labware. If the liquid id is the sentinel value EMPTY, all volumes must be 0.", "type": "object", "additionalProperties": { "type": "number" } } }, - "required": ["liquidId", "labwareId", "volumeByWell"] + "required": [ + "liquidId", + "labwareId", + "volumeByWell" + ] }, "LoadLiquidCreate": { "title": "LoadLiquidCreate", @@ -1602,7 +1868,9 @@ "commandType": { "title": "Commandtype", "default": "loadLiquid", - "enum": ["loadLiquid"], + "enum": [ + "loadLiquid" + ], "type": "string" }, "params": { @@ -1622,7 +1890,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "ModuleModel": { "title": "ModuleModel", @@ -1668,7 +1938,10 @@ "type": "string" } }, - "required": ["model", "location"] + "required": [ + "model", + "location" + ] }, "LoadModuleCreate": { "title": "LoadModuleCreate", @@ -1678,7 +1951,9 @@ "commandType": { "title": "Commandtype", "default": "loadModule", - "enum": ["loadModule"], + "enum": [ + "loadModule" + ], "type": "string" }, "params": { @@ -1698,7 +1973,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "PipetteNameType": { "title": "PipetteNameType", @@ -1761,7 +2038,10 @@ "type": "boolean" } }, - "required": ["pipetteName", "mount"] + "required": [ + "pipetteName", + "mount" + ] }, "LoadPipetteCreate": { "title": "LoadPipetteCreate", @@ -1771,7 +2051,9 @@ "commandType": { "title": "Commandtype", "default": "loadPipette", - "enum": ["loadPipette"], + "enum": [ + "loadPipette" + ], "type": "string" }, "params": { @@ -1791,12 +2073,18 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "LabwareMovementStrategy": { "title": "LabwareMovementStrategy", "description": "Strategy to use for labware movement.", - "enum": ["usingGripper", "manualMoveWithPause", "manualMoveWithoutPause"], + "enum": [ + "usingGripper", + "manualMoveWithPause", + "manualMoveWithoutPause" + ], "type": "string" }, "LabwareOffsetVector": { @@ -1817,7 +2105,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "MoveLabwareParams": { "title": "MoveLabwareParams", @@ -1843,7 +2135,9 @@ "$ref": "#/definitions/OnLabwareLocation" }, { - "enum": ["offDeck"], + "enum": [ + "offDeck" + ], "type": "string" }, { @@ -1878,7 +2172,11 @@ ] } }, - "required": ["labwareId", "newLocation", "strategy"] + "required": [ + "labwareId", + "newLocation", + "strategy" + ] }, "MoveLabwareCreate": { "title": "MoveLabwareCreate", @@ -1888,7 +2186,9 @@ "commandType": { "title": "Commandtype", "default": "moveLabware", - "enum": ["moveLabware"], + "enum": [ + "moveLabware" + ], "type": "string" }, "params": { @@ -1908,12 +2208,18 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MovementAxis": { "title": "MovementAxis", "description": "Axis on which to issue a relative movement.", - "enum": ["x", "y", "z"], + "enum": [ + "x", + "y", + "z" + ], "type": "string" }, "MoveRelativeParams": { @@ -1940,7 +2246,11 @@ "type": "number" } }, - "required": ["pipetteId", "axis", "distance"] + "required": [ + "pipetteId", + "axis", + "distance" + ] }, "MoveRelativeCreate": { "title": "MoveRelativeCreate", @@ -1950,7 +2260,9 @@ "commandType": { "title": "Commandtype", "default": "moveRelative", - "enum": ["moveRelative"], + "enum": [ + "moveRelative" + ], "type": "string" }, "params": { @@ -1970,7 +2282,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeckPoint": { "title": "DeckPoint", @@ -1990,7 +2304,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "MoveToCoordinatesParams": { "title": "MoveToCoordinatesParams", @@ -2028,7 +2346,10 @@ ] } }, - "required": ["pipetteId", "coordinates"] + "required": [ + "pipetteId", + "coordinates" + ] }, "MoveToCoordinatesCreate": { "title": "MoveToCoordinatesCreate", @@ -2038,7 +2359,9 @@ "commandType": { "title": "Commandtype", "default": "moveToCoordinates", - "enum": ["moveToCoordinates"], + "enum": [ + "moveToCoordinates" + ], "type": "string" }, "params": { @@ -2058,7 +2381,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveToWellParams": { "title": "MoveToWellParams", @@ -2106,7 +2431,11 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "MoveToWellCreate": { "title": "MoveToWellCreate", @@ -2116,7 +2445,9 @@ "commandType": { "title": "Commandtype", "default": "moveToWell", - "enum": ["moveToWell"], + "enum": [ + "moveToWell" + ], "type": "string" }, "params": { @@ -2136,7 +2467,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "AddressableOffsetVector": { "title": "AddressableOffsetVector", @@ -2156,7 +2489,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "MoveToAddressableAreaParams": { "title": "MoveToAddressableAreaParams", @@ -2210,7 +2547,10 @@ "type": "boolean" } }, - "required": ["pipetteId", "addressableAreaName"] + "required": [ + "pipetteId", + "addressableAreaName" + ] }, "MoveToAddressableAreaCreate": { "title": "MoveToAddressableAreaCreate", @@ -2220,7 +2560,9 @@ "commandType": { "title": "Commandtype", "default": "moveToAddressableArea", - "enum": ["moveToAddressableArea"], + "enum": [ + "moveToAddressableArea" + ], "type": "string" }, "params": { @@ -2240,7 +2582,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveToAddressableAreaForDropTipParams": { "title": "MoveToAddressableAreaForDropTipParams", @@ -2300,7 +2644,10 @@ "type": "boolean" } }, - "required": ["pipetteId", "addressableAreaName"] + "required": [ + "pipetteId", + "addressableAreaName" + ] }, "MoveToAddressableAreaForDropTipCreate": { "title": "MoveToAddressableAreaForDropTipCreate", @@ -2310,7 +2657,9 @@ "commandType": { "title": "Commandtype", "default": "moveToAddressableAreaForDropTip", - "enum": ["moveToAddressableAreaForDropTip"], + "enum": [ + "moveToAddressableAreaForDropTip" + ], "type": "string" }, "params": { @@ -2330,7 +2679,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "PrepareToAspirateParams": { "title": "PrepareToAspirateParams", @@ -2343,7 +2694,9 @@ "type": "string" } }, - "required": ["pipetteId"] + "required": [ + "pipetteId" + ] }, "PrepareToAspirateCreate": { "title": "PrepareToAspirateCreate", @@ -2353,7 +2706,9 @@ "commandType": { "title": "Commandtype", "default": "prepareToAspirate", - "enum": ["prepareToAspirate"], + "enum": [ + "prepareToAspirate" + ], "type": "string" }, "params": { @@ -2373,7 +2728,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForResumeParams": { "title": "WaitForResumeParams", @@ -2395,7 +2752,10 @@ "commandType": { "title": "Commandtype", "default": "waitForResume", - "enum": ["waitForResume", "pause"], + "enum": [ + "waitForResume", + "pause" + ], "type": "string" }, "params": { @@ -2415,7 +2775,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForDurationParams": { "title": "WaitForDurationParams", @@ -2433,7 +2795,9 @@ "type": "string" } }, - "required": ["seconds"] + "required": [ + "seconds" + ] }, "WaitForDurationCreate": { "title": "WaitForDurationCreate", @@ -2443,7 +2807,9 @@ "commandType": { "title": "Commandtype", "default": "waitForDuration", - "enum": ["waitForDuration"], + "enum": [ + "waitForDuration" + ], "type": "string" }, "params": { @@ -2463,12 +2829,18 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "PickUpTipWellOrigin": { "title": "PickUpTipWellOrigin", "description": "The origin of a PickUpTipWellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well", - "enum": ["top", "bottom", "center"], + "enum": [ + "top", + "bottom", + "center" + ], "type": "string" }, "PickUpTipWellLocation": { @@ -2519,7 +2891,11 @@ ] } }, - "required": ["pipetteId", "labwareId", "wellName"] + "required": [ + "pipetteId", + "labwareId", + "wellName" + ] }, "PickUpTipCreate": { "title": "PickUpTipCreate", @@ -2529,7 +2905,9 @@ "commandType": { "title": "Commandtype", "default": "pickUpTip", - "enum": ["pickUpTip"], + "enum": [ + "pickUpTip" + ], "type": "string" }, "params": { @@ -2549,7 +2927,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SavePositionParams": { "title": "SavePositionParams", @@ -2573,7 +2953,9 @@ "type": "boolean" } }, - "required": ["pipetteId"] + "required": [ + "pipetteId" + ] }, "SavePositionCreate": { "title": "SavePositionCreate", @@ -2583,7 +2965,9 @@ "commandType": { "title": "Commandtype", "default": "savePosition", - "enum": ["savePosition"], + "enum": [ + "savePosition" + ], "type": "string" }, "params": { @@ -2603,7 +2987,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetRailLightsParams": { "title": "SetRailLightsParams", @@ -2616,7 +3002,9 @@ "type": "boolean" } }, - "required": ["on"] + "required": [ + "on" + ] }, "SetRailLightsCreate": { "title": "SetRailLightsCreate", @@ -2626,7 +3014,9 @@ "commandType": { "title": "Commandtype", "default": "setRailLights", - "enum": ["setRailLights"], + "enum": [ + "setRailLights" + ], "type": "string" }, "params": { @@ -2646,7 +3036,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "TouchTipParams": { "title": "TouchTipParams", @@ -2689,7 +3081,11 @@ "type": "number" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "TouchTipCreate": { "title": "TouchTipCreate", @@ -2699,7 +3095,9 @@ "commandType": { "title": "Commandtype", "default": "touchTip", - "enum": ["touchTip"], + "enum": [ + "touchTip" + ], "type": "string" }, "params": { @@ -2719,12 +3117,20 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "StatusBarAnimation": { "title": "StatusBarAnimation", "description": "Status Bar animation options.", - "enum": ["idle", "confirm", "updating", "disco", "off"] + "enum": [ + "idle", + "confirm", + "updating", + "disco", + "off" + ] }, "SetStatusBarParams": { "title": "SetStatusBarParams", @@ -2740,7 +3146,9 @@ ] } }, - "required": ["animation"] + "required": [ + "animation" + ] }, "SetStatusBarCreate": { "title": "SetStatusBarCreate", @@ -2750,7 +3158,9 @@ "commandType": { "title": "Commandtype", "default": "setStatusBar", - "enum": ["setStatusBar"], + "enum": [ + "setStatusBar" + ], "type": "string" }, "params": { @@ -2770,18 +3180,28 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "TipPresenceStatus": { "title": "TipPresenceStatus", "description": "Tip presence status reported by a pipette.", - "enum": ["present", "absent", "unknown"], + "enum": [ + "present", + "absent", + "unknown" + ], "type": "string" }, "InstrumentSensorId": { "title": "InstrumentSensorId", "description": "Primary and secondary sensor ids.", - "enum": ["primary", "secondary", "both"], + "enum": [ + "primary", + "secondary", + "both" + ], "type": "string" }, "VerifyTipPresenceParams": { @@ -2811,7 +3231,10 @@ ] } }, - "required": ["pipetteId", "expectedState"] + "required": [ + "pipetteId", + "expectedState" + ] }, "VerifyTipPresenceCreate": { "title": "VerifyTipPresenceCreate", @@ -2821,7 +3244,9 @@ "commandType": { "title": "Commandtype", "default": "verifyTipPresence", - "enum": ["verifyTipPresence"], + "enum": [ + "verifyTipPresence" + ], "type": "string" }, "params": { @@ -2841,7 +3266,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "GetTipPresenceParams": { "title": "GetTipPresenceParams", @@ -2854,7 +3281,9 @@ "type": "string" } }, - "required": ["pipetteId"] + "required": [ + "pipetteId" + ] }, "GetTipPresenceCreate": { "title": "GetTipPresenceCreate", @@ -2864,7 +3293,9 @@ "commandType": { "title": "Commandtype", "default": "getTipPresence", - "enum": ["getTipPresence"], + "enum": [ + "getTipPresence" + ], "type": "string" }, "params": { @@ -2884,7 +3315,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "LiquidProbeParams": { "title": "LiquidProbeParams", @@ -2916,7 +3349,11 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "LiquidProbeCreate": { "title": "LiquidProbeCreate", @@ -2926,7 +3363,9 @@ "commandType": { "title": "Commandtype", "default": "liquidProbe", - "enum": ["liquidProbe"], + "enum": [ + "liquidProbe" + ], "type": "string" }, "params": { @@ -2946,7 +3385,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "TryLiquidProbeParams": { "title": "TryLiquidProbeParams", @@ -2978,7 +3419,11 @@ "type": "string" } }, - "required": ["labwareId", "wellName", "pipetteId"] + "required": [ + "labwareId", + "wellName", + "pipetteId" + ] }, "TryLiquidProbeCreate": { "title": "TryLiquidProbeCreate", @@ -2988,7 +3433,9 @@ "commandType": { "title": "Commandtype", "default": "tryLiquidProbe", - "enum": ["tryLiquidProbe"], + "enum": [ + "tryLiquidProbe" + ], "type": "string" }, "params": { @@ -3008,7 +3455,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureParams": { "title": "WaitForTemperatureParams", @@ -3026,7 +3475,9 @@ "type": "number" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__heater_shaker__wait_for_temperature__WaitForTemperatureCreate": { "title": "WaitForTemperatureCreate", @@ -3036,7 +3487,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/waitForTemperature", - "enum": ["heaterShaker/waitForTemperature"], + "enum": [ + "heaterShaker/waitForTemperature" + ], "type": "string" }, "params": { @@ -3056,7 +3509,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureParams": { "title": "SetTargetTemperatureParams", @@ -3074,7 +3529,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "opentrons__protocol_engine__commands__heater_shaker__set_target_temperature__SetTargetTemperatureCreate": { "title": "SetTargetTemperatureCreate", @@ -3084,7 +3542,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/setTargetTemperature", - "enum": ["heaterShaker/setTargetTemperature"], + "enum": [ + "heaterShaker/setTargetTemperature" + ], "type": "string" }, "params": { @@ -3104,7 +3564,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateHeaterParams": { "title": "DeactivateHeaterParams", @@ -3117,7 +3579,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateHeaterCreate": { "title": "DeactivateHeaterCreate", @@ -3127,7 +3591,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/deactivateHeater", - "enum": ["heaterShaker/deactivateHeater"], + "enum": [ + "heaterShaker/deactivateHeater" + ], "type": "string" }, "params": { @@ -3147,7 +3613,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetAndWaitForShakeSpeedParams": { "title": "SetAndWaitForShakeSpeedParams", @@ -3165,7 +3633,10 @@ "type": "number" } }, - "required": ["moduleId", "rpm"] + "required": [ + "moduleId", + "rpm" + ] }, "SetAndWaitForShakeSpeedCreate": { "title": "SetAndWaitForShakeSpeedCreate", @@ -3175,7 +3646,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/setAndWaitForShakeSpeed", - "enum": ["heaterShaker/setAndWaitForShakeSpeed"], + "enum": [ + "heaterShaker/setAndWaitForShakeSpeed" + ], "type": "string" }, "params": { @@ -3195,7 +3668,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateShakerParams": { "title": "DeactivateShakerParams", @@ -3208,7 +3683,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateShakerCreate": { "title": "DeactivateShakerCreate", @@ -3218,7 +3695,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/deactivateShaker", - "enum": ["heaterShaker/deactivateShaker"], + "enum": [ + "heaterShaker/deactivateShaker" + ], "type": "string" }, "params": { @@ -3238,7 +3717,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "OpenLabwareLatchParams": { "title": "OpenLabwareLatchParams", @@ -3251,7 +3732,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "OpenLabwareLatchCreate": { "title": "OpenLabwareLatchCreate", @@ -3261,7 +3744,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/openLabwareLatch", - "enum": ["heaterShaker/openLabwareLatch"], + "enum": [ + "heaterShaker/openLabwareLatch" + ], "type": "string" }, "params": { @@ -3281,7 +3766,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CloseLabwareLatchParams": { "title": "CloseLabwareLatchParams", @@ -3294,7 +3781,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "CloseLabwareLatchCreate": { "title": "CloseLabwareLatchCreate", @@ -3304,7 +3793,9 @@ "commandType": { "title": "Commandtype", "default": "heaterShaker/closeLabwareLatch", - "enum": ["heaterShaker/closeLabwareLatch"], + "enum": [ + "heaterShaker/closeLabwareLatch" + ], "type": "string" }, "params": { @@ -3324,7 +3815,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DisengageParams": { "title": "DisengageParams", @@ -3337,7 +3830,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DisengageCreate": { "title": "DisengageCreate", @@ -3347,7 +3842,9 @@ "commandType": { "title": "Commandtype", "default": "magneticModule/disengage", - "enum": ["magneticModule/disengage"], + "enum": [ + "magneticModule/disengage" + ], "type": "string" }, "params": { @@ -3367,7 +3864,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "EngageParams": { "title": "EngageParams", @@ -3385,7 +3884,10 @@ "type": "number" } }, - "required": ["moduleId", "height"] + "required": [ + "moduleId", + "height" + ] }, "EngageCreate": { "title": "EngageCreate", @@ -3395,7 +3897,9 @@ "commandType": { "title": "Commandtype", "default": "magneticModule/engage", - "enum": ["magneticModule/engage"], + "enum": [ + "magneticModule/engage" + ], "type": "string" }, "params": { @@ -3415,7 +3919,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureParams": { "title": "SetTargetTemperatureParams", @@ -3433,7 +3939,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "opentrons__protocol_engine__commands__temperature_module__set_target_temperature__SetTargetTemperatureCreate": { "title": "SetTargetTemperatureCreate", @@ -3443,7 +3952,9 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/setTargetTemperature", - "enum": ["temperatureModule/setTargetTemperature"], + "enum": [ + "temperatureModule/setTargetTemperature" + ], "type": "string" }, "params": { @@ -3463,7 +3974,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureParams": { "title": "WaitForTemperatureParams", @@ -3481,7 +3994,9 @@ "type": "number" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__temperature_module__wait_for_temperature__WaitForTemperatureCreate": { "title": "WaitForTemperatureCreate", @@ -3491,7 +4006,9 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/waitForTemperature", - "enum": ["temperatureModule/waitForTemperature"], + "enum": [ + "temperatureModule/waitForTemperature" + ], "type": "string" }, "params": { @@ -3511,7 +4028,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateTemperatureParams": { "title": "DeactivateTemperatureParams", @@ -3524,7 +4043,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateTemperatureCreate": { "title": "DeactivateTemperatureCreate", @@ -3534,7 +4055,9 @@ "commandType": { "title": "Commandtype", "default": "temperatureModule/deactivate", - "enum": ["temperatureModule/deactivate"], + "enum": [ + "temperatureModule/deactivate" + ], "type": "string" }, "params": { @@ -3554,7 +4077,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetTargetBlockTemperatureParams": { "title": "SetTargetBlockTemperatureParams", @@ -3582,7 +4107,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "SetTargetBlockTemperatureCreate": { "title": "SetTargetBlockTemperatureCreate", @@ -3592,7 +4120,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/setTargetBlockTemperature", - "enum": ["thermocycler/setTargetBlockTemperature"], + "enum": [ + "thermocycler/setTargetBlockTemperature" + ], "type": "string" }, "params": { @@ -3612,7 +4142,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForBlockTemperatureParams": { "title": "WaitForBlockTemperatureParams", @@ -3625,7 +4157,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "WaitForBlockTemperatureCreate": { "title": "WaitForBlockTemperatureCreate", @@ -3635,7 +4169,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/waitForBlockTemperature", - "enum": ["thermocycler/waitForBlockTemperature"], + "enum": [ + "thermocycler/waitForBlockTemperature" + ], "type": "string" }, "params": { @@ -3655,7 +4191,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "SetTargetLidTemperatureParams": { "title": "SetTargetLidTemperatureParams", @@ -3673,7 +4211,10 @@ "type": "number" } }, - "required": ["moduleId", "celsius"] + "required": [ + "moduleId", + "celsius" + ] }, "SetTargetLidTemperatureCreate": { "title": "SetTargetLidTemperatureCreate", @@ -3683,7 +4224,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/setTargetLidTemperature", - "enum": ["thermocycler/setTargetLidTemperature"], + "enum": [ + "thermocycler/setTargetLidTemperature" + ], "type": "string" }, "params": { @@ -3703,7 +4246,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "WaitForLidTemperatureParams": { "title": "WaitForLidTemperatureParams", @@ -3716,7 +4261,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "WaitForLidTemperatureCreate": { "title": "WaitForLidTemperatureCreate", @@ -3726,7 +4273,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/waitForLidTemperature", - "enum": ["thermocycler/waitForLidTemperature"], + "enum": [ + "thermocycler/waitForLidTemperature" + ], "type": "string" }, "params": { @@ -3746,7 +4295,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateBlockParams": { "title": "DeactivateBlockParams", @@ -3759,7 +4310,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateBlockCreate": { "title": "DeactivateBlockCreate", @@ -3769,7 +4322,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/deactivateBlock", - "enum": ["thermocycler/deactivateBlock"], + "enum": [ + "thermocycler/deactivateBlock" + ], "type": "string" }, "params": { @@ -3789,7 +4344,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "DeactivateLidParams": { "title": "DeactivateLidParams", @@ -3802,7 +4359,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "DeactivateLidCreate": { "title": "DeactivateLidCreate", @@ -3812,7 +4371,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/deactivateLid", - "enum": ["thermocycler/deactivateLid"], + "enum": [ + "thermocycler/deactivateLid" + ], "type": "string" }, "params": { @@ -3832,7 +4393,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidParams": { "title": "OpenLidParams", @@ -3845,7 +4408,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__thermocycler__open_lid__OpenLidCreate": { "title": "OpenLidCreate", @@ -3855,7 +4420,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/openLid", - "enum": ["thermocycler/openLid"], + "enum": [ + "thermocycler/openLid" + ], "type": "string" }, "params": { @@ -3875,7 +4442,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidParams": { "title": "CloseLidParams", @@ -3888,7 +4457,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__thermocycler__close_lid__CloseLidCreate": { "title": "CloseLidCreate", @@ -3898,7 +4469,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/closeLid", - "enum": ["thermocycler/closeLid"], + "enum": [ + "thermocycler/closeLid" + ], "type": "string" }, "params": { @@ -3918,7 +4491,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "RunProfileStepParams": { "title": "RunProfileStepParams", @@ -3936,7 +4511,10 @@ "type": "number" } }, - "required": ["celsius", "holdSeconds"] + "required": [ + "celsius", + "holdSeconds" + ] }, "RunProfileParams": { "title": "RunProfileParams", @@ -3962,7 +4540,10 @@ "type": "number" } }, - "required": ["moduleId", "profile"] + "required": [ + "moduleId", + "profile" + ] }, "RunProfileCreate": { "title": "RunProfileCreate", @@ -3972,7 +4553,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/runProfile", - "enum": ["thermocycler/runProfile"], + "enum": [ + "thermocycler/runProfile" + ], "type": "string" }, "params": { @@ -3992,7 +4575,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "ProfileStep": { "title": "ProfileStep", @@ -4010,7 +4595,10 @@ "type": "number" } }, - "required": ["celsius", "holdSeconds"] + "required": [ + "celsius", + "holdSeconds" + ] }, "ProfileCycle": { "title": "ProfileCycle", @@ -4031,7 +4619,10 @@ "type": "integer" } }, - "required": ["steps", "repetitions"] + "required": [ + "steps", + "repetitions" + ] }, "RunExtendedProfileParams": { "title": "RunExtendedProfileParams", @@ -4064,7 +4655,10 @@ "type": "number" } }, - "required": ["moduleId", "profileElements"] + "required": [ + "moduleId", + "profileElements" + ] }, "RunExtendedProfileCreate": { "title": "RunExtendedProfileCreate", @@ -4074,7 +4668,9 @@ "commandType": { "title": "Commandtype", "default": "thermocycler/runExtendedProfile", - "enum": ["thermocycler/runExtendedProfile"], + "enum": [ + "thermocycler/runExtendedProfile" + ], "type": "string" }, "params": { @@ -4094,7 +4690,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidParams": { "title": "CloseLidParams", @@ -4107,7 +4705,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__absorbance_reader__close_lid__CloseLidCreate": { "title": "CloseLidCreate", @@ -4117,7 +4717,9 @@ "commandType": { "title": "Commandtype", "default": "absorbanceReader/closeLid", - "enum": ["absorbanceReader/closeLid"], + "enum": [ + "absorbanceReader/closeLid" + ], "type": "string" }, "params": { @@ -4137,7 +4739,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidParams": { "title": "OpenLidParams", @@ -4150,7 +4754,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "opentrons__protocol_engine__commands__absorbance_reader__open_lid__OpenLidCreate": { "title": "OpenLidCreate", @@ -4160,7 +4766,9 @@ "commandType": { "title": "Commandtype", "default": "absorbanceReader/openLid", - "enum": ["absorbanceReader/openLid"], + "enum": [ + "absorbanceReader/openLid" + ], "type": "string" }, "params": { @@ -4180,7 +4788,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "InitializeParams": { "title": "InitializeParams", @@ -4195,7 +4805,10 @@ "measureMode": { "title": "Measuremode", "description": "Initialize single or multi measurement mode.", - "enum": ["single", "multi"], + "enum": [ + "single", + "multi" + ], "type": "string" }, "sampleWavelengths": { @@ -4212,7 +4825,11 @@ "type": "integer" } }, - "required": ["moduleId", "measureMode", "sampleWavelengths"] + "required": [ + "moduleId", + "measureMode", + "sampleWavelengths" + ] }, "InitializeCreate": { "title": "InitializeCreate", @@ -4222,7 +4839,9 @@ "commandType": { "title": "Commandtype", "default": "absorbanceReader/initialize", - "enum": ["absorbanceReader/initialize"], + "enum": [ + "absorbanceReader/initialize" + ], "type": "string" }, "params": { @@ -4242,7 +4861,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "ReadAbsorbanceParams": { "title": "ReadAbsorbanceParams", @@ -4260,7 +4881,9 @@ "type": "string" } }, - "required": ["moduleId"] + "required": [ + "moduleId" + ] }, "ReadAbsorbanceCreate": { "title": "ReadAbsorbanceCreate", @@ -4270,7 +4893,9 @@ "commandType": { "title": "Commandtype", "default": "absorbanceReader/read", - "enum": ["absorbanceReader/read"], + "enum": [ + "absorbanceReader/read" + ], "type": "string" }, "params": { @@ -4290,12 +4915,17 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CalibrateGripperParamsJaw": { "title": "CalibrateGripperParamsJaw", "description": "An enumeration.", - "enum": ["front", "rear"] + "enum": [ + "front", + "rear" + ] }, "Vec3f": { "title": "Vec3f", @@ -4315,7 +4945,11 @@ "type": "number" } }, - "required": ["x", "y", "z"] + "required": [ + "x", + "y", + "z" + ] }, "CalibrateGripperParams": { "title": "CalibrateGripperParams", @@ -4340,7 +4974,9 @@ ] } }, - "required": ["jaw"] + "required": [ + "jaw" + ] }, "CalibrateGripperCreate": { "title": "CalibrateGripperCreate", @@ -4350,7 +4986,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibrateGripper", - "enum": ["calibration/calibrateGripper"], + "enum": [ + "calibration/calibrateGripper" + ], "type": "string" }, "params": { @@ -4370,7 +5008,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CalibratePipetteParams": { "title": "CalibratePipetteParams", @@ -4386,7 +5026,9 @@ ] } }, - "required": ["mount"] + "required": [ + "mount" + ] }, "CalibratePipetteCreate": { "title": "CalibratePipetteCreate", @@ -4396,7 +5038,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibratePipette", - "enum": ["calibration/calibratePipette"], + "enum": [ + "calibration/calibratePipette" + ], "type": "string" }, "params": { @@ -4416,7 +5060,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "CalibrateModuleParams": { "title": "CalibrateModuleParams", @@ -4442,7 +5088,11 @@ ] } }, - "required": ["moduleId", "labwareId", "mount"] + "required": [ + "moduleId", + "labwareId", + "mount" + ] }, "CalibrateModuleCreate": { "title": "CalibrateModuleCreate", @@ -4452,7 +5102,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/calibrateModule", - "enum": ["calibration/calibrateModule"], + "enum": [ + "calibration/calibrateModule" + ], "type": "string" }, "params": { @@ -4472,12 +5124,17 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MaintenancePosition": { "title": "MaintenancePosition", "description": "Maintenance position options.", - "enum": ["attachPlate", "attachInstrument"] + "enum": [ + "attachPlate", + "attachInstrument" + ] }, "MoveToMaintenancePositionParams": { "title": "MoveToMaintenancePositionParams", @@ -4502,7 +5159,9 @@ ] } }, - "required": ["mount"] + "required": [ + "mount" + ] }, "MoveToMaintenancePositionCreate": { "title": "MoveToMaintenancePositionCreate", @@ -4512,7 +5171,9 @@ "commandType": { "title": "Commandtype", "default": "calibration/moveToMaintenancePosition", - "enum": ["calibration/moveToMaintenancePosition"], + "enum": [ + "calibration/moveToMaintenancePosition" + ], "type": "string" }, "params": { @@ -4532,7 +5193,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "UnsafeBlowOutInPlaceParams": { "title": "UnsafeBlowOutInPlaceParams", @@ -4551,7 +5214,10 @@ "type": "string" } }, - "required": ["flowRate", "pipetteId"] + "required": [ + "flowRate", + "pipetteId" + ] }, "UnsafeBlowOutInPlaceCreate": { "title": "UnsafeBlowOutInPlaceCreate", @@ -4561,7 +5227,9 @@ "commandType": { "title": "Commandtype", "default": "unsafe/blowOutInPlace", - "enum": ["unsafe/blowOutInPlace"], + "enum": [ + "unsafe/blowOutInPlace" + ], "type": "string" }, "params": { @@ -4581,7 +5249,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "UnsafeDropTipInPlaceParams": { "title": "UnsafeDropTipInPlaceParams", @@ -4599,7 +5269,9 @@ "type": "boolean" } }, - "required": ["pipetteId"] + "required": [ + "pipetteId" + ] }, "UnsafeDropTipInPlaceCreate": { "title": "UnsafeDropTipInPlaceCreate", @@ -4609,7 +5281,9 @@ "commandType": { "title": "Commandtype", "default": "unsafe/dropTipInPlace", - "enum": ["unsafe/dropTipInPlace"], + "enum": [ + "unsafe/dropTipInPlace" + ], "type": "string" }, "params": { @@ -4629,7 +5303,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "UpdatePositionEstimatorsParams": { "title": "UpdatePositionEstimatorsParams", @@ -4644,7 +5320,9 @@ } } }, - "required": ["axes"] + "required": [ + "axes" + ] }, "UpdatePositionEstimatorsCreate": { "title": "UpdatePositionEstimatorsCreate", @@ -4654,7 +5332,9 @@ "commandType": { "title": "Commandtype", "default": "unsafe/updatePositionEstimators", - "enum": ["unsafe/updatePositionEstimators"], + "enum": [ + "unsafe/updatePositionEstimators" + ], "type": "string" }, "params": { @@ -4674,7 +5354,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "UnsafeEngageAxesParams": { "title": "UnsafeEngageAxesParams", @@ -4689,7 +5371,9 @@ } } }, - "required": ["axes"] + "required": [ + "axes" + ] }, "UnsafeEngageAxesCreate": { "title": "UnsafeEngageAxesCreate", @@ -4699,7 +5383,9 @@ "commandType": { "title": "Commandtype", "default": "unsafe/engageAxes", - "enum": ["unsafe/engageAxes"], + "enum": [ + "unsafe/engageAxes" + ], "type": "string" }, "params": { @@ -4719,7 +5405,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "UnsafeUngripLabwareParams": { "title": "UnsafeUngripLabwareParams", @@ -4735,7 +5423,9 @@ "commandType": { "title": "Commandtype", "default": "unsafe/ungripLabware", - "enum": ["unsafe/ungripLabware"], + "enum": [ + "unsafe/ungripLabware" + ], "type": "string" }, "params": { @@ -4755,7 +5445,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "UnsafePlaceLabwareParams": { "title": "UnsafePlaceLabwareParams", @@ -4786,7 +5478,10 @@ ] } }, - "required": ["labwareId", "location"] + "required": [ + "labwareId", + "location" + ] }, "UnsafePlaceLabwareCreate": { "title": "UnsafePlaceLabwareCreate", @@ -4796,7 +5491,9 @@ "commandType": { "title": "Commandtype", "default": "unsafe/placeLabware", - "enum": ["unsafe/placeLabware"], + "enum": [ + "unsafe/placeLabware" + ], "type": "string" }, "params": { @@ -4816,7 +5513,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveAxesRelativeParams": { "title": "MoveAxesRelativeParams", @@ -4837,7 +5536,9 @@ "type": "number" } }, - "required": ["axis_map"] + "required": [ + "axis_map" + ] }, "MoveAxesRelativeCreate": { "title": "MoveAxesRelativeCreate", @@ -4847,7 +5548,9 @@ "commandType": { "title": "Commandtype", "default": "robot/moveAxesRelative", - "enum": ["robot/moveAxesRelative"], + "enum": [ + "robot/moveAxesRelative" + ], "type": "string" }, "params": { @@ -4867,7 +5570,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveAxesToParams": { "title": "MoveAxesToParams", @@ -4896,7 +5601,9 @@ "type": "number" } }, - "required": ["axis_map"] + "required": [ + "axis_map" + ] }, "MoveAxesToCreate": { "title": "MoveAxesToCreate", @@ -4906,7 +5613,9 @@ "commandType": { "title": "Commandtype", "default": "robot/moveAxesTo", - "enum": ["robot/moveAxesTo"], + "enum": [ + "robot/moveAxesTo" + ], "type": "string" }, "params": { @@ -4926,7 +5635,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] }, "MoveToParams": { "title": "MoveToParams", @@ -4956,7 +5667,10 @@ "type": "number" } }, - "required": ["mount", "destination"] + "required": [ + "mount", + "destination" + ] }, "MoveToCreate": { "title": "MoveToCreate", @@ -4966,7 +5680,9 @@ "commandType": { "title": "Commandtype", "default": "robot/moveTo", - "enum": ["robot/moveTo"], + "enum": [ + "robot/moveTo" + ], "type": "string" }, "params": { @@ -4986,7 +5702,9 @@ "type": "string" } }, - "required": ["params"] + "required": [ + "params" + ] } }, "$id": "opentronsCommandSchemaV11", From 6eeede9debfcd56ee8133e5d56864a3909009f01 Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Fri, 8 Nov 2024 23:28:49 +0200 Subject: [PATCH 45/46] fixup from last rebase --- api/src/opentrons/types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/opentrons/types.py b/api/src/opentrons/types.py index 0a07e6c0e2f..fa57ce0dcd5 100644 --- a/api/src/opentrons/types.py +++ b/api/src/opentrons/types.py @@ -10,6 +10,7 @@ List, Optional, Protocol, + Dict, ) from opentrons_shared_data.robot.types import RobotType From 1fb5978e94ec0b71de8ddbeb9e21876d3d0e6a8d Mon Sep 17 00:00:00 2001 From: Laura Cox Date: Fri, 8 Nov 2024 23:36:52 +0200 Subject: [PATCH 46/46] more rebase fixes --- .../hardware_control/move_group_runner.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/hardware/opentrons_hardware/hardware_control/move_group_runner.py b/hardware/opentrons_hardware/hardware_control/move_group_runner.py index 7d621bd312b..1b7baf61d6d 100644 --- a/hardware/opentrons_hardware/hardware_control/move_group_runner.py +++ b/hardware/opentrons_hardware/hardware_control/move_group_runner.py @@ -255,16 +255,6 @@ def all_moving_nodes(self) -> Set[NodeId]: node_set.add(node) return node_set - def all_moving_nodes(self) -> Set[NodeId]: - """Get all of the moving nodes in the move group runner's move groups.""" - node_set: Set[NodeId] = set() - for group in self._move_groups: - for sequence in group: - for node, node_step in sequence.items(): - if node_step.is_moving_step(): - node_set.add(node) - return node_set - async def _send_groups(self, can_messenger: CanMessenger) -> None: """Send commands to set up the message groups.""" for group_i, group in enumerate(self._move_groups):