diff --git a/.github/workflows/robot-server-lint-test.yaml b/.github/workflows/robot-server-lint-test.yaml index 98e2d0174cd..d199b09aaa2 100644 --- a/.github/workflows/robot-server-lint-test.yaml +++ b/.github/workflows/robot-server-lint-test.yaml @@ -52,7 +52,7 @@ defaults: jobs: lint-test: name: 'robot server package linting and tests' - timeout-minutes: 20 + timeout-minutes: 40 runs-on: 'ubuntu-22.04' strategy: matrix: diff --git a/api-client/src/maintenance_runs/createMaintenanceCommand.ts b/api-client/src/maintenance_runs/createMaintenanceCommand.ts index 833290ce7c5..2d48e58d2c7 100644 --- a/api-client/src/maintenance_runs/createMaintenanceCommand.ts +++ b/api-client/src/maintenance_runs/createMaintenanceCommand.ts @@ -1,6 +1,6 @@ import { POST, request } from '../request' -import type { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' +import type { CreateCommand } from '@opentrons/shared-data' import type { ResponsePromise } from '../request' import type { HostConfig } from '../types' import type { CommandData, CreateCommandParams } from '../runs/types' diff --git a/api-client/src/protocols/utils.ts b/api-client/src/protocols/utils.ts index f14a028ef91..e8f19c42a7e 100644 --- a/api-client/src/protocols/utils.ts +++ b/api-client/src/protocols/utils.ts @@ -4,21 +4,19 @@ import reduce from 'lodash/reduce' import { COLORS } from '@opentrons/components/src/ui-style-constants' import { getLabwareDefURI } from '@opentrons/shared-data' import type { - ModuleModel, - PipetteName, Liquid, - LoadedPipette, LoadedLabware, LoadedModule, -} from '@opentrons/shared-data' -import type { RunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7' -import type { + LoadedPipette, + LoadFixtureRunTimeCommand, LoadLabwareRunTimeCommand, + LoadLiquidRunTimeCommand, LoadModuleRunTimeCommand, LoadPipetteRunTimeCommand, - LoadLiquidRunTimeCommand, - LoadFixtureRunTimeCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' + ModuleModel, + PipetteName, + RunTimeCommand, +} from '@opentrons/shared-data' interface PipetteNamesByMount { left: PipetteName | null diff --git a/api-client/src/runs/commands/createCommand.ts b/api-client/src/runs/commands/createCommand.ts index d5e5e74defa..4484c913b4f 100644 --- a/api-client/src/runs/commands/createCommand.ts +++ b/api-client/src/runs/commands/createCommand.ts @@ -1,10 +1,10 @@ import { POST, request } from '../../request' +import type { CreateCommand } from '@opentrons/shared-data' import type { ResponsePromise } from '../../request' import type { HostConfig } from '../../types' import type { CommandData } from '../types' import type { CreateCommandParams } from './types' -import type { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' export function createCommand( config: HostConfig, diff --git a/api-client/src/runs/commands/createLiveCommand.ts b/api-client/src/runs/commands/createLiveCommand.ts index 567f76d7c59..2638a2ec647 100644 --- a/api-client/src/runs/commands/createLiveCommand.ts +++ b/api-client/src/runs/commands/createLiveCommand.ts @@ -1,10 +1,10 @@ import { POST, request } from '../../request' +import type { CreateCommand } from '@opentrons/shared-data' import type { ResponsePromise } from '../../request' import type { HostConfig } from '../../types' import type { CommandData } from '../types' import type { CreateCommandParams } from './types' -import type { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' export function createLiveCommand( config: HostConfig, diff --git a/api/src/opentrons/calibration_storage/__init__.py b/api/src/opentrons/calibration_storage/__init__.py index 5daf55b660a..80b389223a8 100644 --- a/api/src/opentrons/calibration_storage/__init__.py +++ b/api/src/opentrons/calibration_storage/__init__.py @@ -1,6 +1,4 @@ -from opentrons import config - -from .ot3 import ot3_gripper_offset +from .ot3 import gripper_offset from .ot2 import mark_bad_calibration # TODO these functions are only used in robot server. We should think about moving them and/or @@ -23,47 +21,10 @@ delete_robot_deck_attitude, ) -if config.feature_flags.enable_ot3_hardware_controller(): - from .ot3.pipette_offset import ( - save_pipette_calibration, - clear_pipette_offset_calibrations, - get_pipette_offset, - delete_pipette_offset_file, - ) - from .ot3.tip_length import ( - clear_tip_length_calibration, - create_tip_length_data, - save_tip_length_calibration, - tip_lengths_for_pipette, - load_tip_length_calibration, - delete_tip_length_calibration, - ) - from .ot3 import models - from .ot3.module_offset import ( - save_module_calibration, - clear_module_offset_calibrations, - get_module_offset, - delete_module_offset_file, - load_all_module_offsets, - ) -else: - from .ot2.pipette_offset import ( - save_pipette_calibration, - clear_pipette_offset_calibrations, - get_pipette_offset, - delete_pipette_offset_file, - ) - from .ot2.tip_length import ( - clear_tip_length_calibration, - create_tip_length_data, - save_tip_length_calibration, - tip_lengths_for_pipette, - load_tip_length_calibration, - delete_tip_length_calibration, - ) - from .ot2 import models # type: ignore[no-redef] +from . import ot2, ot3, helpers __all__ = [ + "helpers", # deck calibration functions "save_robot_deck_attitude", "get_robot_deck_attitude", @@ -71,31 +32,15 @@ "save_robot_belt_attitude", "get_robot_belt_attitude", "delete_robot_belt_attitude", - # pipette calibration functions - "save_pipette_calibration", - "get_pipette_offset", - "clear_pipette_offset_calibrations", - "delete_pipette_offset_file", - # tip length calibration functions - "clear_tip_length_calibration", - "create_tip_length_data", - "save_tip_length_calibration", - "tip_lengths_for_pipette", - "delete_tip_length_calibration", - "load_tip_length_calibration", - # module calibration functions - "save_module_calibration", - "clear_module_offset_calibrations", - "get_module_offset", - "delete_module_offset_file", - "load_all_module_offsets", # functions only used in robot server "_save_custom_tiprack_definition", "get_custom_tiprack_definition_for_tlc", "get_all_pipette_offset_calibrations", "get_all_tip_length_calibrations", # file exports - "models", - "ot3_gripper_offset", + "gripper_offset", "mark_bad_calibration", + # Platform specific submodules + "ot2", + "ot3", ] diff --git a/api/src/opentrons/calibration_storage/ot2/__init__.py b/api/src/opentrons/calibration_storage/ot2/__init__.py index 504c84d0afd..821adc32bfa 100644 --- a/api/src/opentrons/calibration_storage/ot2/__init__.py +++ b/api/src/opentrons/calibration_storage/ot2/__init__.py @@ -1,10 +1,34 @@ from . import ( - models as ot2_models, + models, mark_bad_calibration, ) +from .pipette_offset import ( + save_pipette_calibration, + clear_pipette_offset_calibrations, + get_pipette_offset, + delete_pipette_offset_file, +) +from .tip_length import ( + clear_tip_length_calibration, + create_tip_length_data, + save_tip_length_calibration, + tip_lengths_for_pipette, + load_tip_length_calibration, + delete_tip_length_calibration, +) __all__ = [ - "ot2_models", + "models", "mark_bad_calibration", + "save_pipette_calibration", + "clear_pipette_offset_calibrations", + "get_pipette_offset", + "delete_pipette_offset_file", + "clear_tip_length_calibration", + "create_tip_length_data", + "save_tip_length_calibration", + "tip_lengths_for_pipette", + "load_tip_length_calibration", + "delete_tip_length_calibration", ] diff --git a/api/src/opentrons/calibration_storage/ot3/__init__.py b/api/src/opentrons/calibration_storage/ot3/__init__.py index 977ca8b9162..04806f5fe20 100644 --- a/api/src/opentrons/calibration_storage/ot3/__init__.py +++ b/api/src/opentrons/calibration_storage/ot3/__init__.py @@ -1,10 +1,31 @@ from . import ( - models as ot3_models, - gripper_offset as ot3_gripper_offset, + models, + gripper_offset as gripper_offset, +) +from .pipette_offset import ( + save_pipette_calibration, + clear_pipette_offset_calibrations, + get_pipette_offset, + delete_pipette_offset_file, +) +from .module_offset import ( + save_module_calibration, + clear_module_offset_calibrations, + get_module_offset, + delete_module_offset_file, + load_all_module_offsets, ) - __all__ = [ - "ot3_models", - "ot3_gripper_offset", + "models", + "gripper_offset", + "save_pipette_calibration", + "clear_pipette_offset_calibrations", + "get_pipette_offset", + "delete_pipette_offset_file", + "save_module_calibration", + "clear_module_offset_calibrations", + "get_module_offset", + "delete_module_offset_file", + "load_all_module_offsets", ] diff --git a/api/src/opentrons/calibration_storage/ot3/tip_length.py b/api/src/opentrons/calibration_storage/ot3/tip_length.py deleted file mode 100644 index 638560851aa..00000000000 --- a/api/src/opentrons/calibration_storage/ot3/tip_length.py +++ /dev/null @@ -1,181 +0,0 @@ -import json -import typing -import logging -from pydantic import ValidationError -from dataclasses import asdict - -from opentrons import config - -from .. import file_operators as io, helpers, types as local_types - -from opentrons.util.helpers import utc_now - - -from .models import v1 - -if typing.TYPE_CHECKING: - from opentrons_shared_data.labware.dev_types import LabwareDefinition - -log = logging.getLogger(__name__) -# Get Tip Length Calibration - - -def _conver_tip_length_model_to_dict( - to_dict: typing.Dict[str, v1.TipLengthModel] -) -> typing.Dict[str, typing.Any]: - # This is a workaround since pydantic doesn't have a nice way to - # add encoders when converting to a dict. - dict_of_tip_lengths = {} - for key, item in to_dict.items(): - dict_of_tip_lengths[key] = json.loads(item.json()) - return dict_of_tip_lengths - - -@typing.no_type_check -def tip_lengths_for_pipette( - pipette_id: str, -) -> typing.Dict[str, v1.TipLengthModel]: - tip_lengths = {} - try: - # While you technically could drop some data in for tip length calibration on the flex, - # it is not necessary and there is no UI frontend for it, so this code will mostly be - # taking the FileNotFoundError path. - tip_length_filepath = config.get_tip_length_cal_path() / f"{pipette_id}.json" - all_tip_lengths_for_pipette = io.read_cal_file(tip_length_filepath) - for tiprack, data in all_tip_lengths_for_pipette.items(): - try: - tip_lengths[tiprack] = v1.TipLengthModel(**data) - except (json.JSONDecodeError, ValidationError): - log.debug( - f"Tip length calibration is malformed for {tiprack} on {pipette_id}" - ) - pass - return tip_lengths - except FileNotFoundError: - # this is the overwhelmingly common case - return tip_lengths - - -@typing.no_type_check -def load_tip_length_calibration( - pip_id: str, definition: "LabwareDefinition" -) -> v1.TipLengthModel: - """ - Function used to grab the current tip length associated - with a particular tiprack. - - :param pip_id: pipette you are using - :param definition: full definition of the tiprack - """ - labware_hash = helpers.hash_labware_def(definition) - load_name = definition["parameters"]["loadName"] - try: - return tip_lengths_for_pipette(pip_id)[labware_hash] - except KeyError: - raise local_types.TipLengthCalNotFound( - f"Tip length of {load_name} has not been " - f"calibrated for this pipette: {pip_id} and cannot" - "be loaded" - ) - - -@typing.no_type_check -def create_tip_length_data( - definition: "LabwareDefinition", - length: float, - cal_status: typing.Optional[ - typing.Union[local_types.CalibrationStatus, v1.CalibrationStatus] - ] = None, -) -> typing.Dict[str, v1.TipLengthModel]: - """ - Function to correctly format tip length data. - - :param definition: full labware definition - :param length: the tip length to save - """ - labware_hash = helpers.hash_labware_def(definition) - labware_uri = helpers.uri_from_definition(definition) - - if isinstance(cal_status, local_types.CalibrationStatus): - cal_status_model = v1.CalibrationStatus(**asdict(cal_status)) - elif isinstance(cal_status, v1.CalibrationStatus): - cal_status_model = cal_status - else: - cal_status_model = v1.CalibrationStatus() - tip_length_data = v1.TipLengthModel( - tipLength=length, - lastModified=utc_now(), - source=local_types.SourceType.user, - status=cal_status_model, - uri=labware_uri, - ) - - data = {labware_hash: tip_length_data} - return data - - -# Delete Tip Length Calibration - - -@typing.no_type_check -def delete_tip_length_calibration(tiprack: str, pipette_id: str) -> None: - """ - Delete tip length calibration based on tiprack hash and - pipette serial number - - :param tiprack: tiprack hash - :param pipette: pipette serial number - """ - tip_lengths = tip_lengths_for_pipette(pipette_id) - if tiprack in tip_lengths: - # maybe make modify and delete same file? - del tip_lengths[tiprack] - tip_length_directory = config.get_tip_length_cal_path() - if tip_lengths: - dict_of_tip_lengths = _conver_tip_length_model_to_dict(tip_lengths) - io.save_to_file(tip_length_directory, pipette_id, dict_of_tip_lengths) - else: - io.delete_file(tip_length_directory / f"{pipette_id}.json") - else: - raise local_types.TipLengthCalNotFound( - f"Tip length for hash {tiprack} has not been " - f"calibrated for this pipette: {pipette_id} and cannot" - "be loaded" - ) - - -@typing.no_type_check -def clear_tip_length_calibration() -> None: - """ - Delete all tip length calibration files. - """ - offset_dir = config.get_tip_length_cal_path() - try: - io._remove_json_files_in_directories(offset_dir) - except FileNotFoundError: - pass - - -# Save Tip Length Calibration - - -@typing.no_type_check -def save_tip_length_calibration( - pip_id: str, - tip_length_cal: typing.Dict[str, v1.TipLengthModel], -) -> None: - """ - Function used to save tip length calibration to file. - - :param pip_id: pipette id to associate with this tip length - :param tip_length_cal: results of the data created using - :meth:`create_tip_length_data` - """ - tip_length_dir_path = config.get_tip_length_cal_path() - - all_tip_lengths = tip_lengths_for_pipette(pip_id) - - all_tip_lengths.update(tip_length_cal) - - dict_of_tip_lengths = _conver_tip_length_model_to_dict(all_tip_lengths) - io.save_to_file(tip_length_dir_path, pip_id, dict_of_tip_lengths) diff --git a/api/src/opentrons/config/defaults_ot3.py b/api/src/opentrons/config/defaults_ot3.py index 000d8b795cd..5ac94affe45 100644 --- a/api/src/opentrons/config/defaults_ot3.py +++ b/api/src/opentrons/config/defaults_ot3.py @@ -123,7 +123,7 @@ OT3AxisKind.Y: 10, OT3AxisKind.Z: 5, OT3AxisKind.P: 5, - OT3AxisKind.Z_G: 10, + OT3AxisKind.Z_G: 5, OT3AxisKind.Q: 5, }, low_throughput={ @@ -131,7 +131,7 @@ OT3AxisKind.Y: 10, OT3AxisKind.Z: 5, OT3AxisKind.P: 10, - OT3AxisKind.Z_G: 10, + OT3AxisKind.Z_G: 5, }, ) diff --git a/api/src/opentrons/config/reset.py b/api/src/opentrons/config/reset.py index 42a2b606106..2e71c69aa45 100644 --- a/api/src/opentrons/config/reset.py +++ b/api/src/opentrons/config/reset.py @@ -5,12 +5,13 @@ from pathlib import Path from typing import NamedTuple, Dict, Set +from opentrons_shared_data.robot.dev_types import RobotTypeEnum from opentrons.config import IS_ROBOT from opentrons.calibration_storage import ( delete_robot_deck_attitude, - clear_tip_length_calibration, - clear_pipette_offset_calibrations, - ot3_gripper_offset, + gripper_offset, + ot2, + ot3, ) @@ -102,9 +103,9 @@ class ResetOptionId(str, Enum): } -def reset_options(robot_type: str) -> Dict[ResetOptionId, CommonResetOption]: +def reset_options(robot_type: RobotTypeEnum) -> Dict[ResetOptionId, CommonResetOption]: reset_options_for_robot_type = ( - _OT_2_RESET_OPTIONS if robot_type == "OT-2 Standard" else _FLEX_RESET_OPTIONS + _OT_2_RESET_OPTIONS if robot_type is RobotTypeEnum.OT2 else _FLEX_RESET_OPTIONS ) return { key: _settings_reset_options[key] @@ -113,7 +114,7 @@ def reset_options(robot_type: str) -> Dict[ResetOptionId, CommonResetOption]: } -def reset(options: Set[ResetOptionId]) -> None: +def reset(options: Set[ResetOptionId], robot_type: RobotTypeEnum) -> None: """ Execute a reset of the requested parts of the user configuration. @@ -125,13 +126,13 @@ def reset(options: Set[ResetOptionId]) -> None: reset_boot_scripts() if ResetOptionId.deck_calibration in options: - reset_deck_calibration() + reset_deck_calibration(robot_type) if ResetOptionId.pipette_offset in options: - reset_pipette_offset() + reset_pipette_offset(robot_type) if ResetOptionId.tip_length_calibrations in options: - reset_tip_length_calibrations() + reset_tip_length_calibrations(robot_type) if ResetOptionId.gripper_offset in options: reset_gripper_offset() @@ -151,24 +152,25 @@ def reset_boot_scripts() -> None: log.debug(f"Not on pi, not removing {DATA_BOOT_D}") -# (lc 09-15-2022) Choosing to import both ot2 and ot3 delete modules -# rather than type ignore an import_module command using importlib. -def reset_deck_calibration() -> None: +def reset_deck_calibration(robot_type: RobotTypeEnum) -> None: delete_robot_deck_attitude() - clear_pipette_offset_calibrations() + ot2.clear_pipette_offset_calibrations() if robot_type is RobotTypeEnum.OT2 else ot3.clear_pipette_offset_calibrations() -def reset_pipette_offset() -> None: - clear_pipette_offset_calibrations() +def reset_pipette_offset(robot_type: RobotTypeEnum) -> None: + ot2.clear_pipette_offset_calibrations() if robot_type is RobotTypeEnum.OT2 else ot3.clear_pipette_offset_calibrations() def reset_gripper_offset() -> None: - ot3_gripper_offset.clear_gripper_calibration_offsets() + gripper_offset.clear_gripper_calibration_offsets() -def reset_tip_length_calibrations() -> None: - clear_tip_length_calibration() - clear_pipette_offset_calibrations() +def reset_tip_length_calibrations(robot_type: RobotTypeEnum) -> None: + if robot_type is RobotTypeEnum.OT2: + ot2.clear_tip_length_calibration() + ot2.clear_pipette_offset_calibrations() + else: + raise UnrecognizedOption("Tip Length calibration not supported on Flex") def reset_module_calibration() -> None: diff --git a/api/src/opentrons/execute.py b/api/src/opentrons/execute.py index 21242be00c4..bd450db8086 100644 --- a/api/src/opentrons/execute.py +++ b/api/src/opentrons/execute.py @@ -39,6 +39,8 @@ from opentrons.protocols import parse from opentrons.protocols.api_support.deck_type import ( guess_from_global_config as guess_deck_type_from_global_config, + should_load_fixed_trash, + should_load_fixed_trash_for_python_protocol, ) from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.execution import execute as execute_apiv2 @@ -536,6 +538,7 @@ def _create_live_context_pe( config=_get_protocol_engine_config(), drop_tips_after_run=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, + load_fixed_trash=should_load_fixed_trash_for_python_protocol(api_version), ) ) @@ -609,6 +612,7 @@ async def run(protocol_source: ProtocolSource) -> None: protocol_engine = await create_protocol_engine( hardware_api=hardware_api.wrapped(), config=_get_protocol_engine_config(), + load_fixed_trash=should_load_fixed_trash(protocol_source.config), ) protocol_runner = create_protocol_runner( diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 00afbd992f2..0019f9e9dec 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -47,6 +47,7 @@ moving_axes_in_move_group, gripper_jaw_state_from_fw, ) +from .tip_presence_manager import TipPresenceManager try: import aionotify # type: ignore[import] @@ -86,7 +87,6 @@ update_motor_position_estimation, ) from opentrons_hardware.hardware_control.limit_switches import get_limit_switches -from opentrons_hardware.hardware_control.tip_presence import get_tip_ejector_state from opentrons_hardware.hardware_control.current_settings import ( set_run_current, set_hold_current, @@ -127,7 +127,6 @@ SubSystemState, SubSystem, TipStateType, - FailedTipStateCheck, EstopState, GripperJawState, ) @@ -172,7 +171,6 @@ from opentrons_shared_data.errors.exceptions import ( EStopActivatedError, EStopNotPresentError, - UnmatchedTipPresenceStates, PipetteOverpressureError, FirmwareUpdateRequiredError, ) @@ -316,6 +314,7 @@ def __init__( "or door, likely because not running on linux" ) self._current_settings: Optional[OT3AxisMap[CurrentConfig]] = None + self._tip_presence_manager = TipPresenceManager(self._messenger) async def get_serial_number(self) -> Optional[str]: if not self.initialized: @@ -867,34 +866,6 @@ async def get_limit_switches(self) -> OT3AxisMap[bool]: res = await get_limit_switches(self._messenger, motor_nodes) return {node_to_axis(node): bool(val) for node, val in res.items()} - async def check_for_tip_presence( - self, - mount: OT3Mount, - tip_state: TipStateType, - expect_multiple_responses: bool = False, - ) -> None: - """Raise an error if the expected tip state does not match the current state.""" - res = await self.get_tip_present_state(mount, expect_multiple_responses) - if res != tip_state.value: - raise FailedTipStateCheck(tip_state, res) - - async def get_tip_present_state( - self, - mount: OT3Mount, - expect_multiple_responses: bool = False, - ) -> bool: - """Get the state of the tip ejector flag for a given mount.""" - expected_responses = 2 if expect_multiple_responses else 1 - node = sensor_node_for_mount(OT3Mount(mount.value)) - assert node != NodeId.gripper - res = await get_tip_ejector_state(self._messenger, node, expected_responses) # type: ignore[arg-type] - vals = list(res.values()) - if not all([r == vals[0] for r in vals]): - states = {int(sensor): res[sensor] for sensor in res} - raise UnmatchedTipPresenceStates(states) - tip_present_state = bool(vals[0]) - return tip_present_state - @staticmethod def _tip_motor_nodes(axis_current_keys: KeysView[Axis]) -> List[NodeId]: return [axis_to_node(Axis.Q)] if Axis.Q in axis_current_keys else [] @@ -1333,3 +1304,21 @@ async def build_estop_detector(self) -> bool: def estop_state_machine(self) -> EstopStateMachine: """Accessor for the API to get the state machine, if it exists.""" return self._estop_state_machine + + @property + def tip_presence_manager(self) -> TipPresenceManager: + return self._tip_presence_manager + + async def update_tip_detector(self, mount: OT3Mount, sensor_count: int) -> None: + """Build indiviudal tip detector for a mount.""" + await self.teardown_tip_detector(mount) + await self._tip_presence_manager.build_detector(mount, sensor_count) + + async def teardown_tip_detector(self, mount: OT3Mount) -> None: + await self._tip_presence_manager.clear_detector(mount) + + async def get_tip_status(self, mount: OT3Mount) -> TipStateType: + return await self.tip_presence_manager.get_tip_status(mount) + + def current_tip_state(self, mount: OT3Mount) -> Optional[bool]: + return self.tip_presence_manager.current_tip_state(mount) diff --git a/api/src/opentrons/hardware_control/backends/ot3simulator.py b/api/src/opentrons/hardware_control/backends/ot3simulator.py index fc769e2023d..da111472c19 100644 --- a/api/src/opentrons/hardware_control/backends/ot3simulator.py +++ b/api/src/opentrons/hardware_control/backends/ot3simulator.py @@ -189,6 +189,10 @@ def _sanitize_attached_instrument( self._present_nodes = nodes self._current_settings: Optional[OT3AxisMap[CurrentConfig]] = None self._sim_jaw_state = GripperJawState.HOMED_READY + self._sim_tip_state: Dict[OT3Mount, Optional[bool]] = { + mount: False if self._attached_instruments[mount] else None + for mount in [OT3Mount.LEFT, OT3Mount.RIGHT] + } async def get_serial_number(self) -> Optional[str]: return "simulator" @@ -391,21 +395,6 @@ async def gripper_hold_jaw( self._encoder_position[NodeId.gripper_g] = encoder_position_um / 1000.0 self._sim_jaw_state = GripperJawState.HOLDING - async def check_for_tip_presence( - self, - mount: OT3Mount, - tip_state: TipStateType, - expect_multiple_responses: bool = False, - ) -> None: - """Raise an error if the given state doesn't match the physical state.""" - pass - - async def get_tip_present_state( - self, mount: OT3Mount, expect_multiple_responses: bool = False - ) -> bool: - """Get the state of the tip ejector flag for a given mount.""" - pass - async def get_jaw_state(self) -> GripperJawState: """Get the state of the gripper jaw.""" return self._sim_jaw_state @@ -747,3 +736,15 @@ def subsystems(self) -> Dict[SubSystem, SubSystemState]: def estop_state_machine(self) -> EstopStateMachine: """Return an estop state machine locked in the "disengaged" state.""" return self._estop_state_machine + + async def get_tip_status(self, mount: OT3Mount) -> TipStateType: + return TipStateType(self._sim_tip_state[mount]) + + def current_tip_state(self, mount: OT3Mount) -> Optional[bool]: + return self._sim_tip_state[mount] + + async def update_tip_detector(self, mount: OT3Mount, sensor_count: int) -> None: + pass + + async def teardown_tip_detector(self, mount: OT3Mount) -> None: + pass diff --git a/api/src/opentrons/hardware_control/backends/tip_presence_manager.py b/api/src/opentrons/hardware_control/backends/tip_presence_manager.py new file mode 100644 index 00000000000..8bfed0966c5 --- /dev/null +++ b/api/src/opentrons/hardware_control/backends/tip_presence_manager.py @@ -0,0 +1,146 @@ +import logging +from functools import partial +from typing import cast, Callable, Optional, List, Set +from typing_extensions import TypedDict, Literal + +from opentrons.hardware_control.types import TipStateType, OT3Mount + +from opentrons_hardware.drivers.can_bus import CanMessenger +from opentrons_hardware.firmware_bindings.constants import NodeId +from opentrons_hardware.hardware_control.tip_presence import ( + TipDetector, + types as tip_types, +) +from opentrons_shared_data.errors.exceptions import ( + TipDetectorNotFound, + UnmatchedTipPresenceStates, +) + +log = logging.getLogger(__name__) + +TipListener = Callable[[OT3Mount, bool], None] +PipetteMountKeys = Literal["left", "right"] + + +class TipDetectorByMount(TypedDict): + left: Optional[TipDetector] + right: Optional[TipDetector] + + +class UnsubMethodByMount(TypedDict): + left: Optional[Callable[[], None]] + right: Optional[Callable[[], None]] + + +class TipUpdateByMount(TypedDict): + left: Optional[bool] + right: Optional[bool] + + +def _mount_to_node(mount: OT3Mount) -> NodeId: + return { + OT3Mount.LEFT: NodeId.pipette_left, + OT3Mount.RIGHT: NodeId.pipette_right, + }[mount] + + +class TipPresenceManager: + """Handle tip change notification coming from CAN.""" + + _listeners: Set[TipListener] + _detectors: TipDetectorByMount + _unsub_methods: UnsubMethodByMount + _last_state: TipUpdateByMount + + def __init__( + self, + can_messenger: CanMessenger, + listeners: Set[TipListener] = set(), + ) -> None: + self._messenger = can_messenger + self._listeners = listeners + self._detectors = TipDetectorByMount(left=None, right=None) + self._unsub_methods = UnsubMethodByMount(left=None, right=None) + self._last_state = TipUpdateByMount(left=None, right=None) + + @staticmethod + def _get_key(mount: OT3Mount) -> PipetteMountKeys: + assert mount != OT3Mount.GRIPPER + return cast(PipetteMountKeys, mount.name.lower()) + + async def clear_detector(self, mount: OT3Mount) -> None: + """Clean up and remove tip detector.""" + + def _unsubscribe() -> None: + """Unsubscribe from detector.""" + unsub = self._unsub_methods[self._get_key(mount)] + if unsub: + unsub() + self.set_unsub(mount, None) + + detector = self.get_detector(mount) + if detector: + _unsubscribe() + detector.cleanup() + self.set_detector(mount, None) + + async def build_detector(self, mount: OT3Mount, sensor_count: int) -> None: + assert self.get_detector(mount) is None + # set up and subscribe to the detector + d = TipDetector(self._messenger, _mount_to_node(mount), sensor_count) + # listens to the detector so we can immediately notify listeners + # the most up-to-date tip state + unsub = d.add_subscriber(partial(self._handle_tip_update, mount)) + self.set_unsub(mount, unsub) + self.set_detector(mount, d) + + def _handle_tip_update( + self, mount: OT3Mount, update: tip_types.TipNotification + ) -> None: + """Callback for detector.""" + self._last_state[self._get_key(mount)] = update.presence + + for listener in self._listeners: + listener(mount, update.presence) + + def current_tip_state(self, mount: OT3Mount) -> Optional[bool]: + state = self._last_state[self._get_key(mount)] + if state is None: + log.warning("Tip state for {mount} is unknown") + return state + + @staticmethod + def _get_tip_presence(results: List[tip_types.TipNotification]) -> TipStateType: + # more than one sensor reported, we have to check if their states match + if len(set(r.presence for r in results)) > 1: + raise UnmatchedTipPresenceStates( + {int(r.sensor): int(r.presence) for r in results} + ) + return TipStateType(results[0].presence) + + async def get_tip_status(self, mount: OT3Mount) -> TipStateType: + detector = self.get_detector(mount) + return self._get_tip_presence(await detector.request_tip_status()) + + def get_detector(self, mount: OT3Mount) -> TipDetector: + detector = self._detectors[self._get_key(mount)] + if not detector: + raise TipDetectorNotFound( + message=f"Tip detector not set up for {mount} mount", + detail={"mount": str(mount)}, + ) + return detector + + def set_detector(self, mount: OT3Mount, detector: Optional[TipDetector]) -> None: + self._detectors[self._get_key(mount)] = detector + + def set_unsub(self, mount: OT3Mount, unsub: Optional[Callable[[], None]]) -> None: + self._unsub_methods[self._get_key(mount)] = unsub + + def add_listener(self, listener: TipListener) -> Callable[[], None]: + self._listeners.add(listener) + + def remove() -> None: + self._listeners.discard(listener) + + return remove diff --git a/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py b/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py index baea9a010aa..f296f390a38 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/instrument_calibration.py @@ -4,7 +4,7 @@ from datetime import datetime from opentrons.config.robot_configs import default_pipette_offset -from opentrons import calibration_storage +from opentrons.calibration_storage import ot2 as calibration_storage from opentrons.calibration_storage import types, helpers from opentrons.types import Mount, Point from opentrons.hardware_control.types import OT3Mount diff --git a/api/src/opentrons/hardware_control/instruments/ot3/instrument_calibration.py b/api/src/opentrons/hardware_control/instruments/ot3/instrument_calibration.py index e09a6a65a95..d42ae38b779 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/instrument_calibration.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/instrument_calibration.py @@ -17,7 +17,7 @@ ) from opentrons.calibration_storage import ( types as cal_top_types, - ot3_gripper_offset, + gripper_offset, ) from opentrons.hardware_control.types import OT3Mount @@ -122,7 +122,7 @@ def load_gripper_calibration_offset( status=cal_top_types.CalibrationStatus(), ) if gripper_id and ff.enable_ot3_hardware_controller(): - grip_offset_data = ot3_gripper_offset.get_gripper_calibration_offset(gripper_id) + grip_offset_data = gripper_offset.get_gripper_calibration_offset(gripper_id) if grip_offset_data: return GripperCalibrationOffset( offset=grip_offset_data.offset, @@ -141,7 +141,7 @@ def save_gripper_calibration_offset( gripper_id: typing.Optional[str], delta: Point ) -> None: if gripper_id and ff.enable_ot3_hardware_controller(): - ot3_gripper_offset.save_gripper_calibration(delta, gripper_id) + gripper_offset.save_gripper_calibration(delta, gripper_id) def check_instrument_offset_reasonability( diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py index 9f267fbd64a..272a1dd97ec 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py @@ -100,8 +100,8 @@ def __init__( self._current_volume = 0.0 self._working_volume = float(self._liquid_class.max_volume) self._current_tip_length = 0.0 + self._has_tip_length: Optional[bool] = None self._current_tiprack_diameter = 0.0 - self._has_tip = False self._pipette_id = pipette_id self._log = mod_log.getChild( self._pipette_id if self._pipette_id else "" @@ -231,8 +231,8 @@ def reset_state(self) -> None: self._current_volume = 0.0 self._working_volume = float(self.liquid_class.max_volume) self._current_tip_length = 0.0 + self._has_tip_length = None self._current_tiprack_diameter = 0.0 - self._has_tip = False self.ready_to_aspirate = False #: True if ready to aspirate self.set_liquid_class_by_name("default") @@ -322,7 +322,7 @@ def critical_point(self, cp_override: Optional[CriticalPoint] = None) -> Point: CriticalPoint.GRIPPER_REAR_CALIBRATION_PIN, ]: raise InvalidCriticalPoint(cp_override.name, "pipette") - if not self.has_tip or cp_override == CriticalPoint.NOZZLE: + if not self.has_tip_length or cp_override == CriticalPoint.NOZZLE: cp_type = CriticalPoint.NOZZLE tip_length = 0.0 else: @@ -378,6 +378,7 @@ def current_tip_length(self) -> float: @current_tip_length.setter def current_tip_length(self, tip_length: float) -> None: self._current_tip_length = tip_length + self._has_tip_length = True @property def current_tiprack_diameter(self) -> float: @@ -492,27 +493,36 @@ def add_tip(self, tip_length: float) -> None: :return: """ assert tip_length > 0.0, "tip_length must be greater than 0" - assert not self.has_tip - self._has_tip = True + assert not self.has_tip_length self._current_tip_length = tip_length + self._has_tip_length = True def remove_tip(self) -> None: """ Remove the tip from the pipette (effectively updates the pipette's critical point) """ - assert self.has_tip - self._has_tip = False + assert self.has_tip_length self._current_tip_length = 0.0 + self._has_tip_length = False @property def has_tip(self) -> bool: - return self._has_tip + return self.has_tip_length + + @property + def has_tip_length(self) -> bool: + return self.current_tip_length > 0.0 @property def tip_presence_check_dist_mm(self) -> float: return self._config.tip_presence_check_distance_mm + @property + def tip_presence_responses(self) -> int: + # TODO: put this in shared-data + return 2 if self.channels > 8 else 1 + @property def connect_tiprack_distance_mm(self) -> float: return self._config.connect_tiprack_distance_mm @@ -547,7 +557,7 @@ def __str__(self) -> str: return "{} current volume {}ul critical point: {} at {}".format( self._config.display_name, self.current_volume, - "tip end" if self.has_tip else "nozzle end", + "tip end" if self.has_tip_length else "nozzle end", 0, ) @@ -565,7 +575,7 @@ def as_dict(self) -> "Pipette.DictType": "name": self.name, "model": self.model, "pipette_id": self.pipette_id, - "has_tip": self.has_tip, + "has_tip": self.has_tip_length, "working_volume": self.working_volume, "aspirate_flow_rate": self.aspirate_flow_rate, "dispense_flow_rate": self.dispense_flow_rate, @@ -604,7 +614,7 @@ def set_liquid_class_by_name(self, class_name: str) -> None: }, ) if ( - self.has_tip + self.has_tip_length and self._active_tip_setting_name not in new_class.supported_tips ): raise CommandPreconditionViolated( @@ -616,7 +626,7 @@ def set_liquid_class_by_name(self, class_name: str) -> None: ) self._liquid_class_name = new_name self._liquid_class = new_class - if not self.has_tip: + if not self.has_tip_length: new_tip_class = sorted( [tip for tip in self._liquid_class.supported_tips.keys()], key=lambda tt: tt.value, diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py index 79acc146e7e..ff8fbc64122 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py @@ -892,3 +892,8 @@ async def configure_for_volume(self, mount: OT3Mount, volume: float) -> None: pip.config.liquid_properties, ) pip.set_liquid_class_by_name(new_class_name.name) + + def get_tip_sensor_count(self, mount: OT3Mount) -> int: + if not self.has_pipette(mount): + return 0 + return self.get_pipette(mount).tip_presence_responses diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 4d41a758717..7a87c7aae5e 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -39,7 +39,7 @@ ) from opentrons import types as top_types -from opentrons.config import robot_configs, feature_flags as ff +from opentrons.config import robot_configs from opentrons.config.types import ( RobotConfig, OT3Config, @@ -112,6 +112,7 @@ EstopAttachLocation, EstopStateNotification, EstopState, + FailedTipStateCheck, ) from .errors import ( UpdateOngoingError, @@ -389,7 +390,6 @@ async def build_hardware_controller( ) api_instance = cls(backend, loop=checked_loop, config=checked_config) - await api_instance.set_status_bar_enabled(status_bar_enabled) module_controls = await AttachedModulesControl.build( api_instance, board_revision=backend.board_revision @@ -711,6 +711,25 @@ async def _configure_instruments(self) -> None: """Configure instruments""" await self.set_gantry_load(self._gantry_load_from_instruments()) await self.refresh_positions() + await self.reset_tip_detectors() + + async def reset_tip_detectors( + self, + refresh_state: bool = True, + ) -> None: + """Reset tip detector whenever we configure instruments.""" + for mount in [OT3Mount.LEFT, OT3Mount.RIGHT]: + # rebuild tip detector using the attached instrument + self._log.info(f"resetting tip detector for mount {mount}") + if self._pipette_handler.has_pipette(mount): + await self._backend.update_tip_detector( + mount, self._pipette_handler.get_tip_sensor_count(mount) + ) + else: + await self._backend.teardown_tip_detector(mount) + + if refresh_state and self._pipette_handler.has_pipette(mount): + await self.get_tip_presence_status(mount) @ExecutionManagerProvider.wait_for_running async def _update_position_estimation( @@ -1706,7 +1725,7 @@ async def _move_to_plunger_bottom( ) pip_ax = Axis.of_main_tool_actuator(checked_mount) # speed depends on if there is a tip, and which direction to move - if instrument.has_tip: + if instrument.has_tip_length: # using slower aspirate flow-rate, to avoid pulling droplets up speed_up = self._pipette_handler.plunger_speed( instrument, instrument.aspirate_flow_rate, "aspirate" @@ -1913,41 +1932,54 @@ async def blow_out( blowout_spec.instr.set_current_volume(0) blowout_spec.instr.ready_to_aspirate = False - async def get_tip_presence_status(self, mount: OT3Mount) -> bool: - """ - Check tip presence status. If a high throughput pipette is present, - move the tip motors down before checking the sensor status. - """ - checked_mount = OT3Mount.from_mount(mount) - high_throughput = self._gantry_load == GantryLoad.HIGH_THROUGHPUT - if high_throughput: - # check if we're already at the tip_presence_check position, if we are dont move + @contextlib.asynccontextmanager + async def _high_throughput_check_tip(self) -> AsyncIterator[None]: + """Tip action required for high throughput pipettes to get tip status.""" + instrument = self._pipette_handler.get_pipette(OT3Mount.LEFT) + tip_presence_check_target = instrument.tip_presence_check_dist_mm - instrument = self._pipette_handler.get_pipette(checked_mount) - tip_presence_check_target = instrument.tip_presence_check_dist_mm + # if position is not known, home gear motors before any potential movement + if self._backend.gear_motor_position is None: + await self.home_gear_motors() - # if position is not known, home gear motors before any potential movement - if self._backend.gear_motor_position is None: - await self.home_gear_motors() + tip_motor_pos_float = axis_convert(self._backend.gear_motor_position, 0.0)[ + Axis.of_main_tool_actuator(OT3Mount.LEFT) + ] - tip_motor_pos_float = axis_convert(self._backend.gear_motor_position, 0.0)[ - Axis.of_main_tool_actuator(checked_mount) - ] + # only move tip motors if they are not already below the sensor + if tip_motor_pos_float < tip_presence_check_target: + clamp_moves = self._build_moves( + {Axis.Q: tip_motor_pos_float}, {Axis.Q: tip_presence_check_target} + ) + await self._backend.tip_action(moves=clamp_moves[0]) + try: + yield + finally: + await self.home_gear_motors() - # only move tip motors if they are not already below the sensor - if tip_motor_pos_float < tip_presence_check_target: - clamp_moves = self._build_moves( - {Axis.Q: tip_motor_pos_float}, {Axis.Q: tip_presence_check_target} - ) - await self._backend.tip_action(moves=clamp_moves[0]) - tip_status = await self._backend.get_tip_present_state( - mount=checked_mount, expect_multiple_responses=high_throughput - ) + async def get_tip_presence_status( + self, + mount: OT3Mount, + ) -> TipStateType: + """ + Check tip presence status. If a high throughput pipette is present, + move the tip motors down before checking the sensor status. + """ + async with contextlib.AsyncExitStack() as stack: + if ( + mount == OT3Mount.LEFT + and self._gantry_load == GantryLoad.HIGH_THROUGHPUT + ): + await stack.enter_async_context(self._high_throughput_check_tip()) + result = await self._backend.get_tip_status(mount) + return result - if high_throughput: - # return tip motors to neutral position - await self.home_gear_motors() - return tip_status + async def verify_tip_presence( + self, mount: OT3Mount, expected: TipStateType + ) -> None: + status = await self.get_tip_presence_status(mount) + if status != expected: + raise FailedTipStateCheck(expected, status.value) async def _force_pick_up_tip( self, mount: OT3Mount, pipette_spec: TipActionSpec @@ -2038,15 +2070,6 @@ def add_tip_to_instr() -> None: realmount, top_types.Point(z=spec.ending_z_retract_distance) ) - # TODO: implement tip-detection sequence during pick-up-tip for 96ch, - # but not with DVT pipettes because those can only detect drops - - if ( - self.gantry_load != GantryLoad.HIGH_THROUGHPUT - and ff.tip_presence_detection_enabled() - ): - await self._backend.check_for_tip_presence(realmount, TipStateType.PRESENT) - add_tip_to_instr() if prep_after: @@ -2104,12 +2127,6 @@ def _remove_tips() -> None: for shake in spec.shake_off_moves: await self.move_rel(mount, shake[0], speed=shake[1]) - # TODO: implement tip-detection sequence during drop-tip for 96ch - if ( - self.gantry_load != GantryLoad.HIGH_THROUGHPUT - and ff.tip_presence_detection_enabled() - ): - await self._backend.check_for_tip_presence(realmount, TipStateType.ABSENT) # home mount axis if home_after: @@ -2168,13 +2185,16 @@ def get_attached_instruments(self) -> Dict[top_types.Mount, PipetteDict]: return self.get_attached_pipettes() async def get_instrument_state( - self, mount: Union[top_types.Mount, OT3Mount] + self, + mount: Union[top_types.Mount, OT3Mount], ) -> PipetteStateDict: # TODO we should have a PipetteState that can be returned from # this function with additional state (such as critical points) realmount = OT3Mount.from_mount(mount) - res = await self._backend.get_tip_present_state(realmount) - pipette_state_for_mount: PipetteStateDict = {"tip_detected": res} + tip_attached = self._backend.current_tip_state(realmount) + pipette_state_for_mount: PipetteStateDict = { + "tip_detected": tip_attached if tip_attached is not None else False + } return pipette_state_for_mount def reset_instrument( diff --git a/api/src/opentrons/hardware_control/types.py b/api/src/opentrons/hardware_control/types.py index 0c22a5145a3..4dbd64559b5 100644 --- a/api/src/opentrons/hardware_control/types.py +++ b/api/src/opentrons/hardware_control/types.py @@ -37,6 +37,10 @@ def to_mount(self) -> top_types.Mount: return top_types.Mount.EXTENSION return top_types.Mount[self.name] + @classmethod + def pipette_mounts(cls) -> List["Literal[OT3Mount.LEFT, OT3Mount.RIGHT]"]: + return [cls.LEFT, cls.RIGHT] + class OT3AxisKind(enum.Enum): """An enum of the different kinds of axis we have. diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 22d7290f05c..79773c98264 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -18,7 +18,7 @@ from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION -from opentrons.types import Point +from opentrons.types import Point, DeckSlotName from opentrons_shared_data.pipette.dev_types import PipetteNameType @@ -409,13 +409,7 @@ def _move_to_waste_chute( slot_origin_to_tip_a1 = _waste_chute_dimensions.SLOT_ORIGIN_TO_1_OR_8_TIP_A1 # TODO: All of this logic to compute the destination coordinate belongs in Protocol Engine. - slot_d3 = next( - s - for s in self._protocol_core.get_deck_definition()["locations"][ - "orderedSlots" - ] - if s["id"] == "D3" - ) + slot_d3 = self._protocol_core.get_slot_definition(DeckSlotName.SLOT_D3) slot_d3_origin = Point(*slot_d3["position"]) destination_point = slot_d3_origin + slot_origin_to_tip_a1 @@ -590,3 +584,6 @@ def configure_for_volume(self, volume: float) -> None: self._engine_client.configure_for_volume( pipette_id=self._pipette_id, volume=volume ) + + def prepare_to_aspirate(self) -> None: + self._engine_client.prepare_to_aspirate(pipette_id=self._pipette_id) diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index cff8e48be42..d82edf8cee8 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -4,7 +4,7 @@ from opentrons.protocol_api import _waste_chute_dimensions from opentrons.protocol_engine.commands import LoadModuleResult -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3, SlotDefV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4, SlotDefV3 from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.labware.dev_types import LabwareDefinition as LabwareDefDict from opentrons_shared_data.pipette.dev_types import PipetteNameType @@ -102,17 +102,20 @@ def robot_type(self) -> RobotType: return self._engine_client.state.config.robot_type @property - def fixed_trash(self) -> LabwareCore: + def fixed_trash(self) -> Optional[LabwareCore]: """Get the fixed trash labware.""" trash_id = self._engine_client.state.labware.get_fixed_trash_id() - return self._labware_cores_by_id[trash_id] + if trash_id is not None: + return self._labware_cores_by_id[trash_id] + return None def _load_fixed_trash(self) -> None: trash_id = self._engine_client.state.labware.get_fixed_trash_id() - self._labware_cores_by_id[trash_id] = LabwareCore( - labware_id=trash_id, - engine_client=self._engine_client, - ) + if trash_id is not None: + self._labware_cores_by_id[trash_id] = LabwareCore( + labware_id=trash_id, + engine_client=self._engine_client, + ) def get_max_speeds(self) -> AxisMaxSpeeds: """Get a control interface for maximum move speeds.""" @@ -532,11 +535,12 @@ def set_last_location( self._last_location = location self._last_mount = mount - def get_deck_definition(self) -> DeckDefinitionV3: + def get_deck_definition(self) -> DeckDefinitionV4: """Get the geometry definition of the robot's deck.""" return self._engine_client.state.labware.get_deck_definition() def get_slot_definition(self, slot: DeckSlotName) -> SlotDefV3: + """Get the slot definition from the robot's deck.""" return self._engine_client.state.labware.get_slot_definition(slot) def _ensure_module_location( diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 6a1ddd8eeb6..bcec7f9c0f6 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -243,5 +243,9 @@ def configure_for_volume(self, volume: float) -> None: """ ... + def prepare_to_aspirate(self) -> None: + """Prepare the pipette to aspirate.""" + ... + InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any]) diff --git a/api/src/opentrons/protocol_api/core/legacy/deck.py b/api/src/opentrons/protocol_api/core/legacy/deck.py index 0cddcabd9fc..ea4068934bd 100644 --- a/api/src/opentrons/protocol_api/core/legacy/deck.py +++ b/api/src/opentrons/protocol_api/core/legacy/deck.py @@ -4,12 +4,10 @@ import logging from collections import UserDict from typing import Dict, Optional, List, Union -from typing_extensions import Protocol +from typing_extensions import Protocol, Final + +from opentrons_shared_data.deck import load as load_deck -from opentrons_shared_data.deck import ( - DEFAULT_DECK_DEFINITION_VERSION, - load as load_deck, -) from opentrons_shared_data.deck.dev_types import SlotDefV3 from opentrons_shared_data.labware.dev_types import LabwareUri @@ -34,6 +32,9 @@ FIXED_TRASH_ID = "fixedTrash" +DEFAULT_LEGACY_DECK_DEFINITION_VERSION: Final = 3 + + class DeckItem(Protocol): @property def highest_z(self) -> float: @@ -50,7 +51,7 @@ class Deck(UserDict): # type: ignore[type-arg] def __init__(self, deck_type: str) -> None: super().__init__() self._definition = load_deck( - name=deck_type, version=DEFAULT_DECK_DEFINITION_VERSION + name=deck_type, version=DEFAULT_LEGACY_DECK_DEFINITION_VERSION ) self._positions = {} for slot in self._definition["locations"]["orderedSlots"]: diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index c63904a3158..b9e661a9fce 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -102,7 +102,7 @@ def aspirate( "cause over aspiration if the previous command is a " "blow_out." ) - self.prepare_for_aspirate() + self.prepare_to_aspirate() self.move_to(location=location) elif not in_place: self.move_to(location=location) @@ -443,7 +443,7 @@ def has_tip(self) -> bool: def is_ready_to_aspirate(self) -> bool: return self.get_hardware_state()["ready_to_aspirate"] - def prepare_for_aspirate(self) -> None: + def prepare_to_aspirate(self) -> None: self._protocol_interface.get_hardware().prepare_for_aspirate(self._mount) def get_return_height(self) -> float: 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 853a5552633..01faa63c17b 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 @@ -1,7 +1,7 @@ import logging from typing import Dict, List, Optional, Set, Union, cast, Tuple -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4, SlotDefV3 from opentrons_shared_data.labware.dev_types import LabwareDefinition from opentrons_shared_data.pipette.dev_types import PipetteNameType from opentrons_shared_data.robot.dev_types import RobotType @@ -452,10 +452,14 @@ def get_labware_on_labware( ) -> Optional[LegacyLabwareCore]: assert False, "get_labware_on_labware only supported on engine core" - def get_deck_definition(self) -> DeckDefinitionV3: + def get_deck_definition(self) -> DeckDefinitionV4: """Get the geometry definition of the robot's deck.""" assert False, "get_deck_definition only supported on engine core" + def get_slot_definition(self, slot: DeckSlotName) -> SlotDefV3: + """Get the slot definition from the robot's deck.""" + assert False, "get_slot_definition only supported on engine core" + def get_slot_item( self, slot_name: DeckSlotName ) -> Union[LegacyLabwareCore, legacy_module_core.LegacyModuleCore, None]: diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 29a8b871ac8..ab90676c27e 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -106,7 +106,7 @@ def aspirate( "cause over aspiration if the previous command is a " "blow_out." ) - self.prepare_for_aspirate() + self.prepare_to_aspirate() self.move_to(location=location, well_core=well_core) elif location != self._protocol_interface.get_last_location(): self.move_to(location=location, well_core=well_core) @@ -334,7 +334,7 @@ def has_tip(self) -> bool: def is_ready_to_aspirate(self) -> bool: return self._pipette_dict["ready_to_aspirate"] - def prepare_for_aspirate(self) -> None: + def prepare_to_aspirate(self) -> None: self._raise_if_no_tip(HardwareAction.PREPARE_ASPIRATE.name) def get_return_height(self) -> float: diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index 6fab341f4e5..596cb9c6da4 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -5,7 +5,7 @@ from abc import abstractmethod, ABC from typing import Generic, List, Optional, Union, Tuple -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4, SlotDefV3 from opentrons_shared_data.pipette.dev_types import PipetteNameType from opentrons_shared_data.labware.dev_types import LabwareDefinition from opentrons_shared_data.robot.dev_types import RobotType @@ -28,7 +28,7 @@ class AbstractProtocol( ): @property @abstractmethod - def fixed_trash(self) -> LabwareCoreType: + def fixed_trash(self) -> Optional[LabwareCoreType]: """Get the fixed trash labware core.""" ... @@ -154,9 +154,14 @@ def set_last_location( ... @abstractmethod - def get_deck_definition(self) -> DeckDefinitionV3: + def get_deck_definition(self) -> DeckDefinitionV4: """Get the geometry definition of the robot's deck.""" + # TODO(jbl 10-30-2023) this method may no longer need to exist post deck config work being completed + @abstractmethod + def get_slot_definition(self, slot: DeckSlotName) -> SlotDefV3: + """Get the slot definition from the robot's deck.""" + @abstractmethod def get_slot_item( self, slot_name: DeckSlotName diff --git a/api/src/opentrons/protocol_api/deck.py b/api/src/opentrons/protocol_api/deck.py index 8907f8436b6..c5c9fcb2368 100644 --- a/api/src/opentrons/protocol_api/deck.py +++ b/api/src/opentrons/protocol_api/deck.py @@ -65,13 +65,47 @@ def __init__( self._core_map = core_map self._api_version = api_version - self._protocol_core.robot_type - - deck_locations = protocol_core.get_deck_definition()["locations"] + # TODO(jbl 10-30-2023) this hardcoding should be removed once slots are refactored to work with deck config + if self._protocol_core.robot_type == "OT-2 Standard": + ordered_slot_ids = [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + ] + else: + ordered_slot_ids = [ + "D1", + "D2", + "D3", + "C1", + "C2", + "C3", + "B1", + "B2", + "B3", + "A1", + "A2", + "A3", + ] self._slot_definitions_by_name = { - slot["id"]: slot for slot in deck_locations["orderedSlots"] + slot_id: self._protocol_core.get_slot_definition( + DeckSlotName.from_primitive(slot_id) + ) + for slot_id in ordered_slot_ids } + + deck_locations = protocol_core.get_deck_definition()["locations"] + self._calibration_positions = [ CalibrationPosition( id=point["id"], diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 4c4c5391e24..9f03645f69d 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -16,6 +16,7 @@ from opentrons.protocols.advanced_control.mix import mix_from_kwargs from opentrons.protocols.advanced_control import transfers +from opentrons.protocols.api_support.deck_type import NoTrashDefinedError from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support import instrument from opentrons.protocols.api_support.util import ( @@ -84,7 +85,7 @@ def __init__( broker: LegacyBroker, api_version: APIVersion, tip_racks: List[labware.Labware], - trash: labware.Labware, + trash: Optional[labware.Labware], requested_as: str, ) -> None: super().__init__(broker) @@ -99,7 +100,7 @@ def __init__( default_dispense=_DEFAULT_DISPENSE_CLEARANCE, ) - self.trash_container = trash + self._trash = trash self.requested_as = requested_as @property # type: ignore @@ -1422,6 +1423,10 @@ def trash_container(self) -> labware.Labware: By default, the trash container is in slot A3 on Flex and in slot 12 on OT-2. """ + if self._trash is None: + raise NoTrashDefinedError( + "No trash container has been defined in this protocol." + ) return self._trash @trash_container.setter @@ -1609,3 +1614,47 @@ def configure_for_volume(self, volume: float) -> None: if last_location and isinstance(last_location.labware, labware.Well): self.move_to(last_location.labware.top()) self._core.configure_for_volume(volume) + + @requires_version(2, 16) + def prepare_to_aspirate(self) -> None: + """Prepare a pipette for aspiration. + + Before a pipette can aspirate into an empty tip, the plunger must be in its + bottom position. After dropping a tip or blowing out, the plunger will be in a + different position. This function moves the plunger to the bottom position, + regardless of its current position, to make sure that the pipette is ready to + aspirate. + + You rarely need to call this function. The API automatically prepares the + pipette for aspiration as part of other commands: + + - After picking up a tip with :py:meth:`.pick_up_tip`. + - When calling :py:meth:`.aspirate`, if the pipette isn't already prepared. + If the pipette is in a well, it will move out of the well, move the plunger, + and then move back. + + Use ``prepare_to_aspirate`` when you need to control exactly when the plunger + motion will happen. A common use case is a pre-wetting routine, which requires + preparing for aspiration, moving into a well, and then aspirating *without + leaving the well*:: + + pipette.move_to(well.bottom(z=2)) + pipette.delay(5) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.delay(5) + pipette.aspirate(10, well.bottom(z=2)) + + The call to ``prepare_to_aspirate()`` means that the plunger will be in the + bottom position before the call to ``aspirate()``. Since it doesn't need to + prepare again, it will not move up out of the well to move the plunger. It will + aspirate in place. + """ + if self._core.get_current_volume(): + raise CommandPreconditionViolated( + message=f"Cannot prepare {str(self)} for aspirate while it contains liquid." + ) + self._core.prepare_to_aspirate() diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 02e809a26fd..ec1ff432384 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -23,6 +23,7 @@ from opentrons.commands import protocol_commands as cmds, types as cmd_types from opentrons.commands.publisher import CommandPublisher, publish from opentrons.protocols.api_support import instrument as instrument_support +from opentrons.protocols.api_support.deck_type import NoTrashDefinedError from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import ( AxisMaxSpeeds, @@ -822,13 +823,19 @@ def load_instrument( log=logger, ) + trash: Optional[Labware] + try: + trash = self.fixed_trash + except NoTrashDefinedError: + trash = None + instrument = InstrumentContext( core=instrument_core, protocol_core=self._core, broker=self._broker, api_version=self._api_version, tip_racks=tip_racks, - trash=self.fixed_trash, + trash=trash, requested_as=instrument_name, ) @@ -985,17 +992,23 @@ def fixed_trash(self) -> Labware: It has one well and should be accessed like labware in your protocol. e.g. ``protocol.fixed_trash['A1']`` """ - return self._core_map.get(self._core.fixed_trash) + fixed_trash = self._core_map.get(self._core.fixed_trash) + if fixed_trash is None: + raise NoTrashDefinedError( + "No trash container has been defined in this protocol." + ) + return fixed_trash def _load_fixed_trash(self) -> None: fixed_trash_core = self._core.fixed_trash - fixed_trash = Labware( - core=fixed_trash_core, - api_version=self._api_version, - protocol_core=self._core, - core_map=self._core_map, - ) - self._core_map.add(fixed_trash_core, fixed_trash) + if fixed_trash_core is not None: + fixed_trash = Labware( + core=fixed_trash_core, + api_version=self._api_version, + protocol_core=self._core, + core_map=self._core_map, + ) + self._core_map.add(fixed_trash_core, fixed_trash) @requires_version(2, 5) def set_rail_lights(self, on: bool) -> None: diff --git a/api/src/opentrons/protocol_engine/clients/sync_client.py b/api/src/opentrons/protocol_engine/clients/sync_client.py index 65eacf32d11..cfef710a5ce 100644 --- a/api/src/opentrons/protocol_engine/clients/sync_client.py +++ b/api/src/opentrons/protocol_engine/clients/sync_client.py @@ -284,6 +284,14 @@ def configure_for_volume( result = self._transport.execute_command(request=request) return cast(commands.ConfigureForVolumeResult, result) + def prepare_to_aspirate(self, pipette_id: str) -> commands.PrepareToAspirateResult: + """Execute a PrepareToAspirate command.""" + request = commands.PrepareToAspirateCreate( + params=commands.PrepareToAspirateParams(pipetteId=pipette_id) + ) + result = self._transport.execute_command(request=request) + return cast(commands.PrepareToAspirateResult, result) + def aspirate( self, pipette_id: str, diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index 90e1387b267..85b25046479 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -266,6 +266,14 @@ ConfigureForVolumeCommandType, ) +from .prepare_to_aspirate import ( + PrepareToAspirate, + PrepareToAspirateCreate, + PrepareToAspirateParams, + PrepareToAspirateResult, + PrepareToAspirateCommandType, +) + __all__ = [ # command type unions "Command", @@ -463,4 +471,10 @@ "ConfigureForVolumeParams", "ConfigureForVolumeResult", "ConfigureForVolumeCommandType", + # prepare pipette for aspirate command bundle + "PrepareToAspirate", + "PrepareToAspirateCreate", + "PrepareToAspirateParams", + "PrepareToAspirateResult", + "PrepareToAspirateCommandType", ] diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 12f6675e28c..bf9ff6b7768 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -235,6 +235,14 @@ ConfigureForVolumePrivateResult, ) +from .prepare_to_aspirate import ( + PrepareToAspirate, + PrepareToAspirateParams, + PrepareToAspirateCreate, + PrepareToAspirateResult, + PrepareToAspirateCommandType, +) + Command = Union[ Aspirate, AspirateInPlace, @@ -257,6 +265,7 @@ MoveRelative, MoveToCoordinates, MoveToWell, + PrepareToAspirate, WaitForResume, WaitForDuration, PickUpTip, @@ -313,6 +322,7 @@ MoveRelativeParams, MoveToCoordinatesParams, MoveToWellParams, + PrepareToAspirateParams, WaitForResumeParams, WaitForDurationParams, PickUpTipParams, @@ -370,6 +380,7 @@ MoveRelativeCommandType, MoveToCoordinatesCommandType, MoveToWellCommandType, + PrepareToAspirateCommandType, WaitForResumeCommandType, WaitForDurationCommandType, PickUpTipCommandType, @@ -426,6 +437,7 @@ MoveRelativeCreate, MoveToCoordinatesCreate, MoveToWellCreate, + PrepareToAspirateCreate, WaitForResumeCreate, WaitForDurationCreate, PickUpTipCreate, @@ -482,6 +494,7 @@ MoveRelativeResult, MoveToCoordinatesResult, MoveToWellResult, + PrepareToAspirateResult, WaitForResumeResult, WaitForDurationResult, PickUpTipResult, diff --git a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py new file mode 100644 index 00000000000..57fa679bb09 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py @@ -0,0 +1,73 @@ +"""Prepare to aspirate command request, result, and implementation models.""" + +from __future__ import annotations +from pydantic import BaseModel +from typing import TYPE_CHECKING, Optional, Type +from typing_extensions import Literal + +from .pipetting_common import ( + PipetteIdMixin, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, +) + +if TYPE_CHECKING: + from ..execution.pipetting import PipettingHandler + +PrepareToAspirateCommandType = Literal["prepareToAspirate"] + + +class PrepareToAspirateParams(PipetteIdMixin): + """Parameters required to prepare a specific pipette for aspiration.""" + + pass + + +class PrepareToAspirateResult(BaseModel): + """Result data from execution of an PrepareToAspirate command.""" + + pass + + +class PrepareToAspirateImplementation( + AbstractCommandImpl[ + PrepareToAspirateParams, + PrepareToAspirateResult, + ] +): + """Prepare for aspirate command implementation.""" + + def __init__(self, pipetting: PipettingHandler, **kwargs: object) -> None: + self._pipetting_handler = pipetting + + async def execute(self, params: PrepareToAspirateParams) -> PrepareToAspirateResult: + """Prepare the pipette to aspirate.""" + await self._pipetting_handler.prepare_for_aspirate( + pipette_id=params.pipetteId, + ) + + return PrepareToAspirateResult() + + +class PrepareToAspirate(BaseCommand[PrepareToAspirateParams, PrepareToAspirateResult]): + """Prepare for aspirate command model.""" + + commandType: PrepareToAspirateCommandType = "prepareToAspirate" + params: PrepareToAspirateParams + result: Optional[PrepareToAspirateResult] + + _ImplementationCls: Type[ + PrepareToAspirateImplementation + ] = PrepareToAspirateImplementation + + +class PrepareToAspirateCreate(BaseCommandCreate[PrepareToAspirateParams]): + """Prepare for aspirate command creation request model.""" + + commandType: PrepareToAspirateCommandType = "prepareToAspirate" + params: PrepareToAspirateParams + + _CommandCls: Type[PrepareToAspirate] = PrepareToAspirate diff --git a/api/src/opentrons/protocol_engine/create_protocol_engine.py b/api/src/opentrons/protocol_engine/create_protocol_engine.py index 5139b9b5eba..adb4657d2af 100644 --- a/api/src/opentrons/protocol_engine/create_protocol_engine.py +++ b/api/src/opentrons/protocol_engine/create_protocol_engine.py @@ -16,18 +16,22 @@ # TODO(mm, 2023-06-16): Arguably, this not being a context manager makes us prone to forgetting to # clean it up properly, especially in tests. See e.g. https://opentrons.atlassian.net/browse/RSS-222 async def create_protocol_engine( - hardware_api: HardwareControlAPI, - config: Config, + hardware_api: HardwareControlAPI, config: Config, load_fixed_trash: bool = False ) -> ProtocolEngine: """Create a ProtocolEngine instance. Arguments: hardware_api: Hardware control API to pass down to dependencies. config: ProtocolEngine configuration. + load_fixed_trash: Automatically load fixed trash labware in engine """ deck_data = DeckDataProvider(config.deck_type) deck_definition = await deck_data.get_deck_definition() - deck_fixed_labware = await deck_data.get_deck_fixed_labware(deck_definition) + deck_fixed_labware = ( + await deck_data.get_deck_fixed_labware(deck_definition) + if load_fixed_trash + else [] + ) module_calibration_offsets = ModuleDataProvider.load_module_calibrations() state_store = StateStore( @@ -47,6 +51,7 @@ def create_protocol_engine_in_thread( config: Config, drop_tips_after_run: bool, post_run_hardware_state: PostRunHardwareState, + load_fixed_trash: bool = False, ) -> typing.Generator[ typing.Tuple[ProtocolEngine, asyncio.AbstractEventLoop], None, None ]: @@ -69,7 +74,11 @@ def create_protocol_engine_in_thread( """ with async_context_manager_in_thread( _protocol_engine( - hardware_api, config, drop_tips_after_run, post_run_hardware_state + hardware_api, + config, + drop_tips_after_run, + post_run_hardware_state, + load_fixed_trash, ) ) as ( protocol_engine, @@ -84,10 +93,12 @@ async def _protocol_engine( config: Config, drop_tips_after_run: bool, post_run_hardware_state: PostRunHardwareState, + load_fixed_trash: bool = False, ) -> typing.AsyncGenerator[ProtocolEngine, None]: protocol_engine = await create_protocol_engine( hardware_api=hardware_api, config=config, + load_fixed_trash=load_fixed_trash, ) try: protocol_engine.play() diff --git a/api/src/opentrons/protocol_engine/errors/__init__.py b/api/src/opentrons/protocol_engine/errors/__init__.py index 0cea7ce7943..642d4ff6cd8 100644 --- a/api/src/opentrons/protocol_engine/errors/__init__.py +++ b/api/src/opentrons/protocol_engine/errors/__init__.py @@ -27,6 +27,7 @@ ModuleNotOnDeckError, ModuleNotConnectedError, SlotDoesNotExistError, + FixtureDoesNotExistError, FailedToPlanMoveError, MustHomeError, RunStoppedError, @@ -87,6 +88,7 @@ "ModuleNotOnDeckError", "ModuleNotConnectedError", "SlotDoesNotExistError", + "FixtureDoesNotExistError", "FailedToPlanMoveError", "MustHomeError", "RunStoppedError", diff --git a/api/src/opentrons/protocol_engine/errors/exceptions.py b/api/src/opentrons/protocol_engine/errors/exceptions.py index 27eab6a640f..7f4304f8097 100644 --- a/api/src/opentrons/protocol_engine/errors/exceptions.py +++ b/api/src/opentrons/protocol_engine/errors/exceptions.py @@ -373,6 +373,19 @@ def __init__( super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) +class FixtureDoesNotExistError(ProtocolEngineError): + """Raised when referencing an addressable area (aka fixture) that does not exist.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a FixtureDoesNotExist.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + # TODO(mc, 2020-11-06): flesh out with structured data to replicate # existing LabwareHeightError class FailedToPlanMoveError(ProtocolEngineError): diff --git a/api/src/opentrons/protocol_engine/resources/deck_configuration_provider.py b/api/src/opentrons/protocol_engine/resources/deck_configuration_provider.py new file mode 100644 index 00000000000..cc24a572a70 --- /dev/null +++ b/api/src/opentrons/protocol_engine/resources/deck_configuration_provider.py @@ -0,0 +1,72 @@ +"""Deck configuration resource provider.""" +from dataclasses import dataclass +from typing import List, Set, Dict + +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4, AddressableArea + + +from ..errors import FixtureDoesNotExistError + + +@dataclass(frozen=True) +class DeckCutoutFixture: + """Basic cutout fixture data class.""" + + name: str + # TODO(jbl 10-30-2023) this is in reference to the cutout ID that is supplied in mayMountTo in the definition. + # We might want to make this not a string. + cutout_slot_location: str + + +class DeckConfigurationProvider: + """Provider class to ingest deck configuration data and retrieve relevant deck definition data.""" + + _configuration: Dict[str, DeckCutoutFixture] + + def __init__( + self, + deck_definition: DeckDefinitionV4, + deck_configuration: List[DeckCutoutFixture], + ) -> None: + """Initialize a DeckDataProvider.""" + self._deck_definition = deck_definition + self._configuration = { + cutout_fixture.cutout_slot_location: cutout_fixture + for cutout_fixture in deck_configuration + } + + def get_addressable_areas_for_cutout_fixture( + self, cutout_fixture_id: str, cutout_id: str + ) -> Set[str]: + """Get the allowable addressable areas for a cutout fixture loaded on a specific cutout slot.""" + for cutout_fixture in self._deck_definition["cutoutFixtures"]: + if cutout_fixture_id == cutout_fixture["id"]: + return set( + cutout_fixture["providesAddressableAreas"].get(cutout_id, []) + ) + + raise FixtureDoesNotExistError( + f'Could not resolve "{cutout_fixture_id}" to a fixture.' + ) + + def get_configured_addressable_areas(self) -> Set[str]: + """Get a list of all addressable areas the robot is configured for.""" + configured_addressable_areas = set() + for cutout_id, cutout_fixture in self._configuration.items(): + addressable_areas = self.get_addressable_areas_for_cutout_fixture( + cutout_fixture.name, cutout_id + ) + configured_addressable_areas.update(addressable_areas) + return configured_addressable_areas + + def get_addressable_area_definition( + self, addressable_area_name: str + ) -> AddressableArea: + """Get the addressable area definition from the relevant deck definition.""" + for addressable_area in self._deck_definition["locations"]["addressableAreas"]: + if addressable_area_name == addressable_area["id"]: + return addressable_area + + raise FixtureDoesNotExistError( + f'Could not resolve "{addressable_area_name}" to a fixture.' + ) diff --git a/api/src/opentrons/protocol_engine/resources/deck_data_provider.py b/api/src/opentrons/protocol_engine/resources/deck_data_provider.py index 8ddc46a4725..6098c2f4301 100644 --- a/api/src/opentrons/protocol_engine/resources/deck_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/deck_data_provider.py @@ -9,7 +9,7 @@ load as load_deck, DEFAULT_DECK_DEFINITION_VERSION, ) -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons.protocols.models import LabwareDefinition from opentrons.types import DeckSlotName @@ -39,10 +39,10 @@ def __init__( self._deck_type = deck_type self._labware_data = labware_data or LabwareDataProvider() - async def get_deck_definition(self) -> DeckDefinitionV3: + async def get_deck_definition(self) -> DeckDefinitionV4: """Get a labware definition given the labware's identification.""" - def sync() -> DeckDefinitionV3: + def sync() -> DeckDefinitionV4: return load_deck( name=self._deck_type.value, version=DEFAULT_DECK_DEFINITION_VERSION ) @@ -51,12 +51,12 @@ def sync() -> DeckDefinitionV3: async def get_deck_fixed_labware( self, - deck_definition: DeckDefinitionV3, + deck_definition: DeckDefinitionV4, ) -> List[DeckFixedLabware]: """Get a list of all labware fixtures from a given deck definition.""" labware: List[DeckFixedLabware] = [] - for fixture in deck_definition["locations"]["fixtures"]: + for fixture in deck_definition["locations"]["legacyFixtures"]: labware_id = fixture["id"] load_name = cast(Optional[str], fixture.get("labware")) slot = cast(Optional[str], fixture.get("slot")) diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index c56edf4de5f..b40a00c7b65 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -16,7 +16,7 @@ cast, ) -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3, SlotDefV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4, SlotDefV3 from opentrons_shared_data.gripper.constants import LABWARE_GRIP_FORCE from opentrons_shared_data.labware.labware_definition import LabwareRole from opentrons_shared_data.pipette.dev_types import LabwareUri @@ -104,7 +104,7 @@ class LabwareState: labware_offsets_by_id: Dict[str, LabwareOffset] definitions_by_uri: Dict[str, LabwareDefinition] - deck_definition: DeckDefinitionV3 + deck_definition: DeckDefinitionV4 class LabwareStore(HasState[LabwareState], HandlesActions): @@ -114,7 +114,7 @@ class LabwareStore(HasState[LabwareState], HandlesActions): def __init__( self, - deck_definition: DeckDefinitionV3, + deck_definition: DeckDefinitionV4, deck_fixed_labware: Sequence[DeckFixedLabware], ) -> None: """Initialize a labware store and its state.""" @@ -304,7 +304,7 @@ def get_display_name(self, labware_id: str) -> Optional[str]: """Get the labware's user-specified display name, if set.""" return self.get(labware_id).displayName - def get_deck_definition(self) -> DeckDefinitionV3: + def get_deck_definition(self) -> DeckDefinitionV4: """Get the current deck definition.""" return self._state.deck_definition @@ -312,8 +312,54 @@ def get_slot_definition(self, slot: DeckSlotName) -> SlotDefV3: """Get the definition of a slot in the deck.""" deck_def = self.get_deck_definition() - for slot_def in deck_def["locations"]["orderedSlots"]: - if slot_def["id"] == slot.id: + # TODO(jbl 2023-10-19 this is all incredibly hacky and ultimately we should get rid of SlotDefV3, and maybe + # move all this to another store/provider. However for now, this can be more or less equivalent and not break + # things TM TM TM + + for cutout in deck_def["locations"]["cutouts"]: + if cutout["id"].endswith(slot.id): + base_position = cutout["position"] + break + else: + raise errors.SlotDoesNotExistError( + f"Slot ID {slot.id} does not exist in deck {deck_def['otId']}" + ) + + slot_def: SlotDefV3 + # Slot 12/fixed trash for ot2 is a little weird so if its that just return some hardcoded stuff + if slot.id == "12": + slot_def = { + "id": "12", + "position": base_position, + "boundingBox": { + "xDimension": 128.0, + "yDimension": 86.0, + "zDimension": 0, + }, + "displayName": "Slot 12", + "compatibleModuleTypes": [], + } + return slot_def + + for area in deck_def["locations"]["addressableAreas"]: + if area["id"] == slot.id: + offset = area["offsetFromCutoutFixture"] + position = [ + offset[0] + base_position[0], + offset[1] + base_position[1], + offset[2] + base_position[2], + ] + slot_def = { + "id": area["id"], + "position": position, + "boundingBox": area["boundingBox"], + "displayName": area["displayName"], + "compatibleModuleTypes": area["compatibleModuleTypes"], + } + if area.get("matingSurfaceUnitVector"): + slot_def["matingSurfaceUnitVector"] = area[ + "matingSurfaceUnitVector" + ] return slot_def raise errors.SlotDoesNotExistError( @@ -661,7 +707,7 @@ def find_applicable_labware_offset( return None - def get_fixed_trash_id(self) -> str: + def get_fixed_trash_id(self) -> Optional[str]: """Get the identifier of labware loaded into the fixed trash location. Raises: @@ -677,9 +723,7 @@ def get_fixed_trash_id(self) -> str: }: return labware.id - raise errors.LabwareNotLoadedError( - "No labware loaded into fixed trash location by this deck type." - ) + return None def is_fixed_trash(self, labware_id: str) -> bool: """Check if labware is fixed trash.""" diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 729bcf47383..8c8d7a366cb 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -37,6 +37,7 @@ thermocycler, heater_shaker, CommandPrivateResult, + PrepareToAspirateResult, ) from ..commands.configuring_common import PipetteConfigUpdateResultMixin from ..actions import ( @@ -221,6 +222,10 @@ def _handle_command( # noqa: C901 pipette_id = command.params.pipetteId self._state.aspirated_volume_by_id[pipette_id] = None + elif isinstance(command.result, PrepareToAspirateResult): + pipette_id = command.params.pipetteId + self._state.aspirated_volume_by_id[pipette_id] = 0 + def _update_current_well(self, command: Command) -> None: # These commands leave the pipette in a new well. # Update current_well to reflect that. diff --git a/api/src/opentrons/protocol_engine/state/state.py b/api/src/opentrons/protocol_engine/state/state.py index 7e4695e15e6..3c402701810 100644 --- a/api/src/opentrons/protocol_engine/state/state.py +++ b/api/src/opentrons/protocol_engine/state/state.py @@ -5,7 +5,7 @@ from functools import partial from typing import Any, Callable, Dict, List, Optional, Sequence, TypeVar -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons.protocol_engine.types import ModuleOffsetData @@ -126,7 +126,7 @@ def __init__( self, *, config: Config, - deck_definition: DeckDefinitionV3, + deck_definition: DeckDefinitionV4, deck_fixed_labware: Sequence[DeckFixedLabware], is_door_open: bool, change_notifier: Optional[ChangeNotifier] = None, diff --git a/api/src/opentrons/protocol_reader/file_format_validator.py b/api/src/opentrons/protocol_reader/file_format_validator.py index 7e8c6d3adf1..f13d1339041 100644 --- a/api/src/opentrons/protocol_reader/file_format_validator.py +++ b/api/src/opentrons/protocol_reader/file_format_validator.py @@ -10,7 +10,9 @@ from opentrons_shared_data.protocol.models import ( ProtocolSchemaV6 as JsonProtocolV6, ProtocolSchemaV7 as JsonProtocolV7, + ProtocolSchemaV8 as JsonProtocolV8, ) +from opentrons_shared_data.errors.exceptions import PythonException from opentrons.protocols.models import JsonProtocol as JsonProtocolUpToV5 @@ -51,7 +53,9 @@ def validate_sync() -> None: LabwareDefinition.parse_obj(info.unvalidated_json) except PydanticValidationError as e: raise FileFormatValidationError( - f"{info.original_file.name} could not be read as a labware definition." + message=f"{info.original_file.name} could not be read as a labware definition.", + detail={"kind": "bad-labware-definition"}, + wrapping=[PythonException(e)], ) from e await anyio.to_thread.run_sync(validate_sync) @@ -60,7 +64,9 @@ def validate_sync() -> None: async def _validate_json_protocol(info: IdentifiedJsonMain) -> None: def validate_sync() -> None: try: - if info.schema_version == 7: + if info.schema_version == 8: + JsonProtocolV8.parse_obj(info.unvalidated_json) + elif info.schema_version == 7: JsonProtocolV7.parse_obj(info.unvalidated_json) elif info.schema_version == 6: JsonProtocolV6.parse_obj(info.unvalidated_json) @@ -68,7 +74,9 @@ def validate_sync() -> None: JsonProtocolUpToV5.parse_obj(info.unvalidated_json) except PydanticValidationError as e: raise FileFormatValidationError( - f"{info.original_file.name} could not be read as a JSON protocol." + message=f"{info.original_file.name} could not be read as a JSON protocol.", + detail={"kind": "bad-json-protocol"}, + wrapping=[PythonException(e)], ) from e await anyio.to_thread.run_sync(validate_sync) diff --git a/api/src/opentrons/protocol_reader/file_identifier.py b/api/src/opentrons/protocol_reader/file_identifier.py index a6d5d3bd94b..2ef902b7c4c 100644 --- a/api/src/opentrons/protocol_reader/file_identifier.py +++ b/api/src/opentrons/protocol_reader/file_identifier.py @@ -2,11 +2,12 @@ import json from dataclasses import dataclass -from typing import Any, Dict, Sequence, Union +from typing import Any, Dict, Sequence, Union, Optional import anyio from opentrons_shared_data.robot.dev_types import RobotType +from opentrons_shared_data.errors.exceptions import EnumeratedError, PythonException from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION from opentrons.protocols.api_support.types import APIVersion @@ -96,6 +97,23 @@ class IdentifiedData: class FileIdentificationError(ProtocolFilesInvalidError): """Raised when FileIdentifier detects an invalid file.""" + def __init__( + self, + message: str, + detail: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + only_message: bool = False, + ) -> None: + super().__init__(message=message) + self._only_message = only_message + + def __str__(self) -> str: + """Special stringifier to conform to expecations about python protocol errors.""" + if self._only_message: + return self.message + else: + return super().__str__() + class FileIdentifier: """File identifier interface.""" @@ -128,7 +146,8 @@ async def _identify( return IdentifiedData(original_file=file) else: raise FileIdentificationError( - f"{file.name} has an unrecognized file extension." + message=f"{file.name} has an unrecognized file extension.", + detail={"type": "bad-file-extension", "file": file.name}, ) @@ -139,7 +158,9 @@ async def _analyze_json( json_contents = await anyio.to_thread.run_sync(json.loads, json_file.contents) except json.JSONDecodeError as e: raise FileIdentificationError( - f"{json_file.name} is not valid JSON. {str(e)}" + message=f"{json_file.name} is not valid JSON. {str(e)}", + detail={"type": "invalid-json", "file": json_file.name}, + wrapping=[PythonException(e)], ) from e if _json_seems_like_labware(json_contents): @@ -154,7 +175,8 @@ async def _analyze_json( ) else: raise FileIdentificationError( - f"{json_file.name} is not a known Opentrons format." + message=f"{json_file.name} is not a known Opentrons format.", + detail={"type": "no-schema-match", "file": json_file.name}, ) @@ -182,19 +204,34 @@ def _analyze_json_protocol( schema_version = json_contents["schemaVersion"] robot_type = json_contents["robot"]["model"] except KeyError as e: - raise FileIdentificationError(error_message) from e + raise FileIdentificationError( + message=error_message, + detail={"kind": "missing-json-metadata", "missing-key": str(e)}, + wrapping=[PythonException(e)], + ) from e # todo(mm, 2022-12-22): A JSON protocol file's metadata is not quite just an # arbitrary dict: its fields are supposed to follow a schema. Should we validate # this metadata against that schema instead of doing this simple isinstance() check? if not isinstance(metadata, dict): - raise FileIdentificationError(error_message) + raise FileIdentificationError( + message=error_message, detail={"kind": "json-metadata-not-object"} + ) if not isinstance(schema_version, int): - raise FileIdentificationError(error_message) + raise FileIdentificationError( + message=error_message, + detail={ + "kind": "json-schema-version-not-int", + "schema-version": schema_version, + }, + ) if robot_type not in ("OT-2 Standard", "OT-3 Standard"): - raise FileIdentificationError(error_message) + raise FileIdentificationError( + message=error_message, + detail={"kind": "bad-json-protocol-robot-type", "robot-type": robot_type}, + ) return IdentifiedJsonMain( original_file=original_file, @@ -216,7 +253,12 @@ def _analyze_python_protocol( python_parse_mode=python_parse_mode, ) except MalformedPythonProtocolError as e: - raise FileIdentificationError(e.short_message) from e + raise FileIdentificationError( + message=e.short_message, + detail={"kind": "malformed-python-protocol"}, + wrapping=[PythonException(e)], + only_message=True, + ) from e # We know this should never be a JsonProtocol. Help out the type-checker. assert isinstance( @@ -225,9 +267,13 @@ def _analyze_python_protocol( if parsed.api_level > MAX_SUPPORTED_VERSION: raise FileIdentificationError( - f"API version {parsed.api_level} is not supported by this " - f"robot software. Please either reduce your requested API " - f"version or update your robot." + message=( + f"API version {parsed.api_level} is not supported by this " + f"robot software. Please either reduce your requested API " + f"version or update your robot." + ), + detail={"kind": "future-api-version", "api-version": str(parsed.api_level)}, + only_message=True, ) return IdentifiedPythonMain( diff --git a/api/src/opentrons/protocol_reader/protocol_files_invalid_error.py b/api/src/opentrons/protocol_reader/protocol_files_invalid_error.py index 0d856b2e759..537ade554db 100644 --- a/api/src/opentrons/protocol_reader/protocol_files_invalid_error.py +++ b/api/src/opentrons/protocol_reader/protocol_files_invalid_error.py @@ -1,5 +1,6 @@ # noqa: D100 +from opentrons_shared_data.errors.exceptions import InvalidProtocolData -class ProtocolFilesInvalidError(ValueError): +class ProtocolFilesInvalidError(InvalidProtocolData): """Raised when the input to a ProtocolReader is not a well-formed protocol.""" diff --git a/api/src/opentrons/protocol_reader/role_analyzer.py b/api/src/opentrons/protocol_reader/role_analyzer.py index c6538324942..5c32f53711c 100644 --- a/api/src/opentrons/protocol_reader/role_analyzer.py +++ b/api/src/opentrons/protocol_reader/role_analyzer.py @@ -40,7 +40,9 @@ def analyze(files: Sequence[IdentifiedFile]) -> RoleAnalysis: This validates that there is exactly one main protocol file. """ if len(files) == 0: - raise RoleAnalysisError("No files were provided.") + raise RoleAnalysisError( + message="No files were provided.", detail={"kind": "no-files"} + ) main_file_candidates: List[Union[IdentifiedJsonMain, IdentifiedPythonMain]] = [] labware_files: List[IdentifiedLabwareDefinition] = [] @@ -57,17 +59,22 @@ def analyze(files: Sequence[IdentifiedFile]) -> RoleAnalysis: if len(main_file_candidates) == 0: if len(files) == 1: raise RoleAnalysisError( - f'"{files[0].original_file.name}" is not a valid protocol file.' + message=f'"{files[0].original_file.name}" is not a valid protocol file.', + detail={"kind": "protocol-does-not-have-main"}, ) else: file_list = ", ".join(f'"{f.original_file.name}"' for f in files) - raise RoleAnalysisError(f"No valid protocol file found in {file_list}.") + raise RoleAnalysisError( + message=f"No valid protocol file found in {file_list}.", + detail={"kind": "protocol-does-not-have-main"}, + ) elif len(main_file_candidates) > 1: file_list = ", ".join( f'"{f.original_file.name}"' for f in main_file_candidates ) raise RoleAnalysisError( - f"Could not pick single main file from {file_list}." + message=f"Could not pick single main file from {file_list}.", + detail={"kind": "multiple-main-candidates"}, ) else: main_file = main_file_candidates[0] diff --git a/api/src/opentrons/protocol_runner/create_simulating_runner.py b/api/src/opentrons/protocol_runner/create_simulating_runner.py index 9673db4408a..ff4df1020f7 100644 --- a/api/src/opentrons/protocol_runner/create_simulating_runner.py +++ b/api/src/opentrons/protocol_runner/create_simulating_runner.py @@ -3,6 +3,7 @@ from opentrons.config import feature_flags from opentrons.hardware_control import API as OT2API, HardwareControlAPI from opentrons.protocols.api_support import deck_type +from opentrons.protocols.api_support.deck_type import should_load_fixed_trash from opentrons.protocol_engine import ( Config as ProtocolEngineConfig, DeckType, @@ -58,6 +59,7 @@ async def create_simulating_runner( use_virtual_gripper=True, use_virtual_pipettes=(not feature_flags.disable_fast_protocol_upload()), ), + load_fixed_trash=should_load_fixed_trash(protocol_config), ) simulating_legacy_context_creator = LegacySimulatingContextCreator( diff --git a/api/src/opentrons/protocol_runner/json_file_reader.py b/api/src/opentrons/protocol_runner/json_file_reader.py index 41db89f5cbf..17f82ec8cee 100644 --- a/api/src/opentrons/protocol_runner/json_file_reader.py +++ b/api/src/opentrons/protocol_runner/json_file_reader.py @@ -3,7 +3,12 @@ from opentrons_shared_data.protocol.models.protocol_schema_v6 import ProtocolSchemaV6 from opentrons_shared_data.protocol.models.protocol_schema_v7 import ProtocolSchemaV7 -from opentrons.protocol_reader import ProtocolSource, JsonProtocolConfig +from opentrons_shared_data.protocol.models.protocol_schema_v8 import ProtocolSchemaV8 +from opentrons.protocol_reader import ( + ProtocolSource, + JsonProtocolConfig, + ProtocolFilesInvalidError, +) class JsonFileReader: @@ -12,12 +17,31 @@ class JsonFileReader: @staticmethod def read( protocol_source: ProtocolSource, - ) -> Union[ProtocolSchemaV6, ProtocolSchemaV7]: + ) -> Union[ProtocolSchemaV6, ProtocolSchemaV7, ProtocolSchemaV8]: """Read and parse file into a JsonProtocol model.""" - if ( - isinstance(protocol_source.config, JsonProtocolConfig) - and protocol_source.config.schema_version == 6 - ): + name = protocol_source.metadata.get("name", protocol_source.main_file.name) + if not isinstance(protocol_source.config, JsonProtocolConfig): + raise ProtocolFilesInvalidError( + message=f"Cannot execute {name} as a JSON protocol", + detail={ + "kind": "non-json-file-in-json-file-reader", + "metadata-name": protocol_source.metadata.get("name"), + "file-name": protocol_source.main_file.name, + }, + ) + if protocol_source.config.schema_version == 6: return ProtocolSchemaV6.parse_file(protocol_source.main_file) - else: + elif protocol_source.config.schema_version == 7: return ProtocolSchemaV7.parse_file(protocol_source.main_file) + elif protocol_source.config.schema_version == 8: + return ProtocolSchemaV8.parse_file(protocol_source.main_file) + else: + raise ProtocolFilesInvalidError( + message=f"{name} is a JSON protocol v{protocol_source.config.schema_version} which this robot cannot execute", + detail={ + "kind": "schema-version-unknown", + "requested-schema-version": protocol_source.config.schema_version, + "minimum-handled-schema-version": "6", + "maximum-handled-shcema-version": "8", + }, + ) diff --git a/api/src/opentrons/protocol_runner/json_translator.py b/api/src/opentrons/protocol_runner/json_translator.py index 88cabc13149..c210d51ec77 100644 --- a/api/src/opentrons/protocol_runner/json_translator.py +++ b/api/src/opentrons/protocol_runner/json_translator.py @@ -8,7 +8,10 @@ protocol_schema_v6, ProtocolSchemaV7, protocol_schema_v7, + ProtocolSchemaV8, + protocol_schema_v8, ) +from opentrons_shared_data import command as command_schema from opentrons.types import MountType from opentrons.protocol_engine import ( @@ -163,7 +166,11 @@ def _translate_v7_pipette_command( def _translate_simple_command( - command: Union[protocol_schema_v6.Command, protocol_schema_v7.Command] + command: Union[ + protocol_schema_v6.Command, + protocol_schema_v7.Command, + protocol_schema_v8.Command, + ] ) -> pe_commands.CommandCreate: dict_command = command.dict(exclude_none=True) @@ -208,13 +215,15 @@ def translate_liquids( def translate_commands( self, - protocol: Union[ProtocolSchemaV7, ProtocolSchemaV6], + protocol: Union[ProtocolSchemaV8, ProtocolSchemaV7, ProtocolSchemaV6], ) -> List[pe_commands.CommandCreate]: """Takes json protocol and translates commands->protocol engine commands.""" if isinstance(protocol, ProtocolSchemaV6): return self._translate_v6_commands(protocol) - else: + elif isinstance(protocol, ProtocolSchemaV7): return self._translate_v7_commands(protocol) + else: + return self._translate_v8_commands(protocol) def _translate_v6_commands( self, @@ -244,3 +253,18 @@ def _translate_v7_commands( translated_obj = _translate_simple_command(command) commands_list.append(translated_obj) return commands_list + + def _translate_v8_commands( + self, protocol: ProtocolSchemaV8 + ) -> List[pe_commands.CommandCreate]: + """Translate commands in json protocol schema v8, which might be of different command schemas.""" + command_schema_ref = protocol.commandSchemaId + # these calls will raise if the command schema version is invalid or unknown + command_schema_version = command_schema.schema_version_from_ref( + command_schema_ref + ) + command_schema_string = command_schema.load_schema_string( # noqa: F841 + command_schema_version + ) + + return [_translate_simple_command(command) for command in protocol.commands] diff --git a/api/src/opentrons/protocols/api_support/deck_type.py b/api/src/opentrons/protocols/api_support/deck_type.py index e23599d29bc..f0cadebce43 100644 --- a/api/src/opentrons/protocols/api_support/deck_type.py +++ b/api/src/opentrons/protocols/api_support/deck_type.py @@ -1,7 +1,18 @@ +from typing import Sequence, Dict, Optional, Any + from opentrons_shared_data.robot.dev_types import RobotType +from opentrons_shared_data.errors import ErrorCodes +from opentrons_shared_data.errors.exceptions import EnumeratedError from opentrons.config import feature_flags +from opentrons.protocol_reader.protocol_source import ( + ProtocolConfig, + PythonProtocolConfig, + JsonProtocolConfig, +) +from opentrons.protocols.api_support.types import APIVersion + # TODO(mm, 2023-05-10): Deduplicate these constants with # opentrons.protocol_engine.types.DeckType and consider moving to shared-data. @@ -10,6 +21,48 @@ STANDARD_OT3_DECK = "ot3_standard" +LOAD_FIXED_TRASH_GATE_VERSION_PYTHON = APIVersion(2, 15) +LOAD_FIXED_TRASH_GATE_VERSION_JSON = 7 + + +class NoTrashDefinedError(EnumeratedError): + """ + Error raised when a protocol attempts to automatically access a trash bin without one being loaded. + """ + + def __init__( + self, + message: Optional[str] = None, + detail: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a ProtocolEngineError.""" + super().__init__( + code=ErrorCodes.GENERAL_ERROR, + message=message, + detail=detail, + wrapping=wrapping, + ) + + +def should_load_fixed_trash_for_python_protocol(api_version: APIVersion) -> bool: + return api_version <= LOAD_FIXED_TRASH_GATE_VERSION_PYTHON + + +def should_load_fixed_trash(protocol_config: ProtocolConfig) -> bool: + """Decide whether to automatically load fixed trash on the deck based on version.""" + load_fixed_trash = False + if isinstance(protocol_config, PythonProtocolConfig): + return should_load_fixed_trash_for_python_protocol(protocol_config.api_version) + # TODO(jbl 2023-10-27), when schema v8 is out, use a new deck version field to support fixed trash protocols + elif isinstance(protocol_config, JsonProtocolConfig): + load_fixed_trash = ( + protocol_config.schema_version <= LOAD_FIXED_TRASH_GATE_VERSION_JSON + ) + + return load_fixed_trash + + def guess_from_global_config() -> str: """Return the deck type that the host device physically has. diff --git a/api/src/opentrons/simulate.py b/api/src/opentrons/simulate.py index 3c74d088994..2dc744432c0 100644 --- a/api/src/opentrons/simulate.py +++ b/api/src/opentrons/simulate.py @@ -64,6 +64,8 @@ ) from opentrons.protocols.api_support.deck_type import ( for_simulation as deck_type_for_simulation, + should_load_fixed_trash, + should_load_fixed_trash_for_python_protocol, ) from opentrons.protocols.api_support.types import APIVersion from opentrons_shared_data.labware.labware_definition import LabwareDefinition @@ -792,6 +794,7 @@ def _create_live_context_pe( config=_get_protocol_engine_config(robot_type), drop_tips_after_run=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, + load_fixed_trash=should_load_fixed_trash_for_python_protocol(api_version), ) ) @@ -886,6 +889,7 @@ async def run(protocol_source: ProtocolSource) -> _SimulateResult: protocol_engine = await create_protocol_engine( hardware_api=hardware_api.wrapped(), config=_get_protocol_engine_config(robot_type), + load_fixed_trash=should_load_fixed_trash(protocol_source.config), ) protocol_runner = create_protocol_runner( diff --git a/api/tests/opentrons/calibration_storage/test_gripper_offset.py b/api/tests/opentrons/calibration_storage/test_gripper_offset.py index f9601258305..a2958cada04 100644 --- a/api/tests/opentrons/calibration_storage/test_gripper_offset.py +++ b/api/tests/opentrons/calibration_storage/test_gripper_offset.py @@ -1,13 +1,11 @@ import pytest import typing -import opentrons -import importlib from opentrons.types import Point from opentrons.calibration_storage import ( types as cs_types, - ot3_gripper_offset as gripper, ) +from opentrons.calibration_storage.ot3 import gripper_offset as gripper, models @pytest.fixture @@ -62,9 +60,6 @@ def test_get_gripper_calibration( """ Test ability to get a gripper calibration schema. """ - importlib.reload(opentrons.calibration_storage) - from opentrons.calibration_storage import models - gripper_data = gripper.get_gripper_calibration_offset("gripper1") assert gripper_data is not None assert gripper_data == models.v1.InstrumentOffsetModel( diff --git a/api/tests/opentrons/calibration_storage/test_pipette_offset.py b/api/tests/opentrons/calibration_storage/test_pipette_offset.py deleted file mode 100644 index d4b8ef542ab..00000000000 --- a/api/tests/opentrons/calibration_storage/test_pipette_offset.py +++ /dev/null @@ -1,132 +0,0 @@ -import pytest -import importlib -import opentrons -from typing import Any, TYPE_CHECKING - -from opentrons.types import Mount, Point -from opentrons.calibration_storage import ( - types as cs_types, -) - -if TYPE_CHECKING: - from opentrons_shared_data.deck.dev_types import RobotModel - - -@pytest.fixture(autouse=True) -def reload_module(robot_model: "RobotModel") -> None: - importlib.reload(opentrons.calibration_storage) - - -@pytest.fixture -def starting_calibration_data( - robot_model: "RobotModel", ot_config_tempdir: Any -) -> None: - """ - Starting calibration data fixture. - - Adds dummy data to a temporary directory to test delete commands against. - """ - from opentrons.calibration_storage import save_pipette_calibration - - if robot_model == "OT-3 Standard": - save_pipette_calibration(Point(1, 1, 1), "pip1", Mount.LEFT) - save_pipette_calibration(Point(1, 1, 1), "pip2", Mount.RIGHT) - else: - save_pipette_calibration( - Point(1, 1, 1), "pip1", Mount.LEFT, "mytiprack", "opentrons/tip_rack/1" - ) - save_pipette_calibration( - Point(1, 1, 1), "pip2", Mount.RIGHT, "mytiprack", "opentrons/tip_rack/1" - ) - - -def test_delete_all_pipette_calibration(starting_calibration_data: Any) -> None: - """ - Test delete all pipette calibrations. - """ - from opentrons.calibration_storage import ( - get_pipette_offset, - clear_pipette_offset_calibrations, - ) - - assert get_pipette_offset("pip1", Mount.LEFT) is not None - assert get_pipette_offset("pip2", Mount.RIGHT) is not None - clear_pipette_offset_calibrations() - assert get_pipette_offset("pip1", Mount.LEFT) is None - assert get_pipette_offset("pip2", Mount.RIGHT) is None - - -def test_delete_specific_pipette_offset(starting_calibration_data: Any) -> None: - """ - Test delete a specific pipette calibration. - """ - from opentrons.calibration_storage import ( - get_pipette_offset, - delete_pipette_offset_file, - ) - - assert get_pipette_offset("pip1", Mount.LEFT) is not None - delete_pipette_offset_file("pip1", Mount.LEFT) - assert get_pipette_offset("pip1", Mount.LEFT) is None - - -def test_save_pipette_calibration( - ot_config_tempdir: Any, robot_model: "RobotModel" -) -> None: - """ - Test saving pipette calibrations. - """ - from opentrons.calibration_storage import ( - get_pipette_offset, - save_pipette_calibration, - ) - - assert get_pipette_offset("pip1", Mount.LEFT) is None - if robot_model == "OT-3 Standard": - save_pipette_calibration(Point(1, 1, 1), "pip1", Mount.LEFT) - save_pipette_calibration(Point(1, 1, 1), "pip2", Mount.RIGHT) - else: - save_pipette_calibration( - Point(1, 1, 1), "pip1", Mount.LEFT, "mytiprack", "opentrons/tip_rack/1" - ) - save_pipette_calibration( - Point(1, 1, 1), "pip2", Mount.RIGHT, "mytiprack", "opentrons/tip_rack/1" - ) - assert get_pipette_offset("pip1", Mount.LEFT) is not None - assert get_pipette_offset("pip2", Mount.RIGHT) is not None - assert get_pipette_offset("pip1", Mount.LEFT).offset == Point(1, 1, 1) - assert get_pipette_offset("pip1", Mount.LEFT).offset == Point(1, 1, 1) - - -def test_get_pipette_calibration( - starting_calibration_data: Any, robot_model: "RobotModel" -) -> None: - """ - Test ability to get a pipette calibration model. - """ - from opentrons.calibration_storage import get_pipette_offset - - # needed for proper type checking unfortunately - from opentrons.calibration_storage.ot3.models.v1 import ( - InstrumentOffsetModel as OT3InstrModel, - ) - from opentrons.calibration_storage.ot2.models.v1 import ( - InstrumentOffsetModel as OT2InstrModel, - ) - - pipette_data = get_pipette_offset("pip1", Mount.LEFT) - if robot_model == "OT-3 Standard": - assert pipette_data == OT3InstrModel( - offset=Point(1, 1, 1), - lastModified=pipette_data.lastModified, - source=cs_types.SourceType.user, - ) - else: - - assert pipette_data == OT2InstrModel( - offset=Point(1, 1, 1), - tiprack="mytiprack", - uri="opentrons/tip_rack/1", - last_modified=pipette_data.last_modified, - source=cs_types.SourceType.user, - ) diff --git a/api/tests/opentrons/calibration_storage/test_pipette_offset_ot2.py b/api/tests/opentrons/calibration_storage/test_pipette_offset_ot2.py new file mode 100644 index 00000000000..1411c7fada7 --- /dev/null +++ b/api/tests/opentrons/calibration_storage/test_pipette_offset_ot2.py @@ -0,0 +1,96 @@ +import pytest +from typing import Any, TYPE_CHECKING + +from opentrons.types import Mount, Point +from opentrons.calibration_storage import ( + types as cs_types, +) +from opentrons.calibration_storage.ot2 import ( + save_pipette_calibration, + get_pipette_offset, + clear_pipette_offset_calibrations, + delete_pipette_offset_file, +) + +if TYPE_CHECKING: + from opentrons_shared_data.deck.dev_types import RobotModel + + +@pytest.fixture +def starting_calibration_data( + robot_model: "RobotModel", ot_config_tempdir: Any +) -> None: + """ + Starting calibration data fixture. + + Adds dummy data to a temporary directory to test delete commands against. + """ + save_pipette_calibration( + Point(1, 1, 1), "pip1", Mount.LEFT, "mytiprack", "opentrons/tip_rack/1" + ) + save_pipette_calibration( + Point(1, 1, 1), "pip2", Mount.RIGHT, "mytiprack", "opentrons/tip_rack/1" + ) + + +def test_delete_all_pipette_calibration(starting_calibration_data: Any) -> None: + """ + Test delete all pipette calibrations. + """ + assert get_pipette_offset("pip1", Mount.LEFT) is not None + assert get_pipette_offset("pip2", Mount.RIGHT) is not None + clear_pipette_offset_calibrations() + assert get_pipette_offset("pip1", Mount.LEFT) is None + assert get_pipette_offset("pip2", Mount.RIGHT) is None + + +def test_delete_specific_pipette_offset(starting_calibration_data: Any) -> None: + """ + Test delete a specific pipette calibration. + """ + assert get_pipette_offset("pip1", Mount.LEFT) is not None + delete_pipette_offset_file("pip1", Mount.LEFT) + assert get_pipette_offset("pip1", Mount.LEFT) is None + + +def test_save_pipette_calibration( + ot_config_tempdir: Any, robot_model: "RobotModel" +) -> None: + """ + Test saving pipette calibrations. + """ + assert get_pipette_offset("pip1", Mount.LEFT) is None + save_pipette_calibration( + Point(1, 1, 1), "pip1", Mount.LEFT, "mytiprack", "opentrons/tip_rack/1" + ) + save_pipette_calibration( + Point(1, 1, 1), "pip2", Mount.RIGHT, "mytiprack", "opentrons/tip_rack/1" + ) + assert get_pipette_offset("pip1", Mount.LEFT) is not None + assert get_pipette_offset("pip2", Mount.RIGHT) is not None + pip1 = get_pipette_offset("pip1", Mount.LEFT) + assert pip1 is not None and pip1.offset == Point(1, 1, 1) + pip2 = get_pipette_offset("pip1", Mount.LEFT) + assert pip2 is not None and pip2.offset == Point(1, 1, 1) + + +def test_get_pipette_calibration( + starting_calibration_data: Any, robot_model: "RobotModel" +) -> None: + """ + Test ability to get a pipette calibration model. + """ + from opentrons.calibration_storage.ot2.models.v1 import ( + InstrumentOffsetModel as OT2InstrModel, + ) + + pipette_data = get_pipette_offset("pip1", Mount.LEFT) + assert pipette_data is not None + + assert pipette_data == OT2InstrModel( + offset=Point(1, 1, 1), + tiprack="mytiprack", + uri="opentrons/tip_rack/1", + last_modified=pipette_data.last_modified, + source=cs_types.SourceType.user, + ) diff --git a/api/tests/opentrons/calibration_storage/test_pipette_offset_ot3.py b/api/tests/opentrons/calibration_storage/test_pipette_offset_ot3.py new file mode 100644 index 00000000000..7ce0603083c --- /dev/null +++ b/api/tests/opentrons/calibration_storage/test_pipette_offset_ot3.py @@ -0,0 +1,84 @@ +import pytest +from typing import Any, TYPE_CHECKING + +from opentrons.types import Mount, Point +from opentrons.calibration_storage import ( + types as cs_types, +) +from opentrons.calibration_storage.ot3 import ( + save_pipette_calibration, + get_pipette_offset, + clear_pipette_offset_calibrations, + delete_pipette_offset_file, +) + +if TYPE_CHECKING: + from opentrons_shared_data.deck.dev_types import RobotModel + + +@pytest.fixture +def starting_calibration_data( + robot_model: "RobotModel", ot_config_tempdir: Any +) -> None: + """ + Starting calibration data fixture. + + Adds dummy data to a temporary directory to test delete commands against. + """ + save_pipette_calibration(Point(1, 1, 1), "pip1", Mount.LEFT) + save_pipette_calibration(Point(1, 1, 1), "pip2", Mount.RIGHT) + + +def test_delete_all_pipette_calibration(starting_calibration_data: Any) -> None: + """ + Test delete all pipette calibrations. + """ + assert get_pipette_offset("pip1", Mount.LEFT) is not None + assert get_pipette_offset("pip2", Mount.RIGHT) is not None + clear_pipette_offset_calibrations() + assert get_pipette_offset("pip1", Mount.LEFT) is None + assert get_pipette_offset("pip2", Mount.RIGHT) is None + + +def test_delete_specific_pipette_offset(starting_calibration_data: Any) -> None: + """ + Test delete a specific pipette calibration. + """ + assert get_pipette_offset("pip1", Mount.LEFT) is not None + delete_pipette_offset_file("pip1", Mount.LEFT) + assert get_pipette_offset("pip1", Mount.LEFT) is None + + +def test_save_pipette_calibration( + ot_config_tempdir: Any, robot_model: "RobotModel" +) -> None: + """ + Test saving pipette calibrations. + """ + assert get_pipette_offset("pip1", Mount.LEFT) is None + save_pipette_calibration(Point(1, 1, 1), "pip1", Mount.LEFT) + save_pipette_calibration(Point(1, 1, 1), "pip2", Mount.RIGHT) + + assert get_pipette_offset("pip1", Mount.LEFT) is not None + assert get_pipette_offset("pip2", Mount.RIGHT) is not None + assert get_pipette_offset("pip1", Mount.LEFT).offset == Point(1, 1, 1) + assert get_pipette_offset("pip1", Mount.LEFT).offset == Point(1, 1, 1) + + +def test_get_pipette_calibration( + starting_calibration_data: Any, robot_model: "RobotModel" +) -> None: + """ + Test ability to get a pipette calibration model. + """ + # needed for proper type checking unfortunately + from opentrons.calibration_storage.ot3.models.v1 import ( + InstrumentOffsetModel as OT3InstrModel, + ) + + pipette_data = get_pipette_offset("pip1", Mount.LEFT) + assert pipette_data == OT3InstrModel( + offset=Point(1, 1, 1), + lastModified=pipette_data.lastModified, + source=cs_types.SourceType.user, + ) diff --git a/api/tests/opentrons/calibration_storage/test_tip_length.py b/api/tests/opentrons/calibration_storage/test_tip_length_ot2.py similarity index 79% rename from api/tests/opentrons/calibration_storage/test_tip_length.py rename to api/tests/opentrons/calibration_storage/test_tip_length_ot2.py index 5c69f655593..93a208e0071 100644 --- a/api/tests/opentrons/calibration_storage/test_tip_length.py +++ b/api/tests/opentrons/calibration_storage/test_tip_length_ot2.py @@ -1,6 +1,4 @@ import pytest -import importlib -import opentrons from typing import cast, Any, TYPE_CHECKING from opentrons.calibration_storage import ( @@ -8,15 +6,19 @@ helpers, ) +from opentrons.calibration_storage.ot2 import ( + create_tip_length_data, + save_tip_length_calibration, + tip_lengths_for_pipette, + load_tip_length_calibration, + delete_tip_length_calibration, + clear_tip_length_calibration, + models, +) + if TYPE_CHECKING: from opentrons_shared_data.labware.dev_types import LabwareDefinition from opentrons_shared_data.pipette.dev_types import LabwareUri - from opentrons_shared_data.deck.dev_types import RobotModel - - -@pytest.fixture(autouse=True) -def reload_module(robot_model: "RobotModel") -> None: - importlib.reload(opentrons.calibration_storage) @pytest.fixture @@ -30,11 +32,6 @@ def starting_calibration_data( Adds dummy data to a temporary directory to test delete commands against. """ - from opentrons.calibration_storage import ( - create_tip_length_data, - save_tip_length_calibration, - ) - tip_length1 = create_tip_length_data(minimal_labware_def, 22.0) tip_length2 = create_tip_length_data(minimal_labware_def, 31.0) tip_length3 = create_tip_length_data(minimal_labware_def2, 31.0) @@ -49,12 +46,6 @@ def test_save_tip_length_calibration( """ Test saving tip length calibrations. """ - from opentrons.calibration_storage import ( - tip_lengths_for_pipette, - create_tip_length_data, - save_tip_length_calibration, - ) - assert tip_lengths_for_pipette("pip1") == {} assert tip_lengths_for_pipette("pip2") == {} tip_rack_hash = helpers.hash_labware_def(minimal_labware_def) @@ -72,8 +63,6 @@ def test_get_tip_length_calibration( """ Test ability to get a tip length calibration model. """ - from opentrons.calibration_storage import load_tip_length_calibration, models - tip_length_data = load_tip_length_calibration("pip1", minimal_labware_def) assert tip_length_data == models.v1.TipLengthModel( tipLength=22.0, @@ -92,11 +81,6 @@ def test_delete_specific_tip_calibration( """ Test delete a specific tip length calibration. """ - from opentrons.calibration_storage import ( - tip_lengths_for_pipette, - delete_tip_length_calibration, - ) - assert len(tip_lengths_for_pipette("pip1").keys()) == 2 assert tip_lengths_for_pipette("pip2") != {} tip_rack_hash = helpers.hash_labware_def(minimal_labware_def) @@ -109,11 +93,6 @@ def test_delete_all_tip_calibration(starting_calibration_data: Any) -> None: """ Test delete all tip length calibration. """ - from opentrons.calibration_storage import ( - tip_lengths_for_pipette, - clear_tip_length_calibration, - ) - assert tip_lengths_for_pipette("pip1") != {} assert tip_lengths_for_pipette("pip2") != {} clear_tip_length_calibration() diff --git a/api/tests/opentrons/config/test_reset.py b/api/tests/opentrons/config/test_reset.py index c0a2514efea..3561412bdb0 100644 --- a/api/tests/opentrons/config/test_reset.py +++ b/api/tests/opentrons/config/test_reset.py @@ -4,10 +4,17 @@ from opentrons.config import reset +from opentrons_shared_data.robot.dev_types import RobotTypeEnum + if TYPE_CHECKING: from opentrons_shared_data.deck.dev_types import RobotModel +@pytest.fixture +def robot_type_enum(robot_model: "RobotModel") -> RobotTypeEnum: + return RobotTypeEnum.OT2 if robot_model == "OT-2 Standard" else RobotTypeEnum.FLEX + + @pytest.fixture def mock_reset_boot_scripts() -> Generator[MagicMock, None, None]: with patch("opentrons.config.reset.reset_boot_scripts") as m: @@ -36,7 +43,10 @@ def mock_reset_tip_length_calibrations() -> Generator[MagicMock, None, None]: def mock_cal_pipette_offset( robot_model: "RobotModel", ) -> Generator[MagicMock, None, None]: - with patch("opentrons.config.reset.clear_pipette_offset_calibrations") as m: + prefix = "ot2" if robot_model == "OT-2 Standard" else "ot3" + with patch( + f"opentrons.config.reset.{prefix}.clear_pipette_offset_calibrations" + ) as m: yield m @@ -50,7 +60,7 @@ def mock_cal_gripper_offset( @pytest.fixture def mock_cal_tip_length(robot_model: "RobotModel") -> Generator[MagicMock, None, None]: - with patch("opentrons.config.reset.clear_tip_length_calibration") as m: + with patch("opentrons.config.reset.ot2.clear_tip_length_calibration") as m: yield m @@ -71,10 +81,10 @@ def mock_cal_module_offset( def test_get_options() -> None: - options = reset.reset_options("OT-2 Standard") + options = reset.reset_options(RobotTypeEnum.OT2) assert list(options.keys()) == reset._OT_2_RESET_OPTIONS - options = reset.reset_options("OT-3 Standard") + options = reset.reset_options(RobotTypeEnum.FLEX) assert list(options.keys()) == reset._FLEX_RESET_OPTIONS @@ -84,8 +94,9 @@ def test_reset_empty_set( mock_reset_deck_calibration: MagicMock, mock_reset_tip_length_calibrations: MagicMock, mock_cal_module_offset: MagicMock, + robot_type_enum: RobotTypeEnum, ) -> None: - reset.reset(set()) + reset.reset(set(), robot_type_enum) mock_reset_boot_scripts.assert_not_called() mock_reset_pipette_offset.assert_not_called() mock_reset_deck_calibration.assert_not_called() @@ -98,6 +109,7 @@ def test_reset_all_set( mock_reset_pipette_offset: MagicMock, mock_reset_deck_calibration: MagicMock, mock_reset_tip_length_calibrations: MagicMock, + robot_type_enum: RobotTypeEnum, ) -> None: reset.reset( { @@ -105,7 +117,8 @@ def test_reset_all_set( reset.ResetOptionId.deck_calibration, reset.ResetOptionId.pipette_offset, reset.ResetOptionId.tip_length_calibrations, - } + }, + robot_type_enum, ) mock_reset_boot_scripts.assert_called_once() mock_reset_pipette_offset.assert_called_once() @@ -114,23 +127,36 @@ def test_reset_all_set( def test_deck_calibration_reset( - mock_cal_pipette_offset: MagicMock, mock_cal_robot_attitude: MagicMock + mock_cal_pipette_offset: MagicMock, + mock_cal_robot_attitude: MagicMock, + robot_type_enum: RobotTypeEnum, ) -> None: - reset.reset_deck_calibration() + reset.reset_deck_calibration(robot_type_enum) mock_cal_robot_attitude.assert_called_once() mock_cal_pipette_offset.assert_called_once() +@pytest.mark.ot2_only def test_tip_length_calibrations_reset( - mock_cal_pipette_offset: MagicMock, mock_cal_tip_length: MagicMock + mock_cal_pipette_offset: MagicMock, + mock_cal_tip_length: MagicMock, ) -> None: - reset.reset_tip_length_calibrations() + reset.reset_tip_length_calibrations(RobotTypeEnum.OT2) mock_cal_tip_length.assert_called_once() mock_cal_pipette_offset.assert_called_once() -def test_pipette_offset_reset(mock_cal_pipette_offset: MagicMock) -> None: - reset.reset_pipette_offset() +@pytest.mark.ot3_only +def test_tip_length_calibrations_reset_ot3() -> None: + with pytest.raises(reset.UnrecognizedOption): + reset.reset_tip_length_calibrations(RobotTypeEnum.FLEX) + + +def test_pipette_offset_reset( + mock_cal_pipette_offset: MagicMock, + robot_type_enum: RobotTypeEnum, +) -> None: + reset.reset_pipette_offset(robot_type_enum) mock_cal_pipette_offset.assert_called_once() diff --git a/api/tests/opentrons/conftest.py b/api/tests/opentrons/conftest.py index 9d9d2c8d25c..979fe9b936a 100755 --- a/api/tests/opentrons/conftest.py +++ b/api/tests/opentrons/conftest.py @@ -36,7 +36,11 @@ from opentrons_shared_data.protocol.dev_types import JsonProtocol from opentrons_shared_data.labware.dev_types import LabwareDefinition from opentrons_shared_data.module.dev_types import ModuleDefinitionV3 -from opentrons_shared_data.deck.dev_types import RobotModel, DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import ( + RobotModel, + DeckDefinitionV3, + DeckDefinitionV4, +) from opentrons_shared_data.deck import ( load as load_deck, DEFAULT_DECK_DEFINITION_VERSION, @@ -53,6 +57,9 @@ ) from opentrons.protocol_api import ProtocolContext, Labware, create_protocol_context from opentrons.protocol_api.core.legacy.legacy_labware_core import LegacyLabwareCore +from opentrons.protocol_api.core.legacy.deck import ( + DEFAULT_LEGACY_DECK_DEFINITION_VERSION, +) from opentrons.protocol_engine import ( create_protocol_engine_in_thread, Config as ProtocolEngineConfig, @@ -248,10 +255,15 @@ def deck_definition_name(robot_model: RobotModel) -> str: @pytest.fixture -def deck_definition(deck_definition_name: str) -> DeckDefinitionV3: +def deck_definition(deck_definition_name: str) -> DeckDefinitionV4: return load_deck(deck_definition_name, DEFAULT_DECK_DEFINITION_VERSION) +@pytest.fixture +def legacy_deck_definition(deck_definition_name: str) -> DeckDefinitionV3: + return load_deck(deck_definition_name, DEFAULT_LEGACY_DECK_DEFINITION_VERSION) + + @pytest.fixture() async def hardware( request: pytest.FixtureRequest, @@ -300,6 +312,9 @@ def _make_ot3_pe_ctx( ), drop_tips_after_run=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, + # TODO(jbl 10-30-2023) load_fixed_trash being hardcoded to True will be refactored once we need tests to have + # this be False + load_fixed_trash=True, ) as ( engine, loop, 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 e85f23557d5..79baa8d868a 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py @@ -49,8 +49,6 @@ SubSystemState, UpdateStatus, UpdateState, - TipStateType, - FailedTipStateCheck, EstopState, CurrentConfig, ) @@ -1074,30 +1072,6 @@ async def test_monitor_pressure( mock_move_group_run.assert_called_once() -@pytest.mark.parametrize( - "tip_state_type, mocked_ejector_response, expectation", - [ - [TipStateType.PRESENT, {0: 1, 1: 1}, does_not_raise()], - [TipStateType.ABSENT, {0: 0, 1: 0}, does_not_raise()], - [TipStateType.PRESENT, {0: 0, 1: 0}, pytest.raises(FailedTipStateCheck)], - [TipStateType.ABSENT, {0: 1, 1: 1}, pytest.raises(FailedTipStateCheck)], - ], -) -async def test_get_tip_present( - controller: OT3Controller, - tip_state_type: TipStateType, - mocked_ejector_response: Dict[int, int], - expectation: ContextManager[None], -) -> None: - mount = OT3Mount.LEFT - with mock.patch( - "opentrons.hardware_control.backends.ot3controller.get_tip_ejector_state", - return_value=mocked_ejector_response, - ): - with expectation: - await controller.check_for_tip_presence(mount, tip_state_type) - - @pytest.mark.parametrize( "estop_state, expectation", [ diff --git a/api/tests/opentrons/hardware_control/backends/test_tip_presence_manager.py b/api/tests/opentrons/hardware_control/backends/test_tip_presence_manager.py new file mode 100644 index 00000000000..ce2dbf668d8 --- /dev/null +++ b/api/tests/opentrons/hardware_control/backends/test_tip_presence_manager.py @@ -0,0 +1,129 @@ +import pytest +from typing import AsyncIterator, Dict +from decoy import Decoy + +from opentrons.hardware_control.types import OT3Mount, TipStateType +from opentrons.hardware_control.backends.tip_presence_manager import TipPresenceManager +from opentrons_hardware.hardware_control.tip_presence import ( + TipDetector, + types as tp_types, +) +from opentrons_hardware.firmware_bindings.constants import SensorId +from opentrons_hardware.drivers import can_bus + +from opentrons_shared_data.errors.exceptions import UnmatchedTipPresenceStates + + +@pytest.fixture +def can_messenger(decoy: Decoy) -> can_bus.CanMessenger: + """Build a decoyed can messenger.""" + return decoy.mock(cls=can_bus.CanMessenger) + + +@pytest.fixture +def tip_detector(decoy: Decoy) -> TipDetector: + return decoy.mock(cls=TipDetector) + + +class TipDetectorController: + def __init__(self, tip_detector: TipDetector, decoy: Decoy) -> None: + self._tip_detector = tip_detector + self._decoy = decoy + + async def retrieve_tip_status(self, tip_presence: bool) -> None: + tip_notif = tp_types.TipNotification(sensor=SensorId.S0, presence=tip_presence) + self._decoy.when(await self._tip_detector.request_tip_status()).then_return( + [tip_notif] + ) + + async def retrieve_tip_status_highthroughput( + self, tip_presences: Dict[SensorId, bool] + ) -> None: + tip_notif = [ + tp_types.TipNotification(sensor=sensor_id, presence=presence) + for sensor_id, presence in tip_presences.items() + ] + self._decoy.when(await self._tip_detector.request_tip_status()).then_return( + tip_notif + ) + + +@pytest.fixture +def tip_detector_controller( + tip_detector: TipDetector, + decoy: Decoy, +) -> TipDetectorController: + return TipDetectorController(tip_detector, decoy) + + +@pytest.fixture +async def subject( + can_messenger: can_bus.CanMessenger, + tip_detector: TipDetector, +) -> AsyncIterator[TipPresenceManager]: + """Build a test subject using decoyed can messenger and tip detector.""" + manager = TipPresenceManager(can_messenger) + manager.set_detector(OT3Mount.LEFT, tip_detector) + try: + yield manager + finally: + return + + +@pytest.mark.parametrize( + "tip_presence,expected_type", + [ + (False, TipStateType.ABSENT), + (True, TipStateType.PRESENT), + ], +) +async def test_get_tip_status_for_low_throughput( + subject: TipPresenceManager, + tip_detector_controller: TipDetectorController, + tip_presence: bool, + expected_type: TipStateType, +) -> None: + mount = OT3Mount.LEFT + await tip_detector_controller.retrieve_tip_status(tip_presence) + + result = await subject.get_tip_status(mount) + result == expected_type + + +@pytest.mark.parametrize( + "tip_presence,expected_type", + [ + ({SensorId.S0: False, SensorId.S1: False}, TipStateType.ABSENT), + ({SensorId.S0: True, SensorId.S1: True}, TipStateType.PRESENT), + ], +) +async def test_get_tip_status_for_high_throughput( + subject: TipPresenceManager, + tip_detector_controller: TipDetectorController, + tip_presence: Dict[SensorId, bool], + expected_type: TipStateType, +) -> None: + mount = OT3Mount.LEFT + await tip_detector_controller.retrieve_tip_status_highthroughput(tip_presence) + + result = await subject.get_tip_status(mount) + result == expected_type + + +@pytest.mark.parametrize( + "tip_presence", + [ + {SensorId.S0: True, SensorId.S1: False}, + {SensorId.S0: False, SensorId.S1: True}, + ], +) +async def test_unmatched_tip_responses_should_raise( + subject: TipPresenceManager, + tip_detector_controller: TipDetectorController, + tip_presence: Dict[SensorId, bool], +) -> None: + mount = OT3Mount.LEFT + await tip_detector_controller.retrieve_tip_status_highthroughput(tip_presence) + + with pytest.raises(UnmatchedTipPresenceStates): + await subject.get_tip_status(mount) diff --git a/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py b/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py index a98e7f5138c..6dfc646547e 100644 --- a/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py +++ b/api/tests/opentrons/hardware_control/instruments/test_instrument_calibration.py @@ -32,6 +32,10 @@ def _use_mock_calibration_storage( """Mock out the opentrons.calibration_storage module.""" for name, func in inspect.getmembers(calibration_storage, inspect.isfunction): monkeypatch.setattr(calibration_storage, name, decoy.mock(func=func)) + for name, func in inspect.getmembers(calibration_storage.ot2, inspect.isfunction): + monkeypatch.setattr(calibration_storage.ot2, name, decoy.mock(func=func)) + for name, func in inspect.getmembers(calibration_storage.ot3, inspect.isfunction): + monkeypatch.setattr(calibration_storage.ot3, name, decoy.mock(func=func)) for name, func in inspect.getmembers( calibration_storage_helpers, inspect.isfunction @@ -87,7 +91,7 @@ def test_load_tip_length( ) decoy.when( - calibration_storage.load_tip_length_calibration( + calibration_storage.ot2.load_tip_length_calibration( pip_id="abc123", definition=tip_rack_dict ) ).then_return(tip_length_data) diff --git a/api/tests/opentrons/hardware_control/test_calibration_functions.py b/api/tests/opentrons/hardware_control/test_calibration_functions.py index 1a76a7010eb..3f75bc04322 100644 --- a/api/tests/opentrons/hardware_control/test_calibration_functions.py +++ b/api/tests/opentrons/hardware_control/test_calibration_functions.py @@ -1,7 +1,5 @@ -import importlib from pathlib import Path -import pytest import numpy as np from opentrons import config, calibration_storage @@ -12,14 +10,6 @@ from opentrons.types import Mount, Point -# TODO(mc, 2023-02-17): this reload resolves test polution from repeated -# reloads of this module in tests/opentrons/calibration_storage. -# module reloads to be removed in https://github.com/Opentrons/opentrons/pull/12049 -@pytest.fixture(autouse=True, scope="module") -def reload_module() -> None: - importlib.reload(calibration_storage) - - def test_migrate_affine_xy_to_attitude(): affine = [ [1.0, 2.0, 3.0, 4.0], @@ -97,7 +87,7 @@ def test_load_pipette_offset(ot_config_tempdir): mount = Mount.LEFT offset = Point(1, 2, 3) - calibration_storage.save_pipette_calibration( + calibration_storage.ot2.save_pipette_calibration( offset, pip_id, mount, "hash", "opentrons/opentrons_96_tiprack_10ul/1" ) obj = instrument_calibration.load_pipette_offset(pip_id, mount) diff --git a/api/tests/opentrons/hardware_control/test_instruments.py b/api/tests/opentrons/hardware_control/test_instruments.py index 2c763721b33..64f2b68279b 100644 --- a/api/tests/opentrons/hardware_control/test_instruments.py +++ b/api/tests/opentrons/hardware_control/test_instruments.py @@ -3,6 +3,7 @@ import pytest from decoy import Decoy +from typing import Iterator try: import aionotify @@ -58,6 +59,14 @@ def dummy_instruments_ot3(): return dummy_instruments_attached_ot3() +@pytest.fixture +def mock_api_verify_tip_presence() -> Iterator[mock.AsyncMock]: + from opentrons.hardware_control.ot3api import OT3API + + with mock.patch.object(OT3API, "verify_tip_presence") as mock_tip_presence: + yield mock_tip_presence + + def wrap_build_ot3_sim(): from opentrons.hardware_control.ot3api import OT3API @@ -65,7 +74,7 @@ def wrap_build_ot3_sim(): @pytest.fixture -def ot3_api_obj(request): +def ot3_api_obj(request, mock_api_verify_tip_presence): if request.config.getoption("--ot2-only"): pytest.skip("testing ot2 only") from opentrons.hardware_control.ot3api import OT3API @@ -80,7 +89,7 @@ def ot3_api_obj(request): ], ids=["ot2", "ot3"], ) -def sim_and_instr(request): +def sim_and_instr(request, mock_api_verify_tip_presence): if ( request.node.get_closest_marker("ot2_only") and request.param[0] == wrap_build_ot3_sim diff --git a/api/tests/opentrons/hardware_control/test_ot3_api.py b/api/tests/opentrons/hardware_control/test_ot3_api.py index 79dff2b6959..cd6f383c2cd 100644 --- a/api/tests/opentrons/hardware_control/test_ot3_api.py +++ b/api/tests/opentrons/hardware_control/test_ot3_api.py @@ -42,6 +42,7 @@ StatusBarState, EstopState, EstopStateNotification, + TipStateType, ) from opentrons.hardware_control.errors import InvalidCriticalPoint from opentrons.hardware_control.ot3api import OT3API @@ -462,7 +463,26 @@ async def _update_position( yield mock_pass -load_blowout_configs = [ +@pytest.fixture +def mock_backend_get_tip_status( + ot3_hardware: ThreadManager[OT3API], +) -> Iterator[AsyncMock]: + backend = ot3_hardware.managed_obj._backend + with patch.object(backend, "get_tip_status", AsyncMock()) as mock_tip_status: + yield mock_tip_status + + +@pytest.fixture +def mock_verify_tip_presence( + ot3_hardware: ThreadManager[OT3API], +) -> Iterator[AsyncMock]: + with patch.object( + ot3_hardware.managed_obj, "verify_tip_presence", AsyncMock() + ) as mock_check_tip: + yield mock_check_tip + + +load_pipette_configs = [ {OT3Mount.LEFT: {"channels": 1, "version": (3, 3), "model": "p1000"}}, {OT3Mount.RIGHT: {"channels": 8, "version": (3, 3), "model": "p50"}}, {OT3Mount.LEFT: {"channels": 96, "model": "p1000", "version": (3, 3)}}, @@ -471,6 +491,7 @@ async def _update_position( async def prepare_for_mock_blowout( ot3_hardware: ThreadManager[OT3API], + mock_backend_get_tip_status: AsyncMock, mount: OT3Mount, configs: Any, ) -> Tuple[Any, ThreadManager[OT3API]]: @@ -482,8 +503,9 @@ async def prepare_for_mock_blowout( instr_data = AttachedPipette(config=pipette_config, id="fakepip") await ot3_hardware.cache_pipette(mount, instr_data, None) await ot3_hardware.refresh_positions() + mock_backend_get_tip_status.return_value = TipStateType.PRESENT with patch.object( - ot3_hardware, "pick_up_tip", AsyncMock(spec=ot3_hardware.liquid_probe) + ot3_hardware, "pick_up_tip", AsyncMock(spec=ot3_hardware.pick_up_tip) ) as mock_tip_pickup: mock_tip_pickup.side_effect = ( ot3_hardware._pipette_handler.attached_instruments[mount]["has_tip"] @@ -493,12 +515,67 @@ async def prepare_for_mock_blowout( return instr_data, ot3_hardware -@pytest.mark.parametrize("load_configs", load_blowout_configs) +@pytest.mark.parametrize("load_configs", load_pipette_configs) +async def test_pickup_moves( + ot3_hardware: ThreadManager[OT3API], + mock_instrument_handlers: Tuple[Mock], + mock_move_to_plunger_bottom: AsyncMock, + mock_home_gear_motors: AsyncMock, + load_configs: List[Dict[str, Any]], +) -> None: + _, pipette_handler = mock_instrument_handlers + for mount, configs in load_configs.items(): + if configs["channels"] == 96: + gantry_load = GantryLoad.HIGH_THROUGHPUT + else: + gantry_load = GantryLoad.LOW_THROUGHPUT + + await ot3_hardware.set_gantry_load(gantry_load) + + z_tiprack_distance = 8.0 + end_z_retract_dist = 9.0 + move_plan_return_val = TipActionSpec( + shake_off_moves=[], + tip_action_moves=[ + TipActionMoveSpec( + # Move onto the posts + distance=10, + speed=0, + currents={ + Axis.of_main_tool_actuator(Mount.LEFT): 0, + Axis.Q: 0, + }, + ) + ], + z_distance_to_tiprack=z_tiprack_distance, + ending_z_retract_distance=end_z_retract_dist, + ) + pipette_handler.plan_ht_pick_up_tip.return_value = move_plan_return_val + pipette_handler.plan_lt_pick_up_tip.return_value = move_plan_return_val + + with patch.object( + ot3_hardware.managed_obj, + "move_rel", + AsyncMock(spec=ot3_hardware.managed_obj.move_rel), + ) as mock_move_rel: + await ot3_hardware.pick_up_tip(Mount.LEFT, 40.0) + move_call_list = [call.args for call in mock_move_rel.call_args_list] + if gantry_load == GantryLoad.HIGH_THROUGHPUT: + assert move_call_list == [ + (OT3Mount.LEFT, Point(z=z_tiprack_distance)), + (OT3Mount.LEFT, Point(z=end_z_retract_dist)), + ] + else: + assert move_call_list == [(OT3Mount.LEFT, Point(z=end_z_retract_dist))] + + +@pytest.mark.parametrize("load_configs", load_pipette_configs) @given(blowout_volume=strategies.floats(min_value=0, max_value=10)) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture], max_examples=10) @example(blowout_volume=0.0) async def test_blow_out_position( ot3_hardware: ThreadManager[OT3API], + mock_backend_get_tip_status: AsyncMock, load_configs: List[Dict[str, Any]], blowout_volume: float, ) -> None: @@ -507,7 +584,7 @@ async def test_blow_out_position( if configs["channels"] == 96: await ot3_hardware.set_gantry_load(GantryLoad.HIGH_THROUGHPUT) instr_data, ot3_hardware = await prepare_for_mock_blowout( - ot3_hardware, mount, configs + ot3_hardware, mock_backend_get_tip_status, mount, configs ) max_allowed_input_distance = ( @@ -537,7 +614,7 @@ async def test_blow_out_position( ) -@pytest.mark.parametrize("load_configs", load_blowout_configs) +@pytest.mark.parametrize("load_configs", load_pipette_configs) @given(blowout_volume=strategies.floats(min_value=0, max_value=300)) @settings( suppress_health_check=[ @@ -548,6 +625,7 @@ async def test_blow_out_position( ) async def test_blow_out_error( ot3_hardware: ThreadManager[OT3API], + mock_backend_get_tip_status: AsyncMock, load_configs: List[Dict[str, Any]], blowout_volume: float, ) -> None: @@ -556,7 +634,7 @@ async def test_blow_out_error( if configs["channels"] == 96: await ot3_hardware.set_gantry_load(GantryLoad.HIGH_THROUGHPUT) instr_data, ot3_hardware = await prepare_for_mock_blowout( - ot3_hardware, mount, configs + ot3_hardware, mock_backend_get_tip_status, mount, configs ) max_allowed_input_distance = ( @@ -1385,6 +1463,7 @@ async def test_pick_up_tip_full_tiprack( mock_ungrip: AsyncMock, mock_move_to_plunger_bottom: AsyncMock, mock_home_gear_motors: AsyncMock, + mock_verify_tip_presence: AsyncMock, ) -> None: mock_ungrip.return_value = None await ot3_hardware.home() @@ -1419,9 +1498,9 @@ def _update_gear_motor_pos( if moves: for move in moves: for block in move.blocks: - backend._gear_motor_position[ - NodeId.pipette_left - ] += block.distance + backend._gear_motor_position[NodeId.pipette_left] += ( + block.distance * move.unit_vector[Axis.Q] + ) elif distance: backend._gear_motor_position[NodeId.pipette_left] += distance @@ -1442,6 +1521,7 @@ async def test_drop_tip_full_tiprack( ot3_hardware: ThreadManager[OT3API], mock_instrument_handlers: Tuple[Mock], mock_home_gear_motors: AsyncMock, + mock_verify_tip_presence: AsyncMock, ) -> None: _, pipette_handler = mock_instrument_handlers backend = ot3_hardware.managed_obj._backend @@ -1488,6 +1568,7 @@ def _update_gear_motor_pos( set_mock_plunger_configs() await ot3_hardware.set_gantry_load(GantryLoad.HIGH_THROUGHPUT) + mock_backend_get_tip_status.return_value = TipStateType.ABSENT await ot3_hardware.drop_tip(Mount.LEFT, home_after=True) pipette_handler.plan_ht_drop_tip.assert_called_once_with() # first call should be "clamp", moving down @@ -1726,29 +1807,6 @@ async def test_status_bar_interface( assert ot3_hardware.get_status_bar_state() == response -async def test_tip_presence_disabled_ninety_six_channel( - ot3_hardware: ThreadManager[OT3API], -) -> None: - """Test 96 channel tip presence is disabled.""" - # TODO remove this check once we enable tip presence for 96 chan. - with patch.object( - ot3_hardware.managed_obj._backend, - "check_for_tip_presence", - AsyncMock(spec=ot3_hardware.managed_obj._backend.check_for_tip_presence), - ) as tip_present: - pipette_config = load_pipette_data.load_definition( - PipetteModelType("p1000"), - PipetteChannelType(96), - PipetteVersionType(3, 3), - ) - instr_data = AttachedPipette(config=pipette_config, id="fakepip") - await ot3_hardware.cache_pipette(OT3Mount.LEFT, instr_data, None) - await ot3_hardware._configure_instruments() - await ot3_hardware.pick_up_tip(OT3Mount.LEFT, 60) - - tip_present.assert_not_called() - - @pytest.mark.parametrize( argnames=["old_state", "new_state", "should_trigger"], argvalues=[ diff --git a/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py b/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py index 99f31ae9586..749f6cc4f60 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py @@ -7,7 +7,7 @@ from decoy import Decoy from opentrons_shared_data.deck import load as load_deck -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3, SlotDefV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4, SlotDefV3 from opentrons_shared_data.pipette.dev_types import PipetteNameType from opentrons_shared_data.labware.dev_types import ( LabwareDefinition as LabwareDefDict, @@ -83,15 +83,15 @@ @pytest.fixture(scope="session") -def ot2_standard_deck_def() -> DeckDefinitionV3: +def ot2_standard_deck_def() -> DeckDefinitionV4: """Get the OT-2 standard deck definition.""" - return load_deck(STANDARD_OT2_DECK, 3) + return load_deck(STANDARD_OT2_DECK, 4) @pytest.fixture(scope="session") -def ot3_standard_deck_def() -> DeckDefinitionV3: +def ot3_standard_deck_def() -> DeckDefinitionV4: """Get the OT-2 standard deck definition.""" - return load_deck(STANDARD_OT3_DECK, 3) + return load_deck(STANDARD_OT3_DECK, 4) @pytest.fixture(autouse=True) @@ -167,16 +167,6 @@ def subject( ) -def _get_slot_def( - deck_def: DeckDefinitionV3, slot_name: DeckSlotName -) -> Optional[SlotDefV3]: - slots_def = deck_def["locations"]["orderedSlots"] - for slot in slots_def: - if slot["id"] == slot_name.id: - return slot - return None - - @pytest.mark.parametrize("api_version", [APIVersion(2, 3)]) def test_api_version( decoy: Decoy, subject: ProtocolCore, api_version: APIVersion @@ -887,7 +877,7 @@ def test_load_module( engine_model: EngineModuleModel, expected_core_cls: Type[ModuleCore], subject: ProtocolCore, - deck_def: DeckDefinitionV3, + deck_def: DeckDefinitionV4, slot_name: DeckSlotName, robot_type: RobotType, ) -> None: @@ -904,7 +894,10 @@ def test_load_module( ) decoy.when(subject.get_slot_definition(slot_name)).then_return( - _get_slot_def(deck_def=deck_def, slot_name=slot_name) # type: ignore[arg-type] + cast( + SlotDefV3, + {"compatibleModuleTypes": [ModuleType.from_model(requested_model)]}, + ) ) decoy.when(mock_engine_client.state.config.robot_type).then_return(robot_type) @@ -1019,7 +1012,7 @@ def test_load_module_raises_wrong_location( engine_model: EngineModuleModel, expected_core_cls: Type[ModuleCore], subject: ProtocolCore, - deck_def: DeckDefinitionV3, + deck_def: DeckDefinitionV4, slot_name: DeckSlotName, robot_type: RobotType, ) -> None: @@ -1036,7 +1029,7 @@ def test_load_module_raises_wrong_location( decoy.when(mock_engine_client.state.config.robot_type).then_return(robot_type) decoy.when(subject.get_slot_definition(slot_name)).then_return( - _get_slot_def(deck_def=deck_def, slot_name=slot_name) # type: ignore[arg-type] + cast(SlotDefV3, {"compatibleModuleTypes": []}) ) with pytest.raises( @@ -1055,7 +1048,7 @@ def test_load_mag_block( mock_engine_client: EngineClient, mock_sync_hardware_api: SyncHardwareAPI, subject: ProtocolCore, - ot3_standard_deck_def: DeckDefinitionV3, + ot3_standard_deck_def: DeckDefinitionV4, ) -> None: """It should issue a load module engine command.""" definition = ModuleDefinition.construct() # type: ignore[call-arg] @@ -1063,7 +1056,14 @@ def test_load_mag_block( decoy.when(mock_engine_client.state.config.robot_type).then_return("OT-3 Standard") decoy.when(subject.get_slot_definition(DeckSlotName.SLOT_A2)).then_return( - _get_slot_def(deck_def=ot3_standard_deck_def, slot_name=DeckSlotName.SLOT_A2) # type: ignore[arg-type] + cast( + SlotDefV3, + { + "compatibleModuleTypes": [ + ModuleType.from_model(MagneticBlockModel.MAGNETIC_BLOCK_V1) + ] + }, + ) ) decoy.when( @@ -1140,7 +1140,7 @@ def test_load_module_thermocycler_with_no_location( requested_model: ModuleModel, engine_model: EngineModuleModel, subject: ProtocolCore, - deck_def: DeckDefinitionV3, + deck_def: DeckDefinitionV4, expected_slot: DeckSlotName, ) -> None: """It should issue a load module engine command with location at 7.""" @@ -1151,7 +1151,10 @@ def test_load_module_thermocycler_with_no_location( decoy.when(mock_sync_hardware_api.attached_modules).then_return([mock_hw_mod]) decoy.when(mock_engine_client.state.config.robot_type).then_return("OT-3 Standard") decoy.when(subject.get_slot_definition(expected_slot)).then_return( - _get_slot_def(deck_def=deck_def, slot_name=expected_slot) # type: ignore[arg-type] + cast( + SlotDefV3, + {"compatibleModuleTypes": [ModuleType.from_model(requested_model)]}, + ) ) decoy.when( @@ -1286,7 +1289,7 @@ def test_get_deck_definition( decoy: Decoy, mock_engine_client: EngineClient, subject: ProtocolCore ) -> None: """It should return the loaded deck definition from engine state.""" - deck_definition = cast(DeckDefinitionV3, {"schemaVersion": "3"}) + deck_definition = cast(DeckDefinitionV4, {"schemaVersion": "4"}) decoy.when(mock_engine_client.state.labware.get_deck_definition()).then_return( deck_definition diff --git a/api/tests/opentrons/protocol_api/test_deck.py b/api/tests/opentrons/protocol_api/test_deck.py index 19dac4aea05..b5464603036 100644 --- a/api/tests/opentrons/protocol_api/test_deck.py +++ b/api/tests/opentrons/protocol_api/test_deck.py @@ -5,7 +5,7 @@ import pytest from decoy import Decoy -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons.motion_planning import adjacent_slots_getters as mock_adjacent_slots from opentrons.protocols.api_support.types import APIVersion @@ -23,10 +23,14 @@ @pytest.fixture -def deck_definition() -> DeckDefinitionV3: +def deck_definition() -> DeckDefinitionV4: """Get a deck definition value object.""" return cast( - DeckDefinitionV3, {"locations": {"orderedSlots": [], "calibrationPoints": []}} + DeckDefinitionV4, + { + "locations": {"addressableAreas": [], "calibrationPoints": []}, + "cutoutFixtures": {}, + }, ) @@ -65,7 +69,7 @@ def mock_core_map(decoy: Decoy) -> LoadedCoreMap: @pytest.fixture def subject( decoy: Decoy, - deck_definition: DeckDefinitionV3, + deck_definition: DeckDefinitionV4, mock_protocol_core: ProtocolCore, mock_core_map: LoadedCoreMap, api_version: APIVersion, @@ -224,118 +228,120 @@ def test_delitem_raises_if_slot_has_module( del subject[2] -@pytest.mark.parametrize( - "deck_definition", - [ - { - "locations": { - "orderedSlots": [ - {"id": "1"}, - {"id": "2"}, - {"id": "3"}, - ], - "calibrationPoints": [], - } - }, - ], -) -def test_slot_keys_iter(subject: Deck) -> None: - """It should provide an iterable interface to deck slots.""" - result = list(subject) - - assert len(subject) == 3 - assert result == ["1", "2", "3"] - - -@pytest.mark.parametrize( - "deck_definition", - [ - { - "locations": { - "orderedSlots": [ - {"id": "fee"}, - {"id": "foe"}, - {"id": "fum"}, - ], - "calibrationPoints": [], - } - }, - ], -) -def test_slots_property(subject: Deck) -> None: - """It should provide slot definitions.""" - assert subject.slots == [ - {"id": "fee"}, - {"id": "foe"}, - {"id": "fum"}, - ] - - -@pytest.mark.parametrize( - "deck_definition", - [ - { - "locations": { - "orderedSlots": [ - {"id": DeckSlotName.SLOT_2.id, "displayName": "foobar"}, - ], - "calibrationPoints": [], - } - }, - ], -) -def test_get_slot_definition( - decoy: Decoy, - mock_protocol_core: ProtocolCore, - api_version: APIVersion, - subject: Deck, -) -> None: - """It should provide slot definitions.""" - decoy.when(mock_protocol_core.robot_type).then_return("OT-3 Standard") - decoy.when( - mock_validation.ensure_and_convert_deck_slot(222, api_version, "OT-3 Standard") - ).then_return(DeckSlotName.SLOT_2) - - assert subject.get_slot_definition(222) == { - "id": DeckSlotName.SLOT_2.id, - "displayName": "foobar", - } - - -@pytest.mark.parametrize( - "deck_definition", - [ - { - "locations": { - "orderedSlots": [ - {"id": DeckSlotName.SLOT_3.id, "position": [1.0, 2.0, 3.0]}, - ], - "calibrationPoints": [], - } - }, - ], -) -def test_get_position_for( - decoy: Decoy, - mock_protocol_core: ProtocolCore, - api_version: APIVersion, - subject: Deck, -) -> None: - """It should return a `Location` for a deck slot.""" - decoy.when(mock_protocol_core.robot_type).then_return("OT-3 Standard") - decoy.when( - mock_validation.ensure_and_convert_deck_slot(333, api_version, "OT-3 Standard") - ).then_return(DeckSlotName.SLOT_3) - decoy.when( - mock_validation.internal_slot_to_public_string( - DeckSlotName.SLOT_3, "OT-3 Standard" - ) - ).then_return("foo") - - result = subject.position_for(333) - assert result.point == Point(x=1.0, y=2.0, z=3.0) - assert result.labware.is_slot is True - assert str(result.labware) == "foo" +# TODO(jbl 10-30-2023) the following commented out tests are too tightly coupled to DeckDefinitionV3 to easily port over +# Either refactor them when the deck class is updated/made anew or delete them later +# @pytest.mark.parametrize( +# "deck_definition", +# [ +# { +# "locations": { +# "orderedSlots": [ +# {"id": "1"}, +# {"id": "2"}, +# {"id": "3"}, +# ], +# "calibrationPoints": [], +# } +# }, +# ], +# ) +# def test_slot_keys_iter(subject: Deck) -> None: +# """It should provide an iterable interface to deck slots.""" +# result = list(subject) +# +# assert len(subject) == 3 +# assert result == ["1", "2", "3"] + + +# @pytest.mark.parametrize( +# "deck_definition", +# [ +# { +# "locations": { +# "orderedSlots": [ +# {"id": "fee"}, +# {"id": "foe"}, +# {"id": "fum"}, +# ], +# "calibrationPoints": [], +# } +# }, +# ], +# ) +# def test_slots_property(subject: Deck) -> None: +# """It should provide slot definitions.""" +# assert subject.slots == [ +# {"id": "fee"}, +# {"id": "foe"}, +# {"id": "fum"}, +# ] + + +# @pytest.mark.parametrize( +# "deck_definition", +# [ +# { +# "locations": { +# "orderedSlots": [ +# {"id": DeckSlotName.SLOT_2.id, "displayName": "foobar"}, +# ], +# "calibrationPoints": [], +# } +# }, +# ], +# ) +# def test_get_slot_definition( +# decoy: Decoy, +# mock_protocol_core: ProtocolCore, +# api_version: APIVersion, +# subject: Deck, +# ) -> None: +# """It should provide slot definitions.""" +# decoy.when(mock_protocol_core.robot_type).then_return("OT-3 Standard") +# decoy.when( +# mock_validation.ensure_and_convert_deck_slot(222, api_version, "OT-3 Standard") +# ).then_return(DeckSlotName.SLOT_2) +# +# assert subject.get_slot_definition(222) == { +# "id": DeckSlotName.SLOT_2.id, +# "displayName": "foobar", +# } + + +# @pytest.mark.parametrize( +# "deck_definition", +# [ +# { +# "locations": { +# "orderedSlots": [ +# {"id": DeckSlotName.SLOT_3.id, "position": [1.0, 2.0, 3.0]}, +# ], +# "calibrationPoints": [], +# } +# }, +# ], +# ) +# def test_get_position_for( +# decoy: Decoy, +# mock_protocol_core: ProtocolCore, +# api_version: APIVersion, +# subject: Deck, +# ) -> None: +# """It should return a `Location` for a deck slot.""" +# decoy.when(mock_protocol_core.robot_type).then_return("OT-3 Standard") +# decoy.when( +# mock_validation.ensure_and_convert_deck_slot(333, api_version, "OT-3 Standard") +# ).then_return(DeckSlotName.SLOT_3) +# decoy.when( +# mock_validation.internal_slot_to_public_string( +# DeckSlotName.SLOT_3, "OT-3 Standard" +# ) +# ).then_return("foo") +# +# result = subject.position_for(333) +# assert result.point == Point(x=1.0, y=2.0, z=3.0) +# assert result.labware.is_slot is True +# assert str(result.labware) == "foo" def test_highest_z( diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 54ecab4cefb..9a38dfef87b 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -28,6 +28,10 @@ ) from opentrons.types import Location, Mount, Point +from opentrons_shared_data.errors.exceptions import ( + CommandPreconditionViolated, +) + @pytest.fixture(autouse=True) def _mock_validation_module(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: @@ -912,3 +916,21 @@ def test_plunger_speed_removed(subject: InstrumentContext) -> None: """It should raise an error on PAPI >= v2.14.""" with pytest.raises(APIVersionError): subject.speed + + +def test_prepare_to_aspirate( + subject: InstrumentContext, decoy: Decoy, mock_instrument_core: InstrumentCore +) -> None: + """It should call the core function.""" + decoy.when(mock_instrument_core.get_current_volume()).then_return(0) + subject.prepare_to_aspirate() + decoy.verify(mock_instrument_core.prepare_to_aspirate(), times=1) + + +def test_prepare_to_aspirate_checks_volume( + subject: InstrumentContext, decoy: Decoy, mock_instrument_core: InstrumentCore +) -> None: + """It should raise an error if you prepare for aspirate with liquid in the pipette.""" + decoy.when(mock_instrument_core.get_current_volume()).then_return(10) + with pytest.raises(CommandPreconditionViolated): + subject.prepare_to_aspirate() diff --git a/api/tests/opentrons/protocol_api/test_protocol_context.py b/api/tests/opentrons/protocol_api/test_protocol_context.py index 29776d62c95..d31d0c43ed8 100644 --- a/api/tests/opentrons/protocol_api/test_protocol_context.py +++ b/api/tests/opentrons/protocol_api/test_protocol_context.py @@ -58,9 +58,11 @@ def _mock_instrument_support_module( def mock_core(decoy: Decoy) -> ProtocolCore: """Get a mock implementation core.""" mock_core = decoy.mock(cls=ProtocolCore) - decoy.when(mock_core.fixed_trash.get_name()).then_return("cool trash") - decoy.when(mock_core.fixed_trash.get_display_name()).then_return("Cool Trash") - decoy.when(mock_core.fixed_trash.get_well_columns()).then_return([]) + mock_fixed_trash = decoy.mock(cls=LabwareCore) + decoy.when(mock_core.fixed_trash).then_return(mock_fixed_trash) + decoy.when(mock_fixed_trash.get_name()).then_return("cool trash") + decoy.when(mock_fixed_trash.get_display_name()).then_return("Cool Trash") + decoy.when(mock_fixed_trash.get_well_columns()).then_return([]) return mock_core @@ -107,6 +109,7 @@ def test_fixed_trash( """It should get the fixed trash labware from the core.""" trash_captor = matchers.Captor() + assert mock_core.fixed_trash is not None decoy.verify(mock_core_map.add(mock_core.fixed_trash, trash_captor), times=1) trash = trash_captor.value diff --git a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py index 5555fd5e979..d0916dd4108 100644 --- a/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api_old/core/simulator/test_instrument_context.py @@ -43,7 +43,7 @@ def test_prepare_to_aspirate_no_tip(subject: InstrumentCore) -> None: with pytest.raises( UnexpectedTipRemovalError, match="Cannot perform PREPARE_ASPIRATE" ): - subject.prepare_for_aspirate() # type: ignore[attr-defined] + subject.prepare_to_aspirate() def test_dispense_no_tip(subject: InstrumentCore) -> None: @@ -161,7 +161,7 @@ def test_aspirate_too_much( increment=None, prep_after=False, ) - subject.prepare_for_aspirate() # type: ignore[attr-defined] + subject.prepare_to_aspirate() with pytest.raises( AssertionError, match="Cannot aspirate more than pipette max volume" ): @@ -215,7 +215,7 @@ def test_pipette_dict( def _aspirate(i: InstrumentCore, labware: LabwareCore) -> None: """pipette dict with tip fixture.""" - i.prepare_for_aspirate() # type: ignore[attr-defined] + i.prepare_to_aspirate() i.aspirate( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), @@ -228,7 +228,7 @@ def _aspirate(i: InstrumentCore, labware: LabwareCore) -> None: def _aspirate_dispense(i: InstrumentCore, labware: LabwareCore) -> None: """pipette dict with tip fixture.""" - i.prepare_for_aspirate() # type: ignore[attr-defined] + i.prepare_to_aspirate() i.aspirate( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), @@ -250,7 +250,7 @@ def _aspirate_dispense(i: InstrumentCore, labware: LabwareCore) -> None: def _aspirate_blowout(i: InstrumentCore, labware: LabwareCore) -> None: """pipette dict with tip fixture.""" - i.prepare_for_aspirate() # type: ignore[attr-defined] + i.prepare_to_aspirate() i.aspirate( location=Location(point=Point(1, 2, 3), labware=None), well_core=labware.get_well_core("A1"), diff --git a/api/tests/opentrons/protocol_api_old/test_labware_load.py b/api/tests/opentrons/protocol_api_old/test_labware_load.py index 8ff48429f45..69a41f7a365 100644 --- a/api/tests/opentrons/protocol_api_old/test_labware_load.py +++ b/api/tests/opentrons/protocol_api_old/test_labware_load.py @@ -11,16 +11,16 @@ # TODO(mm, 2022-04-28): Make sure this logic is tested elsewhere, then delete this test. @pytest.mark.apiv2_non_pe_only def test_load_to_slot( - ctx: papi.ProtocolContext, deck_definition: DeckDefinitionV3 + ctx: papi.ProtocolContext, legacy_deck_definition: DeckDefinitionV3 ) -> None: slot_1 = [ slot - for slot in deck_definition["locations"]["orderedSlots"] + for slot in legacy_deck_definition["locations"]["orderedSlots"] if slot["id"] == "1" ][0] slot_2 = [ slot - for slot in deck_definition["locations"]["orderedSlots"] + for slot in legacy_deck_definition["locations"]["orderedSlots"] if slot["id"] == "2" ][0] labware = ctx.load_labware(labware_name, "1") diff --git a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py new file mode 100644 index 00000000000..d3f09d6685f --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py @@ -0,0 +1,29 @@ +"""Test prepare to aspirate commands.""" + +from decoy import Decoy + +from opentrons.protocol_engine.execution import ( + PipettingHandler, +) + +from opentrons.protocol_engine.commands.prepare_to_aspirate import ( + PrepareToAspirateParams, + PrepareToAspirateImplementation, + PrepareToAspirateResult, +) + + +async def test_prepare_to_aspirate_implmenetation( + decoy: Decoy, pipetting: PipettingHandler +) -> None: + """A PrepareToAspirate command should have an executing implementation.""" + subject = PrepareToAspirateImplementation(pipetting=pipetting) + + data = PrepareToAspirateParams(pipetteId="some id") + + decoy.when(await pipetting.prepare_for_aspirate(pipette_id="some id")).then_return( + None + ) + + result = await subject.execute(data) + assert isinstance(result, PrepareToAspirateResult) diff --git a/api/tests/opentrons/protocol_engine/conftest.py b/api/tests/opentrons/protocol_engine/conftest.py index 785b04f8989..8574cefe248 100644 --- a/api/tests/opentrons/protocol_engine/conftest.py +++ b/api/tests/opentrons/protocol_engine/conftest.py @@ -7,7 +7,7 @@ from opentrons_shared_data import load_shared_data from opentrons_shared_data.deck import load as load_deck -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons_shared_data.labware import load_definition from opentrons_shared_data.pipette import pipette_definition from opentrons.protocols.models import LabwareDefinition @@ -51,21 +51,21 @@ def ot3_hardware_api(decoy: Decoy) -> OT3API: @pytest.fixture(scope="session") -def ot2_standard_deck_def() -> DeckDefinitionV3: +def ot2_standard_deck_def() -> DeckDefinitionV4: """Get the OT-2 standard deck definition.""" - return load_deck(STANDARD_OT2_DECK, 3) + return load_deck(STANDARD_OT2_DECK, 4) @pytest.fixture(scope="session") -def ot2_short_trash_deck_def() -> DeckDefinitionV3: +def ot2_short_trash_deck_def() -> DeckDefinitionV4: """Get the OT-2 short-trash deck definition.""" - return load_deck(SHORT_TRASH_DECK, 3) + return load_deck(SHORT_TRASH_DECK, 4) @pytest.fixture(scope="session") -def ot3_standard_deck_def() -> DeckDefinitionV3: +def ot3_standard_deck_def() -> DeckDefinitionV4: """Get the OT-2 standard deck definition.""" - return load_deck(STANDARD_OT3_DECK, 3) + return load_deck(STANDARD_OT3_DECK, 4) @pytest.fixture(scope="session") diff --git a/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py index f7e04646d22..2e01abb3119 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py @@ -3,7 +3,7 @@ from pytest_lazyfixture import lazy_fixture # type: ignore[import] from decoy import Decoy -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons.protocols.models import LabwareDefinition from opentrons.types import DeckSlotName @@ -31,7 +31,7 @@ def mock_labware_data_provider(decoy: Decoy) -> LabwareDataProvider: ) async def test_get_deck_definition( deck_type: DeckType, - expected_definition: DeckDefinitionV3, + expected_definition: DeckDefinitionV4, mock_labware_data_provider: LabwareDataProvider, ) -> None: """It should be able to load the correct deck definition.""" @@ -44,7 +44,7 @@ async def test_get_deck_definition( async def test_get_deck_labware_fixtures_ot2_standard( decoy: Decoy, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, ot2_fixed_trash_def: LabwareDefinition, mock_labware_data_provider: LabwareDataProvider, ) -> None: @@ -74,7 +74,7 @@ async def test_get_deck_labware_fixtures_ot2_standard( async def test_get_deck_labware_fixtures_ot2_short_trash( decoy: Decoy, - ot2_short_trash_deck_def: DeckDefinitionV3, + ot2_short_trash_deck_def: DeckDefinitionV4, ot2_short_fixed_trash_def: LabwareDefinition, mock_labware_data_provider: LabwareDataProvider, ) -> None: @@ -104,7 +104,7 @@ async def test_get_deck_labware_fixtures_ot2_short_trash( async def test_get_deck_labware_fixtures_ot3_standard( decoy: Decoy, - ot3_standard_deck_def: DeckDefinitionV3, + ot3_standard_deck_def: DeckDefinitionV4, ot3_fixed_trash_def: LabwareDefinition, mock_labware_data_provider: LabwareDataProvider, ) -> None: diff --git a/api/tests/opentrons/protocol_engine/state/command_fixtures.py b/api/tests/opentrons/protocol_engine/state/command_fixtures.py index e0b5368be2f..ef548377a3e 100644 --- a/api/tests/opentrons/protocol_engine/state/command_fixtures.py +++ b/api/tests/opentrons/protocol_engine/state/command_fixtures.py @@ -466,3 +466,17 @@ def create_move_labware_command( params=params, result=result, ) + + +def create_prepare_to_aspirate_command(pipette_id: str) -> cmd.PrepareToAspirate: + """Get a completed PrepareToAspirate command.""" + params = cmd.PrepareToAspirateParams(pipetteId=pipette_id) + result = cmd.PrepareToAspirateResult() + return cmd.PrepareToAspirate( + id="command-id", + key="command-key", + status=cmd.CommandStatus.SUCCEEDED, + createdAt=datetime(year=2023, month=10, day=24), + params=params, + result=result, + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index 0e1204688af..e46dd87d58a 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -5,7 +5,7 @@ from decoy import Decoy from typing import cast, List, Tuple, Optional, NamedTuple -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons_shared_data.labware.dev_types import LabwareUri from opentrons_shared_data.pipette import pipette_definition from opentrons.calibration_storage.helpers import uri_from_details @@ -129,7 +129,7 @@ def test_get_labware_parent_position_on_module( decoy: Decoy, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should return a module position for labware on a module.""" @@ -178,7 +178,7 @@ def test_get_labware_parent_position_on_labware( decoy: Decoy, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should return a labware position for labware on a labware on a module.""" @@ -243,7 +243,7 @@ def test_module_calibration_offset_rotation( decoy: Decoy, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """Return the rotated module calibration offset if the module was moved from one side of the deck to the other.""" @@ -365,7 +365,7 @@ def test_get_module_labware_highest_z( well_plate_def: LabwareDefinition, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should get the absolute location of a labware's highest Z point.""" @@ -669,7 +669,7 @@ def test_get_module_labware_well_position( well_plate_def: LabwareDefinition, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should be able to get the position of a well top in a labware on module.""" @@ -1147,7 +1147,7 @@ def test_get_labware_grip_point( decoy: Decoy, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should get the grip point of the labware at the specified location.""" @@ -1169,7 +1169,7 @@ def test_get_labware_grip_point_on_labware( decoy: Decoy, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should get the grip point of a labware on another labware.""" @@ -1215,7 +1215,7 @@ def test_get_labware_grip_point_for_labware_on_module( decoy: Decoy, labware_view: LabwareView, module_view: ModuleView, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, subject: GeometryView, ) -> None: """It should return the grip point for labware directly on a module.""" diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_store.py b/api/tests/opentrons/protocol_engine/state/test_labware_store.py index 1cd2711d06e..efe67422da0 100644 --- a/api/tests/opentrons/protocol_engine/state/test_labware_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_labware_store.py @@ -4,11 +4,10 @@ from datetime import datetime from opentrons.calibration_storage.helpers import uri_from_details -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons.protocols.models import LabwareDefinition from opentrons.types import DeckSlotName -from opentrons.protocol_engine.resources import DeckFixedLabware from opentrons.protocol_engine.types import ( LabwareOffset, LabwareOffsetCreate, @@ -34,47 +33,25 @@ @pytest.fixture def subject( - ot2_standard_deck_def: DeckDefinitionV3, - ot2_fixed_trash_def: LabwareDefinition, + ot2_standard_deck_def: DeckDefinitionV4, ) -> LabwareStore: """Get a LabwareStore test subject.""" return LabwareStore( deck_definition=ot2_standard_deck_def, - deck_fixed_labware=[ - DeckFixedLabware( - labware_id="fixedTrash", - location=DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), - definition=ot2_fixed_trash_def, - ) - ], + deck_fixed_labware=[], ) def test_initial_state( - ot2_standard_deck_def: DeckDefinitionV3, - ot2_fixed_trash_def: LabwareDefinition, + ot2_standard_deck_def: DeckDefinitionV4, subject: LabwareStore, ) -> None: """It should create the labware store with preloaded fixed labware.""" - expected_trash_uri = uri_from_details( - namespace=ot2_fixed_trash_def.namespace, - version=ot2_fixed_trash_def.version, - load_name=ot2_fixed_trash_def.parameters.loadName, - ) - assert subject.state == LabwareState( deck_definition=ot2_standard_deck_def, - labware_by_id={ - "fixedTrash": LoadedLabware( - id="fixedTrash", - loadName=ot2_fixed_trash_def.parameters.loadName, - definitionUri=expected_trash_uri, - location=DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), - offsetId=None, - ) - }, + labware_by_id={}, labware_offsets_by_id={}, - definitions_by_uri={expected_trash_uri: ot2_fixed_trash_def}, + definitions_by_uri={}, ) diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_view.py b/api/tests/opentrons/protocol_engine/state/test_labware_view.py index 6de6ba0d191..494b92ed548 100644 --- a/api/tests/opentrons/protocol_engine/state/test_labware_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_labware_view.py @@ -5,7 +5,7 @@ from contextlib import nullcontext as does_not_raise from opentrons_shared_data.deck import load as load_deck -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons_shared_data.pipette.dev_types import LabwareUri from opentrons_shared_data.labware.labware_definition import ( Parameters, @@ -108,14 +108,14 @@ def get_labware_view( labware_by_id: Optional[Dict[str, LoadedLabware]] = None, labware_offsets_by_id: Optional[Dict[str, LabwareOffset]] = None, definitions_by_uri: Optional[Dict[str, LabwareDefinition]] = None, - deck_definition: Optional[DeckDefinitionV3] = None, + deck_definition: Optional[DeckDefinitionV4] = None, ) -> LabwareView: """Get a labware view test subject.""" state = LabwareState( labware_by_id=labware_by_id or {}, labware_offsets_by_id=labware_offsets_by_id or {}, definitions_by_uri=definitions_by_uri or {}, - deck_definition=deck_definition or cast(DeckDefinitionV3, {"fake": True}), + deck_definition=deck_definition or cast(DeckDefinitionV4, {"fake": True}), ) return LabwareView(state=state) @@ -694,7 +694,7 @@ def test_get_labware_overlap_offsets() -> None: class ModuleOverlapSpec(NamedTuple): """Spec data to test LabwareView.get_module_overlap_offsets.""" - spec_deck_definition: DeckDefinitionV3 + spec_deck_definition: DeckDefinitionV4 module_model: ModuleModel stacking_offset_with_module: Dict[str, SharedDataOverlapOffset] expected_offset: OverlapOffset @@ -703,7 +703,7 @@ class ModuleOverlapSpec(NamedTuple): module_overlap_specs: List[ModuleOverlapSpec] = [ ModuleOverlapSpec( # Labware on temp module on OT2, with stacking overlap for temp module - spec_deck_definition=load_deck(STANDARD_OT2_DECK, 3), + spec_deck_definition=load_deck(STANDARD_OT2_DECK, 4), module_model=ModuleModel.TEMPERATURE_MODULE_V2, stacking_offset_with_module={ str(ModuleModel.TEMPERATURE_MODULE_V2.value): SharedDataOverlapOffset( @@ -714,7 +714,7 @@ class ModuleOverlapSpec(NamedTuple): ), ModuleOverlapSpec( # Labware on TC Gen1 on OT2, with stacking overlap for TC Gen1 - spec_deck_definition=load_deck(STANDARD_OT2_DECK, 3), + spec_deck_definition=load_deck(STANDARD_OT2_DECK, 4), module_model=ModuleModel.THERMOCYCLER_MODULE_V1, stacking_offset_with_module={ str(ModuleModel.THERMOCYCLER_MODULE_V1.value): SharedDataOverlapOffset( @@ -725,21 +725,21 @@ class ModuleOverlapSpec(NamedTuple): ), ModuleOverlapSpec( # Labware on TC Gen2 on OT2, with no stacking overlap - spec_deck_definition=load_deck(STANDARD_OT2_DECK, 3), + spec_deck_definition=load_deck(STANDARD_OT2_DECK, 4), module_model=ModuleModel.THERMOCYCLER_MODULE_V2, stacking_offset_with_module={}, expected_offset=OverlapOffset(x=0, y=0, z=10.7), ), ModuleOverlapSpec( # Labware on TC Gen2 on Flex, with no stacking overlap - spec_deck_definition=load_deck(STANDARD_OT3_DECK, 3), + spec_deck_definition=load_deck(STANDARD_OT3_DECK, 4), module_model=ModuleModel.THERMOCYCLER_MODULE_V2, stacking_offset_with_module={}, expected_offset=OverlapOffset(x=0, y=0, z=0), ), ModuleOverlapSpec( # Labware on TC Gen2 on Flex, with stacking overlap for TC Gen2 - spec_deck_definition=load_deck(STANDARD_OT3_DECK, 3), + spec_deck_definition=load_deck(STANDARD_OT3_DECK, 4), module_model=ModuleModel.THERMOCYCLER_MODULE_V2, stacking_offset_with_module={ str(ModuleModel.THERMOCYCLER_MODULE_V2.value): SharedDataOverlapOffset( @@ -756,7 +756,7 @@ class ModuleOverlapSpec(NamedTuple): argvalues=module_overlap_specs, ) def test_get_module_overlap_offsets( - spec_deck_definition: DeckDefinitionV3, + spec_deck_definition: DeckDefinitionV4, module_model: ModuleModel, stacking_offset_with_module: Dict[str, SharedDataOverlapOffset], expected_offset: OverlapOffset, @@ -798,25 +798,25 @@ def test_get_default_magnet_height( assert subject.get_default_magnet_height(module_id="module-id", offset=2) == 12.0 -def test_get_deck_definition(ot2_standard_deck_def: DeckDefinitionV3) -> None: +def test_get_deck_definition(ot2_standard_deck_def: DeckDefinitionV4) -> None: """It should get the deck definition from the state.""" subject = get_labware_view(deck_definition=ot2_standard_deck_def) assert subject.get_deck_definition() == ot2_standard_deck_def -def test_get_slot_definition(ot2_standard_deck_def: DeckDefinitionV3) -> None: +def test_get_slot_definition(ot2_standard_deck_def: DeckDefinitionV4) -> None: """It should return a deck slot's definition.""" subject = get_labware_view(deck_definition=ot2_standard_deck_def) result = subject.get_slot_definition(DeckSlotName.SLOT_6) assert result["id"] == "6" - assert result == ot2_standard_deck_def["locations"]["orderedSlots"][5] + assert result["displayName"] == "Slot 6" def test_get_slot_definition_raises_with_bad_slot_name( - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, ) -> None: """It should raise a SlotDoesNotExistError if a bad slot name is given.""" subject = get_labware_view(deck_definition=ot2_standard_deck_def) @@ -825,17 +825,17 @@ def test_get_slot_definition_raises_with_bad_slot_name( subject.get_slot_definition(DeckSlotName.SLOT_A1) -def test_get_slot_position(ot2_standard_deck_def: DeckDefinitionV3) -> None: +def test_get_slot_position(ot2_standard_deck_def: DeckDefinitionV4) -> None: """It should get the absolute location of a deck slot's origin.""" subject = get_labware_view(deck_definition=ot2_standard_deck_def) - slot_pos = ot2_standard_deck_def["locations"]["orderedSlots"][2]["position"] - result = subject.get_slot_position(DeckSlotName.SLOT_3) + expected_position = Point(x=132.5, y=90.5, z=0.0) + result = subject.get_slot_position(DeckSlotName.SLOT_5) - assert result == Point(x=slot_pos[0], y=slot_pos[1], z=slot_pos[2]) + assert result == expected_position -def test_get_slot_center_position(ot2_standard_deck_def: DeckDefinitionV3) -> None: +def test_get_slot_center_position(ot2_standard_deck_def: DeckDefinitionV4) -> None: """It should get the absolute location of a deck slot's center.""" subject = get_labware_view(deck_definition=ot2_standard_deck_def) @@ -1081,8 +1081,7 @@ def test_get_fixed_trash_id() -> None: ) }, ) - with pytest.raises(errors.LabwareNotLoadedError): - subject.get_fixed_trash_id() + assert subject.get_fixed_trash_id() is None @pytest.mark.parametrize( @@ -1383,7 +1382,7 @@ def test_raise_if_labware_cannot_be_stacked_on_labware_on_adapter() -> None: ) -def test_get_deck_gripper_offsets(ot3_standard_deck_def: DeckDefinitionV3) -> None: +def test_get_deck_gripper_offsets(ot3_standard_deck_def: DeckDefinitionV4) -> None: """It should get the deck's gripper offsets.""" subject = get_labware_view(deck_definition=ot3_standard_deck_def) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index b4fb8f868ec..25370a410fc 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -46,6 +46,7 @@ create_move_labware_command, create_move_to_coordinates_command, create_move_relative_command, + create_prepare_to_aspirate_command, ) @@ -855,3 +856,40 @@ def test_homing_commands_clear_deck_point( assert subject.state.current_deck_point == CurrentDeckPoint( mount=None, deck_point=None ) + + +@pytest.mark.parametrize( + "previous", + [ + create_blow_out_command(pipette_id="pipette-id", flow_rate=1.0), + create_dispense_command(pipette_id="pipette-id", volume=10, flow_rate=1.0), + ], +) +def test_prepare_to_aspirate_marks_pipette_ready( + subject: PipetteStore, previous: cmd.Command +) -> None: + """It should mark a pipette as ready to aspirate.""" + load_pipette_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P50_MULTI_FLEX, + mount=MountType.LEFT, + ) + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="pipette-id", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + subject.handle_action( + UpdateCommandAction(private_result=None, command=load_pipette_command) + ) + subject.handle_action( + UpdateCommandAction(private_result=None, command=pick_up_tip_command) + ) + + subject.handle_action(UpdateCommandAction(private_result=None, command=previous)) + + prepare_to_aspirate_command = create_prepare_to_aspirate_command( + pipette_id="pipette-id" + ) + subject.handle_action( + UpdateCommandAction(private_result=None, command=prepare_to_aspirate_command) + ) + assert subject.state.aspirated_volume_by_id["pipette-id"] == 0.0 diff --git a/api/tests/opentrons/protocol_engine/state/test_state_store.py b/api/tests/opentrons/protocol_engine/state/test_state_store.py index e1b4b115ca6..44c42fe50b2 100644 --- a/api/tests/opentrons/protocol_engine/state/test_state_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_state_store.py @@ -5,7 +5,7 @@ import pytest from decoy import Decoy -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons.protocol_engine.actions import PlayAction from opentrons.protocol_engine.state import State, StateStore, Config @@ -32,7 +32,7 @@ def engine_config() -> Config: @pytest.fixture def subject( change_notifier: ChangeNotifier, - ot2_standard_deck_def: DeckDefinitionV3, + ot2_standard_deck_def: DeckDefinitionV4, engine_config: Config, ) -> StateStore: """Get a StateStore test subject.""" diff --git a/api/tests/opentrons/protocol_engine/test_create_protocol_engine.py b/api/tests/opentrons/protocol_engine/test_create_protocol_engine.py index 78454056ca6..c099b0c4521 100644 --- a/api/tests/opentrons/protocol_engine/test_create_protocol_engine.py +++ b/api/tests/opentrons/protocol_engine/test_create_protocol_engine.py @@ -2,7 +2,7 @@ import pytest from pytest_lazyfixture import lazy_fixture # type: ignore[import] -from opentrons_shared_data.deck.dev_types import DeckDefinitionV3 +from opentrons_shared_data.deck.dev_types import DeckDefinitionV4 from opentrons_shared_data.robot.dev_types import RobotType from opentrons.calibration_storage.helpers import uri_from_details @@ -19,6 +19,53 @@ from opentrons.types import DeckSlotName +@pytest.mark.parametrize( + ( + "robot_type", + "deck_type", + "expected_deck_def", + ), + [ + ( + "OT-2 Standard", + DeckType.OT2_STANDARD, + lazy_fixture("ot2_standard_deck_def"), + ), + ( + "OT-2 Standard", + DeckType.OT2_SHORT_TRASH, + lazy_fixture("ot2_short_trash_deck_def"), + ), + ( + "OT-3 Standard", + DeckType.OT3_STANDARD, + lazy_fixture("ot3_standard_deck_def"), + ), + ], +) +async def test_create_engine_initializes_state_with_no_fixed_trash( + hardware_api: HardwareAPI, + robot_type: RobotType, + deck_type: DeckType, + expected_deck_def: DeckDefinitionV4, +) -> None: + """It should load deck geometry data into the store on create.""" + engine = await create_protocol_engine( + hardware_api=hardware_api, + config=EngineConfig( + # robot_type chosen to match hardware_api. + robot_type=robot_type, + deck_type=deck_type, + ), + load_fixed_trash=False, + ) + state = engine.state_view + + assert isinstance(engine, ProtocolEngine) + assert state.labware.get_deck_definition() == expected_deck_def + assert state.labware.get_all() == [] + + @pytest.mark.parametrize( ( "robot_type", @@ -51,11 +98,11 @@ ), ], ) -async def test_create_engine_initializes_state_with_deck_geometry( +async def test_create_engine_initializes_state_with_fixed_trash( hardware_api: HardwareAPI, robot_type: RobotType, deck_type: DeckType, - expected_deck_def: DeckDefinitionV3, + expected_deck_def: DeckDefinitionV4, expected_fixed_trash_def: LabwareDefinition, expected_fixed_trash_slot: DeckSlotName, ) -> None: @@ -67,6 +114,7 @@ async def test_create_engine_initializes_state_with_deck_geometry( robot_type=robot_type, deck_type=deck_type, ), + load_fixed_trash=True, ) state = engine.state_view diff --git a/api/tests/opentrons/protocol_runner/test_json_translator.py b/api/tests/opentrons/protocol_runner/test_json_translator.py index 57d10ec0176..10669b30047 100644 --- a/api/tests/opentrons/protocol_runner/test_json_translator.py +++ b/api/tests/opentrons/protocol_runner/test_json_translator.py @@ -17,6 +17,7 @@ from opentrons_shared_data.protocol.models import ( protocol_schema_v6, protocol_schema_v7, + protocol_schema_v8, Liquid, Labware, Location, @@ -78,6 +79,21 @@ ), ), ), + protocol_schema_v8.Command( + commandType="aspirate", + key=None, + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + labwareId="labware-id-2", + volume=1.23, + flowRate=4.56, + wellName="A1", + wellLocation=SD_WellLocation( + origin="bottom", + offset=OffsetVector(x=0, y=0, z=7.89), + ), + ), + ), pe_commands.AspirateCreate( key=None, params=pe_commands.AspirateParams( @@ -125,6 +141,21 @@ ), ), ), + protocol_schema_v8.Command( + commandType="dispense", + key="dispense-key", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + labwareId="labware-id-2", + volume=1.23, + flowRate=4.56, + wellName="A1", + wellLocation=SD_WellLocation( + origin="bottom", + offset=OffsetVector(x=0, y=0, z=7.89), + ), + ), + ), pe_commands.DispenseCreate( key="dispense-key", params=pe_commands.DispenseParams( @@ -157,6 +188,14 @@ wellName="A1", ), ), + protocol_schema_v8.Command( + commandType="dropTip", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + labwareId="labware-id-2", + wellName="A1", + ), + ), pe_commands.DropTipCreate( params=pe_commands.DropTipParams( pipetteId="pipette-id-1", @@ -186,6 +225,14 @@ wellName="A1", ), ), + protocol_schema_v8.Command( + commandType="pickUpTip", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + labwareId="labware-id-2", + wellName="A1", + ), + ), pe_commands.PickUpTipCreate( params=pe_commands.PickUpTipParams( pipetteId="pipette-id-1", @@ -220,6 +267,18 @@ ), ), ), + protocol_schema_v8.Command( + commandType="touchTip", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + labwareId="labware-id-2", + wellName="A1", + wellLocation=SD_WellLocation( + origin="bottom", + offset=OffsetVector(x=0, y=0, z=-1.23), + ), + ), + ), pe_commands.TouchTipCreate( params=pe_commands.TouchTipParams( pipetteId="pipette-id-1", @@ -243,6 +302,12 @@ pipetteId="pipette-id-1", mount="left", pipetteName="p10_single" ), ), + protocol_schema_v8.Command( + commandType="loadPipette", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", mount="left", pipetteName="p10_single" + ), + ), pe_commands.LoadPipetteCreate( params=pe_commands.LoadPipetteParams( pipetteId="pipette-id-1", @@ -267,6 +332,14 @@ location=Location(slotName="3"), ), ), + protocol_schema_v8.Command( + commandType="loadModule", + params=protocol_schema_v8.Params( + moduleId="module-id-1", + model="magneticModuleV2", + location=Location(slotName="3"), + ), + ), pe_commands.LoadModuleCreate( params=pe_commands.LoadModuleParams( model=ModuleModel("magneticModuleV2"), @@ -294,6 +367,17 @@ displayName="Trash", ), ), + protocol_schema_v8.Command( + commandType="loadLabware", + params=protocol_schema_v8.Params( + labwareId="labware-id-2", + version=1, + namespace="example", + loadName="foo_8_plate_33ul", + location=Location(moduleId="temperatureModuleId"), + displayName="Trash", + ), + ), pe_commands.LoadLabwareCreate( params=pe_commands.LoadLabwareParams( loadName="foo_8_plate_33ul", @@ -332,6 +416,19 @@ flowRate=1.23, ), ), + protocol_schema_v8.Command( + commandType="blowout", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + labwareId="labware-id-2", + wellName="A1", + wellLocation=SD_WellLocation( + origin="bottom", + offset=OffsetVector(x=0, y=0, z=7.89), + ), + flowRate=1.23, + ), + ), pe_commands.BlowOutCreate( params=pe_commands.BlowOutParams( pipetteId="pipette-id-1", @@ -354,6 +451,10 @@ commandType="delay", params=protocol_schema_v7.Params(waitForResume=True, message="hello world"), ), + protocol_schema_v8.Command( + commandType="delay", + params=protocol_schema_v8.Params(waitForResume=True, message="hello world"), + ), pe_commands.WaitForResumeCreate( params=pe_commands.WaitForResumeParams(message="hello world") ), @@ -367,6 +468,10 @@ commandType="delay", params=protocol_schema_v7.Params(seconds=12.34, message="hello world"), ), + protocol_schema_v8.Command( + commandType="delay", + params=protocol_schema_v8.Params(seconds=12.34, message="hello world"), + ), pe_commands.WaitForDurationCreate( params=pe_commands.WaitForDurationParams( seconds=12.34, @@ -383,6 +488,10 @@ commandType="waitForResume", params=protocol_schema_v7.Params(message="hello world"), ), + protocol_schema_v8.Command( + commandType="waitForResume", + params=protocol_schema_v8.Params(message="hello world"), + ), pe_commands.WaitForResumeCreate( params=pe_commands.WaitForResumeParams(message="hello world") ), @@ -396,6 +505,10 @@ commandType="waitForDuration", params=protocol_schema_v7.Params(seconds=12.34, message="hello world"), ), + protocol_schema_v8.Command( + commandType="waitForDuration", + params=protocol_schema_v8.Params(seconds=12.34, message="hello world"), + ), pe_commands.WaitForDurationCreate( params=pe_commands.WaitForDurationParams( seconds=12.34, @@ -422,6 +535,15 @@ forceDirect=True, ), ), + protocol_schema_v8.Command( + commandType="moveToCoordinates", + params=protocol_schema_v8.Params( + pipetteId="pipette-id-1", + coordinates=OffsetVector(x=1.1, y=2.2, z=3.3), + minimumZHeight=123.4, + forceDirect=True, + ), + ), pe_commands.MoveToCoordinatesCreate( params=pe_commands.MoveToCoordinatesParams( pipetteId="pipette-id-1", @@ -466,6 +588,23 @@ ], ), ), + protocol_schema_v8.Command( + commandType="thermocycler/runProfile", + params=protocol_schema_v8.Params( + moduleId="module-id-2", + blockMaxVolumeUl=1.11, + profile=[ + ProfileStep( + celsius=2.22, + holdSeconds=3.33, + ), + ProfileStep( + celsius=4.44, + holdSeconds=5.55, + ), + ], + ), + ), pe_commands.thermocycler.RunProfileCreate( params=pe_commands.thermocycler.RunProfileParams( moduleId="module-id-2", @@ -500,6 +639,15 @@ volumeByWell={"A1": 32, "B2": 50}, ), ), + protocol_schema_v8.Command( + commandType="loadLiquid", + key=None, + params=protocol_schema_v8.Params( + labwareId="labware-id-2", + liquidId="liquid-id-555", + volumeByWell={"A1": 32, "B2": 50}, + ), + ), pe_commands.LoadLiquidCreate( key=None, params=pe_commands.LoadLiquidParams( @@ -618,13 +766,47 @@ def _make_v7_json_protocol( ) +def _make_v8_json_protocol( + *, + labware_definitions: Dict[str, LabwareDefinition] = { + "example/plate/1": _load_labware_definition_data(), + "example/trash/1": _load_labware_definition_data(), + }, + commands: List[protocol_schema_v8.Command] = [], + liquids: Dict[str, Liquid] = { + "liquid-id-555": Liquid( + displayName="water", description="water description", displayColor="#F00" + ) + }, +) -> protocol_schema_v8.ProtocolSchemaV8: + """Return a minimal json protocol with the given elements.""" + return protocol_schema_v8.ProtocolSchemaV8( + otSharedSchema="#/protocol/schemas/8", + schemaVersion=8, + metadata=SD_Metadata(), + robot=Robot( + model="OT-2 Standard", + deckId="ot2_standard", + ), + labwareDefinitions=labware_definitions, + labwareDefinitionSchemaId="opentronsLabwareSchemaV2", + commandSchemaId="opentronsCommandSchemaV8", + commands=commands, + liquidSchemaId="opentronsLiquidSchemaV1", + liquids=liquids, + commandAnnotationSchemaId="opentronsCommandAnnotationSchemaV1", + commandAnnotations=[], + ) + + @pytest.mark.parametrize( - "test_v6_input, test_v7_input,expected_output", VALID_TEST_PARAMS + "test_v6_input,test_v7_input,test_v8_input,expected_output", VALID_TEST_PARAMS ) def test_load_command( subject: JsonTranslator, test_v6_input: protocol_schema_v6.Command, test_v7_input: protocol_schema_v7.Command, + test_v8_input: protocol_schema_v8.Command, expected_output: pe_commands.CommandCreate, ) -> None: """Test translating v6 commands to protocol engine commands.""" @@ -634,8 +816,12 @@ def test_load_command( v7_output = subject.translate_commands( _make_v7_json_protocol(commands=[test_v7_input]) ) + v8_output = subject.translate_commands( + _make_v8_json_protocol(commands=[test_v8_input]) + ) assert v6_output == [expected_output] assert v7_output == [expected_output] + assert v8_output == [expected_output] def test_load_liquid( diff --git a/app-shell/electron-builder.config.js b/app-shell/electron-builder.config.js index d7017740f6b..4d265ac5e3c 100644 --- a/app-shell/electron-builder.config.js +++ b/app-shell/electron-builder.config.js @@ -2,7 +2,11 @@ const path = require('path') const { versionForProject } = require('../scripts/git-version') -const { OT_APP_DEPLOY_BUCKET, OT_APP_DEPLOY_FOLDER } = process.env +const { + OT_APP_DEPLOY_BUCKET, + OT_APP_DEPLOY_FOLDER, + APPLE_TEAM_ID, +} = process.env const DEV_MODE = process.env.NODE_ENV !== 'production' const USE_PYTHON = process.env.NO_PYTHON !== 'true' const project = process.env.OPENTRONS_PROJECT ?? 'robot-stack' @@ -56,6 +60,9 @@ module.exports = async () => ({ icon: project === 'robot-stack' ? 'build/icon.icns' : 'build/three.icns', forceCodeSigning: !DEV_MODE, gatekeeperAssess: true, + notarize: { + teamId: APPLE_TEAM_ID, + }, }, dmg: { icon: null, diff --git a/app-testing/automation/data/protocol_files.py b/app-testing/automation/data/protocol_files.py index 36c04e02587..277d31ea3dc 100644 --- a/app-testing/automation/data/protocol_files.py +++ b/app-testing/automation/data/protocol_files.py @@ -3,9 +3,9 @@ names = Literal[ "OT2_P1000SLeft_None_6_1_SimpleTransfer", - "OT2_P20SRight_None_6_1_SimpleTransferError", "OT2_P20S_P300M_HS_6_1_HS_WithCollision_Error", "OT2_P20S_P300M_NoMods_6_1_TransferReTransferLiquid", + "OT2_P20SRight_None_6_1_SimpleTransferError", "OT2_P300M_P20S_HS_6_1_Smoke620release", "OT2_P300M_P20S_MM_HS_TD_TC_6_1_AllMods_Error", "OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40", @@ -18,21 +18,24 @@ "OT2_None_None_2_13_PythonSyntaxError", "OT2_P10S_P300M_TC1_TM_MM_2_11_Swift", "OT2_P20S_None_2_7_Walkthrough", - "OT2_P300MLeft_MM_TM_2_4_Zymo", "OT2_P300M_P20S_None_2_12_FailOnRun", - "OT2_P300M_P20S_TC_MM_TM_6_13_Smoke620Release", - "OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase", - "OT2_P300SLeft_MM1_MM_TM_2_3_Mix", + "OT2_P300M_P20S_TC_HS_TM_2_13_SmokeTestV3", + "OT2_P300M_P20S_TC_HS_TM_2_14_SmokeTestV3", + "OT2_P300M_P20S_TC_HS_TM_2_15_SmokeTestV3", + "OT2_P300M_P20S_TC_MM_TM_2_13_Smoke620Release", + "OT2_P300MLeft_MM_TM_2_4_Zymo", "OT2_P300S_Thermocycler_Moam_Error", "OT2_P300S_Twinning_Error", + "OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase", + "OT2_P300SLeft_MM1_MM_TM_2_3_Mix", + "OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria", + "OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel", "OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch", - "OT3_P1000SRight_None_2_15_ABR_Simple_Normalize_Long_Right", - "OT3_P50MLeft_P1000MRight_None_2_15_ABRKAPALibraryQuantLongv2", + "OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III", + "OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR", "OT3_P1000MLeft_P50MRight_HS_MM_TC_TM_2_15_ABR3_Illumina_DNA_Enrichment_v4", "OT3_P1000MLeft_P50MRight_HS_MM_TC_TM_2_15_ABR3_Illumina_DNA_Enrichment", "OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x", - "OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel", - "OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR", - "OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III", - "OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria", + "OT3_P1000SRight_None_2_15_ABR_Simple_Normalize_Long_Right", + "OT3_P50MLeft_P1000MRight_None_2_15_ABRKAPALibraryQuantLongv2", ] diff --git a/app-testing/automation/data/protocols.py b/app-testing/automation/data/protocols.py index c3a42c9f8dd..139739b784d 100644 --- a/app-testing/automation/data/protocols.py +++ b/app-testing/automation/data/protocols.py @@ -8,86 +8,81 @@ class Protocols: # The name of the property must match the file_name property # and be in protocol_files.names - OT2_P20S_None_2_7_Walkthrough: Protocol = Protocol( - file_name="OT2_P20S_None_2_7_Walkthrough", - file_extension="py", - protocol_name="OT-2 Guided Walk-through", + ########################################################################################################## + # Begin JSON Protocols ################################################################################### + ########################################################################################################## + + OT2_P1000SLeft_None_6_1_SimpleTransfer: Protocol = Protocol( + file_name="OT2_P1000SLeft_None_6_1_SimpleTransfer", + file_extension="json", + protocol_name="Need Pipette", robot="OT-2", app_error=False, robot_error=False, ) - - OT2_None_None_2_13_PythonSyntaxError: Protocol = Protocol( - file_name="OT2_None_None_2_13_PythonSyntaxError", - file_extension="py", - protocol_name="bad import", - robot="OT-2", - app_error=True, - robot_error=True, - app_analysis_error="No module named 'superspecialmagic'", - robot_analysis_error="?", - ) - - OT2_P10S_P300M_TC1_TM_MM_2_11_Swift: Protocol = Protocol( - file_name="OT2_P10S_P300M_TC1_TM_MM_2_11_Swift", - file_extension="py", - protocol_name="OT2_P10S_P300M_TC1_TM_MM_2_11_Swift.py", + OT2_P20S_P300M_HS_6_1_HS_WithCollision_Error: Protocol = Protocol( + file_name="OT2_P20S_P300M_HS_6_1_HS_WithCollision_Error", + file_extension="json", + protocol_name="HS Collision", robot="OT-2", app_error=False, robot_error=False, + description="""This protocol gives an error in PD.8-Channel pipette cannot access labware8-Channel pipettes cannot access labware or tip racks to the left or right of a Heater-Shaker GEN1 module. Move labware to a different slot to access it with an 8-Channel pipette.If you export it anyway there are NOT analysis errors in the app side analysis.TODO on if there are robot side analysis errors but do not expect them?""", # noqa: E501 ) - - OT2_P300MLeft_MM_TM_2_4_Zymo: Protocol = Protocol( - file_name="OT2_P300MLeft_MM_TM_2_4_Zymo", - file_extension="py", - protocol_name="Zymo Direct-zol96 Magbead RNA", + OT2_P20S_P300M_NoMods_6_1_TransferReTransferLiquid: Protocol = Protocol( + file_name="OT2_P20S_P300M_NoMods_6_1_TransferReTransferLiquid", + file_extension="json", + protocol_name="Transfer- Multi liquid (retransfer)", robot="OT-2", app_error=False, robot_error=False, ) - - OT2_P300S_Thermocycler_Moam_Error: Protocol = Protocol( - file_name="OT2_P300S_Thermocycler_Moam_Error", - file_extension="py", - protocol_name="OT2_P300S_Thermocycler_Moam_Error.py", + OT2_P20SRight_None_6_1_SimpleTransferError: Protocol = Protocol( + file_name="OT2_P20SRight_None_6_1_SimpleTransferError", + file_extension="json", + protocol_name="Have Pipette", robot="OT-2", app_error=True, robot_error=True, - app_analysis_error="DeckConflictError [line 19]: thermocyclerModuleV2 in slot 7 prevents thermocyclerModuleV1 from using slot 7.", # noqa: E501 + app_analysis_error="Cannot aspirate more than pipette max volume", robot_analysis_error="?", ) - - OT2_P300S_Twinning_Error: Protocol = Protocol( - file_name="OT2_P300S_Twinning_Error", - file_extension="py", - protocol_name="My Protocol", + OT2_P300M_P20S_HS_6_1_Smoke620release: Protocol = Protocol( + file_name="OT2_P300M_P20S_HS_6_1_Smoke620release", + file_extension="json", + protocol_name="H/S normal use", + robot="OT-2", + app_error=False, + robot_error=False, + ) + OT2_P300M_P20S_MM_HS_TD_TC_6_1_AllMods_Error: Protocol = Protocol( + file_name="OT2_P300M_P20S_MM_HS_TD_TC_6_1_AllMods_Error", + file_extension="json", + protocol_name="All mods", robot="OT-2", app_error=True, robot_error=True, - app_analysis_error="AttributeError [line 24]: 'InstrumentContext' object has no attribute 'pair_with'", + app_analysis_error="Heater-Shaker cannot open its labware latch while it is shaking.", robot_analysis_error="?", ) - - OT2_P20S_P300M_NoMods_6_1_TransferReTransferLiquid: Protocol = Protocol( - file_name="OT2_P20S_P300M_NoMods_6_1_TransferReTransferLiquid", + OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40: Protocol = Protocol( + file_name="OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40", file_extension="json", - protocol_name="Transfer- Multi liquid (retransfer)", + protocol_name="script_pur_sample_1", robot="OT-2", app_error=False, robot_error=False, ) - - OT2_P300M_P20S_MM_HS_TD_TC_6_1_AllMods_Error: Protocol = Protocol( - file_name="OT2_P300M_P20S_MM_HS_TD_TC_6_1_AllMods_Error", + OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40Error: Protocol = Protocol( + file_name="OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40Error", file_extension="json", - protocol_name="All mods", + protocol_name="script_pur_sample_1", robot="OT-2", app_error=True, robot_error=True, - app_analysis_error="Heater-Shaker cannot open its labware latch while it is shaking.", + app_analysis_error="Cannot aspirate more than pipette max volume", robot_analysis_error="?", ) - OT2_P300M_P20S_NoMod_6_1_MixTransferManyLiquids: Protocol = Protocol( file_name="OT2_P300M_P20S_NoMod_6_1_MixTransferManyLiquids", file_extension="json", @@ -96,7 +91,6 @@ class Protocols: app_error=False, robot_error=False, ) - OT2_P300M_P300S_HS_6_1_HS_NormalUseWithTransfer: Protocol = Protocol( file_name="OT2_P300M_P300S_HS_6_1_HS_NormalUseWithTransfer", file_extension="json", @@ -105,139 +99,168 @@ class Protocols: app_error=False, robot_error=False, ) - - OT2_P20S_P300M_HS_6_1_HS_WithCollision_Error: Protocol = Protocol( - file_name="OT2_P20S_P300M_HS_6_1_HS_WithCollision_Error", + OT2_P300SG1_None_5_2_6_Gen1PipetteSimple: Protocol = Protocol( + file_name="OT2_P300SG1_None_5_2_6_Gen1PipetteSimple", file_extension="json", - protocol_name="HS Collision", + protocol_name="gen1 pipette", robot="OT-2", app_error=False, robot_error=False, - description=""" - This protocol gives an error in PD. - 8-Channel pipette cannot access labware - 8-Channel pipettes cannot access labware or tip racks to the left or right of - a Heater-Shaker GEN1 module. Move labware to a different slot to access it - with an 8-Channel pipette. - If you export it anyway there are NOT analysis errors in the app side analysis. - TODO on if there are robot side analysis errors but do not expect them? - """, ) - - OT2_P300SLeft_MM1_MM_TM_2_3_Mix: Protocol = Protocol( - file_name="OT2_P300SLeft_MM1_MM_TM_2_3_Mix", - file_extension="py", - protocol_name="OT2_P300SLeft_MM1_MM_TM_2_3_Mix.py", + OT2_P300SLeft_MM_TM_TM_5_2_6_MOAMTemps: Protocol = Protocol( + file_name="OT2_P300SLeft_MM_TM_TM_5_2_6_MOAMTemps", + file_extension="json", + protocol_name="MoaM", robot="OT-2", app_error=False, robot_error=False, ) - OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase: Protocol = Protocol( - file_name="OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase", + ############################################################################################################ + # Begin Python Protocols ################################################################################### + ############################################################################################################ + + OT2_None_None_2_12_Python310SyntaxRobotAnalysisOnlyError: Protocol = Protocol( + file_name="OT2_None_None_2_12_Python310SyntaxRobotAnalysisOnlyError", file_extension="py", - protocol_name="OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase.py", + protocol_name="🛠 3.10 only Python 🛠", robot="OT-2", app_error=False, - robot_error=False, + robot_error=True, + robot_analysis_error="?", ) - - OT2_P300M_P20S_TC_MM_TM_6_13_Smoke620Release: Protocol = Protocol( - file_name="OT2_P300M_P20S_TC_MM_TM_6_13_Smoke620Release", + OT2_None_None_2_13_PythonSyntaxError: Protocol = Protocol( + file_name="OT2_None_None_2_13_PythonSyntaxError", file_extension="py", - protocol_name="🛠 Logo-Modules-CustomLabware 🛠", + protocol_name="bad import", + robot="OT-2", + app_error=True, + robot_error=True, + app_analysis_error="No module named 'superspecialmagic'", + robot_analysis_error="?", + ) + OT2_P10S_P300M_TC1_TM_MM_2_11_Swift: Protocol = Protocol( + file_name="OT2_P10S_P300M_TC1_TM_MM_2_11_Swift", + file_extension="py", + protocol_name="OT2_P10S_P300M_TC1_TM_MM_2_11_Swift.py", robot="OT-2", app_error=False, robot_error=False, - custom_labware=["cpx_4_tuberack_100ul"], ) - - OT2_P300SLeft_MM_TM_TM_5_2_6_MOAMTemps: Protocol = Protocol( - file_name="OT2_P300SLeft_MM_TM_TM_5_2_6_MOAMTemps", - file_extension="json", - protocol_name="MoaM", + OT2_P20S_None_2_7_Walkthrough: Protocol = Protocol( + file_name="OT2_P20S_None_2_7_Walkthrough", + file_extension="py", + protocol_name="OT-2 Guided Walk-through", robot="OT-2", app_error=False, robot_error=False, ) - - OT2_P20SRight_None_6_1_SimpleTransferError: Protocol = Protocol( - file_name="OT2_P20SRight_None_6_1_SimpleTransferError", - file_extension="json", - protocol_name="Have Pipette", + OT2_P300M_P20S_None_2_12_FailOnRun: Protocol = Protocol( + file_name="OT2_P300M_P20S_None_2_12_FailOnRun", + file_extension="py", + protocol_name="Will fail on run", robot="OT-2", - app_error=True, - robot_error=True, - app_analysis_error="Cannot aspirate more than pipette max volume", - robot_analysis_error="?", + app_error=False, + robot_error=False, ) - - OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40: Protocol = Protocol( - file_name="OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40", - file_extension="json", - protocol_name="script_pur_sample_1", + OT2_P300M_P20S_TC_HS_TM_2_13_SmokeTestV3: Protocol = Protocol( + file_name="OT2_P300M_P20S_TC_HS_TM_2_13_SmokeTestV3", + file_extension="py", + protocol_name="🛠️ 2.13 Smoke Test V3 🪄", robot="OT-2", app_error=False, robot_error=False, + custom_labware=["cpx_4_tuberack_100ul"], ) - - OT2_P300M_P20S_None_2_12_FailOnRun: Protocol = Protocol( - file_name="OT2_P300M_P20S_None_2_12_FailOnRun", + OT2_P300M_P20S_TC_HS_TM_2_14_SmokeTestV3: Protocol = Protocol( + file_name="OT2_P300M_P20S_TC_HS_TM_2_14_SmokeTestV3", file_extension="py", - protocol_name="Will fail on run", + protocol_name="🛠️ 2.14 Smoke Test V3 🪄", robot="OT-2", app_error=False, robot_error=False, + custom_labware=["cpx_4_tuberack_100ul"], ) - - OT2_P300SG1_None_5_2_6_Gen1PipetteSimple: Protocol = Protocol( - file_name="OT2_P300SG1_None_5_2_6_Gen1PipetteSimple", - file_extension="json", - protocol_name="gen1 pipette", + OT2_P300M_P20S_TC_HS_TM_2_15_SmokeTestV3: Protocol = Protocol( + file_name="OT2_P300M_P20S_TC_HS_TM_2_15_SmokeTestV3", + file_extension="py", + protocol_name="🛠️ 2.15 Smoke Test V3 🪄", robot="OT-2", app_error=False, robot_error=False, + custom_labware=["cpx_4_tuberack_100ul"], ) - - OT2_P1000SLeft_None_6_1_SimpleTransfer: Protocol = Protocol( - file_name="OT2_P1000SLeft_None_6_1_SimpleTransfer", - file_extension="json", - protocol_name="Need Pipette", + OT2_P300M_P20S_TC_MM_TM_2_13_Smoke620Release: Protocol = Protocol( + file_name="OT2_P300M_P20S_TC_MM_TM_2_13_Smoke620Release", + file_extension="py", + protocol_name="🛠 Logo-Modules-CustomLabware 🛠", robot="OT-2", app_error=False, robot_error=False, + custom_labware=["cpx_4_tuberack_100ul"], ) - - OT2_P300M_P20S_HS_6_1_Smoke620release: Protocol = Protocol( - file_name="OT2_P300M_P20S_HS_6_1_Smoke620release", - file_extension="json", - protocol_name="H/S normal use", + OT2_P300MLeft_MM_TM_2_4_Zymo: Protocol = Protocol( + file_name="OT2_P300MLeft_MM_TM_2_4_Zymo", + file_extension="py", + protocol_name="Zymo Direct-zol96 Magbead RNA", robot="OT-2", app_error=False, robot_error=False, ) - - OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40Error: Protocol = Protocol( - file_name="OT2_P300M_P20S_MM_TM_TC1_5_2_6_PD40Error", - file_extension="json", - protocol_name="script_pur_sample_1", + OT2_P300S_Thermocycler_Moam_Error: Protocol = Protocol( + file_name="OT2_P300S_Thermocycler_Moam_Error", + file_extension="py", + protocol_name="OT2_P300S_Thermocycler_Moam_Error.py", robot="OT-2", app_error=True, robot_error=True, - app_analysis_error="Cannot aspirate more than pipette max volume", + app_analysis_error="DeckConflictError [line 19]: thermocyclerModuleV2 in slot 7 prevents thermocyclerModuleV1 from using slot 7.", # noqa: E501 robot_analysis_error="?", ) - - OT2_None_None_2_12_Python310SyntaxRobotAnalysisOnlyError: Protocol = Protocol( - file_name="OT2_None_None_2_12_Python310SyntaxRobotAnalysisOnlyError", + OT2_P300S_Twinning_Error: Protocol = Protocol( + file_name="OT2_P300S_Twinning_Error", file_extension="py", - protocol_name="🛠 3.10 only Python 🛠", + protocol_name="My Protocol", robot="OT-2", - app_error=False, + app_error=True, robot_error=True, + app_analysis_error="AttributeError [line 24]: 'InstrumentContext' object has no attribute 'pair_with'", robot_analysis_error="?", ) - + OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase: Protocol = Protocol( + file_name="OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase", + file_extension="py", + protocol_name="OT2_P300SLeft_MM1_MM_2_2_EngageMagHeightFromBase.py", + robot="OT-2", + app_error=False, + robot_error=False, + ) + OT2_P300SLeft_MM1_MM_TM_2_3_Mix: Protocol = Protocol( + file_name="OT2_P300SLeft_MM1_MM_TM_2_3_Mix", + file_extension="py", + protocol_name="OT2_P300SLeft_MM1_MM_TM_2_3_Mix.py", + robot="OT-2", + app_error=False, + robot_error=False, + ) + OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria: Protocol = Protocol( + file_name="OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria", + file_extension="py", + protocol_name="Quick Zymo Magbead RNA Extraction with Lysis: Bacteria 96 Channel Deletion Test", + robot="OT-3", + app_error=False, + robot_error=False, + custom_labware=["opentrons_ot3_96_tiprack_1000ul_rss"], + ) + OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel: Protocol = Protocol( + file_name="OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel", + file_extension="py", + protocol_name="Omega HDQ DNA Extraction: Bacteria 96 FOR ABR TESTING", + robot="OT-3", + app_error=False, + robot_error=False, + custom_labware=["opentrons_ot3_96_tiprack_1000ul_rss"], + ) OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch: Protocol = Protocol( file_name="OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch", file_extension="py", @@ -247,26 +270,24 @@ class Protocols: robot_error=False, custom_labware=["opentrons_ot3_96_tiprack_200ul_rss"], ) - - OT3_P1000SRight_None_2_15_ABR_Simple_Normalize_Long_Right: Protocol = Protocol( - file_name="OT3_P1000SRight_None_2_15_ABR_Simple_Normalize_Long_Right", + OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III: Protocol = Protocol( + file_name="OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III", file_extension="py", - protocol_name="OT3 ABR Simple Normalize Long", + protocol_name="Illumina DNA Prep 96x Head PART III", robot="OT-3", app_error=False, robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_200ul_rss"], + custom_labware=["opentrons_ot3_96_tiprack_200ul_rss", "opentrons_ot3_96_tiprack_50ul_rss"], ) - - OT3_P50MLeft_P1000MRight_None_2_15_ABRKAPALibraryQuantLongv2: Protocol = Protocol( - file_name="OT3_P50MLeft_P1000MRight_None_2_15_ABRKAPALibraryQuantLongv2", + OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR: Protocol = Protocol( + file_name="OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR", file_extension="py", - protocol_name="OT3 ABR KAPA Library Quant v2", + protocol_name="IDT xGen EZ 96x Head PART I-III ABR", robot="OT-3", app_error=False, robot_error=False, + custom_labware=["opentrons_ot3_96_tiprack_50ul_rss", "opentrons_ot3_96_tiprack_200ul_rss"], ) - OT3_P1000MLeft_P50MRight_HS_MM_TC_TM_2_15_ABR3_Illumina_DNA_Enrichment_v4: Protocol = Protocol( file_name="OT3_P1000MLeft_P50MRight_HS_MM_TC_TM_2_15_ABR3_Illumina_DNA_Enrichment_v4", file_extension="py", @@ -275,7 +296,6 @@ class Protocols: app_error=False, robot_error=False, ) - OT3_P1000MLeft_P50MRight_HS_MM_TC_TM_2_15_ABR3_Illumina_DNA_Enrichment: Protocol = Protocol( file_name="OT3_P1000MLeft_P50MRight_HS_MM_TC_TM_2_15_ABR3_Illumina_DNA_Enrichment", file_extension="py", @@ -284,7 +304,6 @@ class Protocols: app_error=False, robot_error=False, ) - OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x: Protocol = Protocol( file_name="OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x", file_extension="py", @@ -293,43 +312,20 @@ class Protocols: app_error=False, robot_error=False, ) - - OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel: Protocol = Protocol( - file_name="OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel", - file_extension="py", - protocol_name="Omega HDQ DNA Extraction: Bacteria 96 FOR ABR TESTING", - robot="OT-3", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_1000ul_rss"], - ) - - OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR: Protocol = Protocol( - file_name="OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR", - file_extension="py", - protocol_name="IDT xGen EZ 96x Head PART I-III ABR", - robot="OT-3", - app_error=False, - robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_50ul_rss", "opentrons_ot3_96_tiprack_200ul_rss"], - ) - - OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III: Protocol = Protocol( - file_name="OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III", + OT3_P1000SRight_None_2_15_ABR_Simple_Normalize_Long_Right: Protocol = Protocol( + file_name="OT3_P1000SRight_None_2_15_ABR_Simple_Normalize_Long_Right", file_extension="py", - protocol_name="Illumina DNA Prep 96x Head PART III", + protocol_name="OT3 ABR Simple Normalize Long", robot="OT-3", app_error=False, robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_200ul_rss", "opentrons_ot3_96_tiprack_50ul_rss"], + custom_labware=["opentrons_ot3_96_tiprack_200ul_rss"], ) - - OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria: Protocol = Protocol( - file_name="OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria", + OT3_P50MLeft_P1000MRight_None_2_15_ABRKAPALibraryQuantLongv2: Protocol = Protocol( + file_name="OT3_P50MLeft_P1000MRight_None_2_15_ABRKAPALibraryQuantLongv2", file_extension="py", - protocol_name="Quick Zymo Magbead RNA Extraction with Lysis: Bacteria 96 Channel Deletion Test", + protocol_name="OT3 ABR KAPA Library Quant v2", robot="OT-3", app_error=False, robot_error=False, - custom_labware=["opentrons_ot3_96_tiprack_1000ul_rss"], ) diff --git a/app-testing/ci-tools/linux_get_chromedriver.sh b/app-testing/ci-tools/linux_get_chromedriver.sh index a064ba8dd42..c42e7d4b79b 100755 --- a/app-testing/ci-tools/linux_get_chromedriver.sh +++ b/app-testing/ci-tools/linux_get_chromedriver.sh @@ -3,13 +3,16 @@ set -eo pipefail VERSION=$1 +URL="https://github.com/electron/electron/releases/download/v${VERSION}/chromedriver-v${VERSION}-linux-x64.zip" CHROMEAPP=google-chrome if ! type -a google-chrome >/dev/null 2>&1; then sudo apt-get update sudo apt-get install -y google-chrome fi -wget -c -nc --retry-connrefused --tries=0 https://github.com/electron/electron/releases/download/v${VERSION}/chromedriver-v${VERSION}-linux-x64.zip +echo "Downloading chromedriver" +echo "URL: $URL" +wget -c -nc --retry-connrefused --tries=0 $URL unzip -o -q chromedriver-v${VERSION}-linux-x64.zip sudo mv chromedriver /usr/local/bin/chromedriver rm chromedriver-v${VERSION}-linux-x64.zip diff --git a/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_13_SmokeTestV3.py b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_13_SmokeTestV3.py new file mode 100644 index 00000000000..43a2e61dff5 --- /dev/null +++ b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_13_SmokeTestV3.py @@ -0,0 +1,212 @@ +"""Smoke Test v3.0 """ +# https://opentrons.atlassian.net/projects/RQA?selectedItem=com.atlassian.plugins.atlassian-connect-plugin:com.kanoah.test-manager__main-project-page#!/testCase/QB-T497 +from opentrons import protocol_api + +metadata = { + "protocolName": "🛠️ 2.13 Smoke Test V3 🪄", + "author": "Opentrons Engineering ", + "source": "Software Testing Team", + "description": ("Description of the protocol that is longish \n has \n returns and \n emoji 😊 ⬆️ "), +} + +requirements = {"robotType": "OT-2", "apiLevel": "2.13"} + + +def run(ctx: protocol_api.ProtocolContext) -> None: + """This method is run by the protocol engine.""" + + ctx.set_rail_lights(True) + ctx.comment(f"Let there be light! {ctx.rail_lights_on} 🌠🌠🌠") + ctx.comment(f"Is the door is closed? {ctx.door_closed} 🚪🚪🚪") + ctx.comment(f"Is this a simulation? {ctx.is_simulating()} 🔮🔮🔮") + ctx.comment(f"Running against API Version: {ctx.api_version}") + + # deck positions + tips_300ul_position = "5" + tips_20ul_position = "4" + dye_source_position = "3" + logo_position = "2" + temperature_position = "9" + custom_lw_position = "6" + hs_position = "1" + + # Thermocycler has a default position that covers Slots 7, 8, 10, and 11. + # This is the only valid location for the Thermocycler on the OT-2 deck. + # This position is a default parameter when declaring the TC so you do not need to specify. + + # 300ul tips + tips_300ul = [ + ctx.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=tips_300ul_position, + label="300ul tips", + ) + ] + + # 20ul tips + tips_20ul = [ + ctx.load_labware( + load_name="opentrons_96_tiprack_20ul", + location=tips_20ul_position, + label="20ul tips", + ) + ] + + # pipettes + pipette_left = ctx.load_instrument(instrument_name="p300_multi_gen2", mount="left", tip_racks=tips_300ul) + + pipette_right = ctx.load_instrument(instrument_name="p20_single_gen2", mount="right", tip_racks=tips_20ul) + + # modules https://docs.opentrons.com/v2/new_modules.html#available-modules + hs_module = ctx.load_module("heaterShakerModuleV1", hs_position) + temperature_module = ctx.load_module("temperature module gen2", temperature_position) + thermocycler_module = ctx.load_module("thermocycler module gen2") + + # module labware + temp_plate = temperature_module.load_labware( + "opentrons_96_aluminumblock_nest_wellplate_100ul", + label="Temperature-Controlled plate", + ) + hs_plate = hs_module.load_labware("opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt") + tc_plate = thermocycler_module.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") + + custom_labware = ctx.load_labware( + "cpx_4_tuberack_100ul", + custom_lw_position, + "4 tubes", + "custom_beta", + ) + + # create plates and pattern list + logo_destination_plate = ctx.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + location=logo_position, + label="logo destination", + ) + + dye_container = ctx.load_labware( + load_name="nest_12_reservoir_15ml", + location=dye_source_position, + label="dye container", + ) + + dye_source = dye_container.wells_by_name()["A2"] + + # Well Location set-up + dye_destination_wells = [ + logo_destination_plate.wells_by_name()["C7"], + logo_destination_plate.wells_by_name()["D6"], + logo_destination_plate.wells_by_name()["D7"], + logo_destination_plate.wells_by_name()["D8"], + logo_destination_plate.wells_by_name()["E5"], + ] + + hs_module.close_labware_latch() + + # Distribute dye + pipette_right.pick_up_tip() + pipette_right.distribute( + volume=18, + source=dye_source, + dest=dye_destination_wells, + new_tip="never", + ) + pipette_right.drop_tip() + + # transfer + transfer_destinations = [ + logo_destination_plate.wells_by_name()["A11"], + logo_destination_plate.wells_by_name()["B11"], + logo_destination_plate.wells_by_name()["C11"], + ] + pipette_right.pick_up_tip() + pipette_right.transfer( + volume=60, + source=dye_container.wells_by_name()["A2"], + dest=transfer_destinations, + new_tip="never", + touch_tip=True, + blow_out=True, + blowout_location="destination well", + mix_before=(3, 20), + mix_after=(1, 20), + mix_touch_tip=True, + ) + + # consolidate + pipette_right.consolidate( + volume=20, + source=transfer_destinations, + dest=dye_container.wells_by_name()["A5"], + new_tip="never", + touch_tip=False, + blow_out=True, + blowout_location="destination well", + mix_before=(3, 20), + ) + + # well to well + pipette_right.return_tip() + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=5, location=logo_destination_plate.wells_by_name()["A11"]) + pipette_right.air_gap(volume=10) + ctx.delay(seconds=3) + pipette_right.dispense(volume=5, location=logo_destination_plate.wells_by_name()["H11"]) + + # move to + pipette_right.move_to(logo_destination_plate.wells_by_name()["E12"].top()) + pipette_right.move_to(logo_destination_plate.wells_by_name()["E11"].bottom()) + pipette_right.blow_out() + # touch tip + # pipette ends in the middle of the well as of 6.3.0 in all touch_tip + pipette_right.touch_tip(location=logo_destination_plate.wells_by_name()["H1"]) + pipette_right.return_tip() + + # Play with the modules + temperature_module.await_temperature(25) + + hs_module.set_and_wait_for_shake_speed(466) + ctx.delay(seconds=5) + + hs_module.set_and_wait_for_temperature(38) + + thermocycler_module.open_lid() + thermocycler_module.close_lid() + thermocycler_module.set_lid_temperature(38) # 37 is the minimum + thermocycler_module.set_block_temperature(temperature=28, hold_time_seconds=5) + thermocycler_module.deactivate_block() + thermocycler_module.deactivate_lid() + thermocycler_module.open_lid() + + hs_module.deactivate_shaker() + + ctx.pause("This is a pause") + + # dispense to modules + + # to temperature module + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=15, location=dye_source) + pipette_right.dispense(volume=15, location=temp_plate.well(0)) + pipette_right.drop_tip() + + # to heater shaker + pipette_left.pick_up_tip() + pipette_left.aspirate(volume=50, location=dye_source) + pipette_left.dispense(volume=50, location=hs_plate.well(0)) + hs_module.set_and_wait_for_shake_speed(350) + ctx.delay(seconds=5) + hs_module.deactivate_shaker() + + # to custom labware + # This labware does not EXIST!!!! so... + # Use tip rack lid to catch dye on wet run + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=10, location=dye_source, rate=2.0) + pipette_right.dispense(volume=10, location=custom_labware.well(3), rate=1.5) + pipette_right.drop_tip() + + # to thermocycler + pipette_left.aspirate(volume=75, location=dye_source) + pipette_left.dispense(volume=60, location=tc_plate.wells_by_name()["A6"]) + pipette_left.drop_tip() diff --git a/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_14_SmokeTestV3.py b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_14_SmokeTestV3.py new file mode 100644 index 00000000000..3413a55af87 --- /dev/null +++ b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_14_SmokeTestV3.py @@ -0,0 +1,230 @@ +"""Smoke Test v3.0 """ +# https://opentrons.atlassian.net/projects/RQA?selectedItem=com.atlassian.plugins.atlassian-connect-plugin:com.kanoah.test-manager__main-project-page#!/testCase/QB-T497 +from opentrons import protocol_api + +metadata = { + "protocolName": "🛠️ 2.14 Smoke Test V3 🪄", + "author": "Opentrons Engineering ", + "source": "Software Testing Team", + "description": ("Description of the protocol that is longish \n has \n returns and \n emoji 😊 ⬆️ "), +} + +requirements = {"robotType": "OT-2", "apiLevel": "2.14"} + + +def run(ctx: protocol_api.ProtocolContext) -> None: + """This method is run by the protocol engine.""" + + ctx.set_rail_lights(True) + ctx.comment(f"Let there be light! {ctx.rail_lights_on} 🌠🌠🌠") + ctx.comment(f"Is the door is closed? {ctx.door_closed} 🚪🚪🚪") + ctx.comment(f"Is this a simulation? {ctx.is_simulating()} 🔮🔮🔮") + ctx.comment(f"Running against API Version: {ctx.api_version}") + + # deck positions + tips_300ul_position = "5" + tips_20ul_position = "4" + dye_source_position = "3" + logo_position = "2" + temperature_position = "9" + custom_lw_position = "6" + hs_position = "1" + + # Thermocycler has a default position that covers Slots 7, 8, 10, and 11. + # This is the only valid location for the Thermocycler on the OT-2 deck. + # This position is a default parameter when declaring the TC so you do not need to specify. + + # 300ul tips + tips_300ul = [ + ctx.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=tips_300ul_position, + label="300ul tips", + ) + ] + + # 20ul tips + tips_20ul = [ + ctx.load_labware( + load_name="opentrons_96_tiprack_20ul", + location=tips_20ul_position, + label="20ul tips", + ) + ] + + # pipettes + pipette_left = ctx.load_instrument(instrument_name="p300_multi_gen2", mount="left", tip_racks=tips_300ul) + + pipette_right = ctx.load_instrument(instrument_name="p20_single_gen2", mount="right", tip_racks=tips_20ul) + + # modules https://docs.opentrons.com/v2/new_modules.html#available-modules + hs_module = ctx.load_module("heaterShakerModuleV1", hs_position) + temperature_module = ctx.load_module("temperature module gen2", temperature_position) + thermocycler_module = ctx.load_module("thermocycler module gen2") + + # module labware + temp_plate = temperature_module.load_labware( + "opentrons_96_aluminumblock_nest_wellplate_100ul", + label="Temperature-Controlled plate", + ) + hs_plate = hs_module.load_labware("opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt") + tc_plate = thermocycler_module.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") + + # A 2.14 difference, no params specified, still should find it. + custom_labware = ctx.load_labware( + "cpx_4_tuberack_100ul", + custom_lw_position, + label="4 custom tubes", + ) + + # create plates and pattern list + logo_destination_plate = ctx.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + location=logo_position, + label="logo destination", + ) + + dye_container = ctx.load_labware( + load_name="nest_12_reservoir_15ml", + location=dye_source_position, + label="dye container", + ) + + dye_source = dye_container.wells_by_name()["A2"] + + # Well Location set-up + dye_destination_wells = [ + logo_destination_plate.wells_by_name()["C7"], + logo_destination_plate.wells_by_name()["D6"], + logo_destination_plate.wells_by_name()["D7"], + logo_destination_plate.wells_by_name()["D8"], + logo_destination_plate.wells_by_name()["E5"], + ] + + # >= 2.14 define_liquid and load_liquid + water = ctx.define_liquid( + name="water", description="H₂O", display_color="#42AB2D" + ) # subscript 2 https://www.compart.com/en/unicode/U+2082 + + acetone = ctx.define_liquid( + name="acetone", description="C₃H₆O", display_color="#38588a" + ) # subscript 3 https://www.compart.com/en/unicode/U+2083 + # subscript 6 https://www.compart.com/en/unicode/U+2086 + + dye_container.wells_by_name()["A1"].load_liquid(liquid=water, volume=4000) + dye_container.wells_by_name()["A2"].load_liquid(liquid=water, volume=2000) + dye_container.wells_by_name()["A5"].load_liquid(liquid=acetone, volume=555.55555) + + # 2 different liquids in the same well + dye_container.wells_by_name()["A8"].load_liquid(liquid=water, volume=900.00) + dye_container.wells_by_name()["A8"].load_liquid(liquid=acetone, volume=1001.11) + + hs_module.close_labware_latch() + + # Distribute dye + pipette_right.pick_up_tip() + pipette_right.distribute( + volume=18, + source=dye_source, + dest=dye_destination_wells, + new_tip="never", + ) + pipette_right.drop_tip() + + # transfer + transfer_destinations = [ + logo_destination_plate.wells_by_name()["A11"], + logo_destination_plate.wells_by_name()["B11"], + logo_destination_plate.wells_by_name()["C11"], + ] + pipette_right.pick_up_tip() + + pipette_right.transfer( + volume=60, + source=dye_container.wells_by_name()["A2"], + dest=transfer_destinations, + new_tip="never", + touch_tip=True, + blow_out=True, + blowout_location="destination well", + mix_before=(3, 20), + mix_after=(1, 20), + mix_touch_tip=True, + ) + + # consolidate + pipette_right.consolidate( + volume=20, + source=transfer_destinations, + dest=dye_container.wells_by_name()["A5"], + new_tip="never", + touch_tip=False, + blow_out=True, + blowout_location="destination well", + mix_before=(3, 20), + ) + + # well to well + pipette_right.return_tip() + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=5, location=logo_destination_plate.wells_by_name()["A11"]) + pipette_right.air_gap(volume=10) + ctx.delay(seconds=3) + pipette_right.dispense(volume=5, location=logo_destination_plate.wells_by_name()["H11"]) + + # move to + pipette_right.move_to(logo_destination_plate.wells_by_name()["E12"].top()) + pipette_right.move_to(logo_destination_plate.wells_by_name()["E11"].bottom()) + pipette_right.blow_out() + # touch tip + # pipette ends in the middle of the well as of 6.3.0 in all touch_tip + pipette_right.touch_tip(location=logo_destination_plate.wells_by_name()["H1"]) + ctx.pause("Is the pipette tip in the middle of the well?") + pipette_right.return_tip() + + # Play with the modules + temperature_module.await_temperature(25) + + hs_module.set_and_wait_for_shake_speed(466) + ctx.delay(seconds=5) + + hs_module.set_and_wait_for_temperature(38) + + thermocycler_module.open_lid() + thermocycler_module.close_lid() + thermocycler_module.set_lid_temperature(38) # 37 is the minimum + thermocycler_module.set_block_temperature(temperature=28, hold_time_seconds=5) + thermocycler_module.deactivate_block() + thermocycler_module.deactivate_lid() + thermocycler_module.open_lid() + + hs_module.deactivate_shaker() + + # dispense to modules + + # to temperature module + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=15, location=dye_source) + pipette_right.dispense(volume=15, location=temp_plate.well(0)) + pipette_right.drop_tip() + + # to heater shaker + pipette_left.pick_up_tip() + pipette_left.aspirate(volume=50, location=dye_source) + pipette_left.dispense(volume=50, location=hs_plate.well(0)) + hs_module.set_and_wait_for_shake_speed(350) + ctx.delay(seconds=5) + hs_module.deactivate_shaker() + + # to custom labware + # This labware does not EXIST!!!! so... + # Use tip rack lid to catch dye on wet run + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=10, location=dye_source, rate=2.0) + pipette_right.dispense(volume=10, location=custom_labware.well(3), rate=1.5) + pipette_right.drop_tip() + + # to thermocycler + pipette_left.aspirate(volume=75, location=dye_source) + pipette_left.dispense(volume=60, location=tc_plate.wells_by_name()["A6"]) + pipette_left.drop_tip() diff --git a/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_15_SmokeTestV3.py b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_15_SmokeTestV3.py new file mode 100644 index 00000000000..c60e647844d --- /dev/null +++ b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_HS_TM_2_15_SmokeTestV3.py @@ -0,0 +1,322 @@ +"""Smoke Test v3.0 """ +# https://opentrons.atlassian.net/projects/RQA?selectedItem=com.atlassian.plugins.atlassian-connect-plugin:com.kanoah.test-manager__main-project-page#!/testCase/QB-T497 +from opentrons import protocol_api + +metadata = { + "protocolName": "🛠️ 2.15 Smoke Test V3 🪄", + "author": "Opentrons Engineering ", + "source": "Software Testing Team", + "description": ("Description of the protocol that is longish \n has \n returns and \n emoji 😊 ⬆️ "), +} + +requirements = {"robotType": "OT-2", "apiLevel": "2.15"} + + +def run(ctx: protocol_api.ProtocolContext) -> None: + """This method is run by the protocol engine.""" + + ctx.set_rail_lights(True) + ctx.comment(f"Let there be light! {ctx.rail_lights_on} 🌠🌠🌠") + ctx.comment(f"Is the door is closed? {ctx.door_closed} 🚪🚪🚪") + ctx.comment(f"Is this a simulation? {ctx.is_simulating()} 🔮🔮🔮") + ctx.comment(f"Running against API Version: {ctx.api_version}") + + # deck positions + tips_300ul_position = "5" + tips_20ul_position = "4" + dye_source_position = "3" + logo_position = "2" + temperature_position = "9" + custom_lw_position = "6" + hs_position = "1" + + # Thermocycler has a default position that covers Slots 7, 8, 10, and 11. + # This is the only valid location for the Thermocycler on the OT-2 deck. + # This position is a default parameter when declaring the TC so you do not need to specify. + + # 300ul tips + tips_300ul = [ + ctx.load_labware( + load_name="opentrons_96_tiprack_300ul", + location=tips_300ul_position, + label="300ul tips", + ) + ] + + # 20ul tips + tips_20ul = [ + ctx.load_labware( + load_name="opentrons_96_tiprack_20ul", + location=tips_20ul_position, + label="20ul tips", + ) + ] + + # pipettes + pipette_left = ctx.load_instrument(instrument_name="p300_multi_gen2", mount="left", tip_racks=tips_300ul) + + pipette_right = ctx.load_instrument(instrument_name="p20_single_gen2", mount="right", tip_racks=tips_20ul) + + # modules https://docs.opentrons.com/v2/new_modules.html#available-modules + hs_module = ctx.load_module("heaterShakerModuleV1", hs_position) + temperature_module = ctx.load_module("temperature module gen2", temperature_position) + thermocycler_module = ctx.load_module("thermocycler module gen2") + + # module labware + temp_adapter = temperature_module.load_adapter("opentrons_96_well_aluminum_block") + temp_plate = temp_adapter.load_labware( + "nest_96_wellplate_100ul_pcr_full_skirt", + label="Temperature-Controlled plate", + ) + hs_plate = hs_module.load_labware(name="nest_96_wellplate_100ul_pcr_full_skirt", adapter="opentrons_96_pcr_adapter") + tc_plate = thermocycler_module.load_labware("nest_96_wellplate_100ul_pcr_full_skirt") + + # A 2.14 difference, no params specified, still should find it. + custom_labware = ctx.load_labware( + "cpx_4_tuberack_100ul", + custom_lw_position, + label="4 custom tubes", + ) + + # create plates and pattern list + logo_destination_plate = ctx.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + location=logo_position, + label="logo destination", + ) + + dye_container = ctx.load_labware( + load_name="nest_12_reservoir_15ml", + location=dye_source_position, + label="dye container", + ) + + dye_source = dye_container.wells_by_name()["A2"] + + # Well Location set-up + dye_destination_wells = [ + logo_destination_plate.wells_by_name()["C7"], + logo_destination_plate.wells_by_name()["D6"], + logo_destination_plate.wells_by_name()["D7"], + logo_destination_plate.wells_by_name()["D8"], + logo_destination_plate.wells_by_name()["E5"], + ] + + # >= 2.14 define_liquid and load_liquid + water = ctx.define_liquid( + name="water", description="H₂O", display_color="#42AB2D" + ) # subscript 2 https://www.compart.com/en/unicode/U+2082 + + acetone = ctx.define_liquid( + name="acetone", description="C₃H₆O", display_color="#38588a" + ) # subscript 3 https://www.compart.com/en/unicode/U+2083 + # subscript 6 https://www.compart.com/en/unicode/U+2086 + + dye_container.wells_by_name()["A1"].load_liquid(liquid=water, volume=4000) + dye_container.wells_by_name()["A2"].load_liquid(liquid=water, volume=2000) + dye_container.wells_by_name()["A5"].load_liquid(liquid=acetone, volume=555.55555) + + # 2 different liquids in the same well + dye_container.wells_by_name()["A8"].load_liquid(liquid=water, volume=900.00) + dye_container.wells_by_name()["A8"].load_liquid(liquid=acetone, volume=1001.11) + + hs_module.close_labware_latch() + + pipette_right.pick_up_tip() + + ######################################## + # Manual Deck State Modification Start # + ######################################## + + # -------------------------- # + # Added in API version: 2.15 # + # -------------------------- # + + # Putting steps for this at beginning of protocol so you can do the manual stuff + # then walk away to let the rest of the protocol execute + + # The test flow is as follows: + # 1. Remove the existing PCR plate from slot 2 + # 2. Move the reservoir from slot 3 to slot 2 + # 3. Pickup P20 tip, move pipette to reservoir A1 in slot 2 + # 4. Pause and ask user to validate that the tip is in the middle of reservoir A1 in slot 2 + # 5. Move the reservoir back to slot 3 from slot 2 + # 6. Move pipette to reservoir A1 in slot 3 + # 7. Pause and ask user to validate that the tip is in the middle of reservoir A1 in slot 3 + # 8. Move custom labware from slot 6 to slot 2 + # 9. Move pipette to well A1 in slot 2 + # 10. Pause and ask user to validate that the tip is in the middle of well A1 in slot 2 + # 11. Move the custom labware back to slot 6 from slot 2 + # 12. Move pipette to well A1 in slot 6 + # 13. Pause and ask user to validate that the tip is in the middle of well A1 in slot 6 + # 14. Move the offdeck PCR plate back to slot 2 + # 15. Move pipette to well A1 in slot 2 + # 16. Pause and ask user to validate that the tip is in the middle of well A1 in slot 2 + + # In effect, nothing will actually change to the protocol, + # but we will be able to test that the UI responds appropriately. + + # Note: + # logo_destination_plate is a nest_96_wellplate_100ul_pcr_full_skirt - starting position is slot 2 + # dye_container is a nest_12_reservoir_15ml - starting position is slot 3 + + # Step 1 + ctx.move_labware( + labware=logo_destination_plate, + new_location=protocol_api.OFF_DECK, + ) + + # Step 2 + ctx.move_labware(labware=dye_container, new_location="2") + + # Step 3 + pipette_right.move_to(location=dye_container.wells_by_name()["A1"].top()) + + # Step 4 + ctx.pause("Is the pipette tip in the middle of reservoir A1 in slot 2?") + + # Step 5 + ctx.move_labware(labware=dye_container, new_location="3") + + # Step 6 + pipette_right.move_to(location=dye_container.wells_by_name()["A1"].top()) + + # Step 7 + ctx.pause("Is the pipette tip in the middle of reservoir A1 in slot 3?") + + # Step 8 + ctx.move_labware(labware=custom_labware, new_location="2") + + # Step 9 + pipette_right.move_to(location=custom_labware.wells_by_name()["A1"].top()) + + # Step 10 + ctx.pause("Is the pipette tip in the middle of custom labware A1 in slot 2?") + + # Step 11 + ctx.move_labware(labware=custom_labware, new_location="6") + + # Step 12 + pipette_right.move_to(location=custom_labware.wells_by_name()["A1"].top()) + + # Step 13 + ctx.pause("Is the pipette tip in the middle of custom labware A1 in slot 6?") + + # Step 14 + ctx.move_labware(labware=logo_destination_plate, new_location="2") + + # Step 15 + pipette_right.move_to(location=logo_destination_plate.wells_by_name()["A1"].top()) + + # Step 16 + ctx.pause("Is the pipette tip in the middle of well A1 in slot 2?") + + ###################################### + # Manual Deck State Modification End # + ###################################### + + # Distribute dye + pipette_right.distribute( + volume=18, + source=dye_source, + dest=dye_destination_wells, + new_tip="never", + ) + pipette_right.drop_tip() + + # transfer + transfer_destinations = [ + logo_destination_plate.wells_by_name()["A11"], + logo_destination_plate.wells_by_name()["B11"], + logo_destination_plate.wells_by_name()["C11"], + ] + pipette_right.pick_up_tip() + pipette_right.transfer( + volume=60, + source=dye_container.wells_by_name()["A2"], + dest=transfer_destinations, + new_tip="never", + touch_tip=True, + blow_out=True, + blowout_location="destination well", + mix_before=(3, 20), + mix_after=(1, 20), + mix_touch_tip=True, + ) + + # consolidate + pipette_right.consolidate( + volume=20, + source=transfer_destinations, + dest=dye_container.wells_by_name()["A5"], + new_tip="never", + touch_tip=False, + blow_out=True, + blowout_location="destination well", + mix_before=(3, 20), + ) + + # well to well + pipette_right.return_tip() + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=5, location=logo_destination_plate.wells_by_name()["A11"]) + pipette_right.air_gap(volume=10) + ctx.delay(seconds=3) + pipette_right.dispense(volume=5, location=logo_destination_plate.wells_by_name()["H11"]) + + # move to + pipette_right.move_to(logo_destination_plate.wells_by_name()["E12"].top()) + pipette_right.move_to(logo_destination_plate.wells_by_name()["E11"].bottom()) + pipette_right.blow_out() + # touch tip + # pipette ends in the middle of the well as of 6.3.0 in all touch_tip + pipette_right.touch_tip(location=logo_destination_plate.wells_by_name()["H1"]) + ctx.pause("Is the pipette tip in the middle of the well?") + pipette_right.return_tip() + + # Play with the modules + temperature_module.await_temperature(25) + + hs_module.set_and_wait_for_shake_speed(466) + ctx.delay(seconds=5) + + hs_module.set_and_wait_for_temperature(38) + + thermocycler_module.open_lid() + thermocycler_module.close_lid() + thermocycler_module.set_lid_temperature(38) # 37 is the minimum + thermocycler_module.set_block_temperature(temperature=28, hold_time_seconds=5) + thermocycler_module.deactivate_block() + thermocycler_module.deactivate_lid() + thermocycler_module.open_lid() + + hs_module.deactivate_shaker() + + # dispense to modules + + # to temperature module + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=15, location=dye_source) + pipette_right.dispense(volume=15, location=temp_plate.well(0)) + pipette_right.drop_tip() + + # to heater shaker + pipette_left.pick_up_tip() + pipette_left.aspirate(volume=50, location=dye_source) + pipette_left.dispense(volume=50, location=hs_plate.well(0)) + hs_module.set_and_wait_for_shake_speed(350) + ctx.delay(seconds=5) + hs_module.deactivate_shaker() + + # to custom labware + # This labware does not EXIST!!!! so... + # Use tip rack lid to catch dye on wet run + pipette_right.pick_up_tip() + pipette_right.aspirate(volume=10, location=dye_source, rate=2.0) + pipette_right.dispense(volume=10, location=custom_labware.well(3), rate=1.5) + pipette_right.drop_tip() + + # to thermocycler + pipette_left.aspirate(volume=75, location=dye_source) + pipette_left.dispense(volume=60, location=tc_plate.wells_by_name()["A6"]) + pipette_left.drop_tip() diff --git a/app-testing/files/protocols/py/OT2_P300M_P20S_TC_MM_TM_6_13_Smoke620Release.py b/app-testing/files/protocols/py/OT2_P300M_P20S_TC_MM_TM_2_13_Smoke620Release.py similarity index 100% rename from app-testing/files/protocols/py/OT2_P300M_P20S_TC_MM_TM_6_13_Smoke620Release.py rename to app-testing/files/protocols/py/OT2_P300M_P20S_TC_MM_TM_2_13_Smoke620Release.py diff --git a/app-testing/files/protocols/py/OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x.py b/app-testing/files/protocols/py/OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x.py index 134fa0bf89c..4d35cdf23fe 100644 --- a/app-testing/files/protocols/py/OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x.py +++ b/app-testing/files/protocols/py/OT3_P1000MLeft_P50MRight_HS_TM_MM_TC_2_15_ABR4_Illumina_DNA_Prep_24x.py @@ -5,7 +5,6 @@ "protocolName": "Illumina DNA Prep 24x", "author": "Opentrons ", "source": "Protocol Library", - "apiLevel": "2.15", } requirements = { diff --git a/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel.py b/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel.py index efe8473686f..dfdcd4587be 100644 --- a/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel.py +++ b/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_ABR5_6_HDQ_Bacteria_ParkTips_96_channel.py @@ -26,7 +26,6 @@ metadata = { "protocolName": "Omega HDQ DNA Extraction: Bacteria 96 FOR ABR TESTING", "author": "Zach Galluzzo ", - "apiLevel": "2.15", } requirements = { @@ -87,7 +86,7 @@ def run(ctx): elution_vol = 100 # load 96 channel pipette - pip = ctx.load_instrument("p1000_96", mount="left") + pip = ctx.load_instrument("flex_96channel_1000", mount="left") pip.flow_rate.aspirate = 50 pip.flow_rate.dispense = 150 diff --git a/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch.py b/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch.py index c940ecb4b2d..0be6b796136 100644 --- a/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch.py +++ b/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_MM_2_15_MagMaxRNACells96Ch.py @@ -84,7 +84,7 @@ def run(ctx): tips1 = ctx.load_labware("opentrons_ot3_96_tiprack_200ul_rss", "11").wells()[0] # load 96 channel pipette - pip = ctx.load_instrument("p1000_96", mount="left") + pip = ctx.load_instrument("flex_96channel_1000", mount="left") pip.flow_rate.aspirate = 50 pip.flow_rate.dispense = 150 diff --git a/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III.py b/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III.py index 75739ef2a44..a1f76f22ec0 100644 --- a/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III.py +++ b/app-testing/files/protocols/py/OT3_P1000_96_HS_TM_TC_MM_2_15_ABR5_6_Illumina_DNA_Prep_96x_Head_PART_III.py @@ -5,7 +5,6 @@ "protocolName": "Illumina DNA Prep 96x Head PART III", "author": "Opentrons ", "source": "Protocol Library", - "apiLevel": "2.15", } requirements = { @@ -63,7 +62,7 @@ def run(protocol: protocol_api.ProtocolContext): if USE_8xMULTI == "YES": p1000 = protocol.load_instrument("flex_1channel_1000", "right") else: - p1000 = protocol.load_instrument("p1000_96", "left") + p1000 = protocol.load_instrument("flex_96channel_1000", "left") def grip_offset(action, item, slot=None): """Grip offset.""" diff --git a/app-testing/files/protocols/py/OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR.py b/app-testing/files/protocols/py/OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR.py index 71d8aa6ab48..3c572c62f24 100644 --- a/app-testing/files/protocols/py/OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR.py +++ b/app-testing/files/protocols/py/OT3_P1000_96_None_2_15_ABR5_6_IDT_xGen_EZ_96x_Head_PART_I_III_ABR.py @@ -5,7 +5,6 @@ "protocolName": "IDT xGen EZ 96x Head PART I-III ABR", "author": "Opentrons ", "source": "Protocol Library", - "apiLevel": "2.15", } requirements = { @@ -89,7 +88,7 @@ def run(protocol: protocol_api.ProtocolContext): Liquid_trash = reservoir_4["A1"] # pipette - p1000 = protocol.load_instrument("p1000_96", "left") + p1000 = protocol.load_instrument("flex_96channel_1000", "left") # tip and sample tracking diff --git a/app-testing/files/protocols/py/OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria.py b/app-testing/files/protocols/py/OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria.py index 8d0687075aa..c3df5669932 100644 --- a/app-testing/files/protocols/py/OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria.py +++ b/app-testing/files/protocols/py/OT3_P100_96_HS_TM_2_15_Quick_Zymo_RNA_Bacteria.py @@ -91,7 +91,7 @@ def run(ctx): tips1 = ctx.load_labware("opentrons_ot3_96_tiprack_1000ul_rss", "11").wells()[0] # load instruments - pip = ctx.load_instrument("p1000_96", mount="left") + pip = ctx.load_instrument("flex_96channel_1000", mount="left") pip.flow_rate.aspirate = 50 pip.flow_rate.dispense = 150 diff --git a/app/src/App/hooks.ts b/app/src/App/hooks.ts index 77e52119ca2..bf73bf64b7b 100644 --- a/app/src/App/hooks.ts +++ b/app/src/App/hooks.ts @@ -26,7 +26,7 @@ import { import { checkShellUpdate } from '../redux/shell' import { useToaster } from '../organisms/ToasterOven' -import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/incidental' +import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' import type { Dispatch } from '../redux/types' const CURRENT_RUN_POLL = 5000 diff --git a/app/src/assets/localization/en/app_settings.json b/app/src/assets/localization/en/app_settings.json index 9629ae37879..f717a04bac9 100644 --- a/app/src/assets/localization/en/app_settings.json +++ b/app/src/assets/localization/en/app_settings.json @@ -1,5 +1,4 @@ { - "__dev_internal__enableDeckConfiguration": "Enable Deck Configuration", "__dev_internal__protocolStats": "Protocol Stats", "add_folder_button": "Add labware source folder", "add_ip_button": "Add", diff --git a/app/src/assets/localization/en/device_details.json b/app/src/assets/localization/en/device_details.json index 3e729705757..3398bd2bead 100644 --- a/app/src/assets/localization/en/device_details.json +++ b/app/src/assets/localization/en/device_details.json @@ -33,6 +33,7 @@ "current_temp": "Current: {{temp}} °C", "current_version": "Current Version", "deck_cal_missing": "Pipette Offset calibration missing. Calibrate deck first.", + "deck_configuration_is_not_available": "Deck configuration is not available when run is in progress", "deck_configuration": "deck configuration", "deck_fixture_setup_instructions": "Deck fixture setup instructions", "deck_fixture_setup_modal_bottom_description_desktop": "For detailed instructions for different types of fixtures, scan the QR code or go to the link below.", diff --git a/app/src/assets/localization/en/protocol_command_text.json b/app/src/assets/localization/en/protocol_command_text.json index 6feb4ff5066..8d84b9e341f 100644 --- a/app/src/assets/localization/en/protocol_command_text.json +++ b/app/src/assets/localization/en/protocol_command_text.json @@ -3,9 +3,9 @@ "adapter_in_slot": "{{adapter}} in {{slot}}", "aspirate": "Aspirating {{volume}} µL from well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", "blowout": "Blowing out at well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", - "configure_for_volume": "Configure {{pipette}} to aspirate {{volume}} µL", "closing_tc_lid": "Closing Thermocycler lid", "comment": "Comment", + "configure_for_volume": "Configure {{pipette}} to aspirate {{volume}} µL", "confirm_and_resume": "Confirm and resume", "deactivate_hs_shake": "Deactivating shaker", "deactivate_temperature_module": "Deactivating Temperature Module", @@ -14,8 +14,8 @@ "deactivating_tc_lid": "Deactivating Thermocycler lid", "degrees_c": "{{temp}}°C", "disengaging_magnetic_module": "Disengaging Magnetic Module", - "dispense": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", "dispense_push_out": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec and pushing out {{push_out_volume}} µL", + "dispense": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", "drop_tip": "Dropping tip in {{well_name}} of {{labware}}", "engaging_magnetic_module": "Engaging Magnetic Module", "fixed_trash": "Fixed Trash", @@ -38,6 +38,7 @@ "pause_on": "Pause on {{robot_name}}", "pause": "Pause", "pickup_tip": "Picking up tip from {{well_name}} of {{labware}} in {{labware_location}}", + "prepare_to_aspirate": "Preparing {{pipette}} to aspirate", "return_tip": "Returning tip to {{well_name}} of {{labware}} in {{labware_location}}", "save_position": "Saving position", "set_and_await_hs_shake": "Setting Heater-Shaker to shake at {{rpm}} rpm and waiting until reached", diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json index eea63a7ff82..dfd9cad4b88 100644 --- a/app/src/assets/localization/en/protocol_setup.json +++ b/app/src/assets/localization/en/protocol_setup.json @@ -21,8 +21,8 @@ "calibrate_deck_to_proceed_to_pipette_calibration": "Calibrate your deck in order to proceed to pipette calibration", "calibrate_deck_to_proceed_to_tip_length_calibration": "Calibrate your deck in order to proceed to tip length calibration", "calibrate_gripper_failure_reason": "Calibrate the required gripper to continue", - "calibrate_now": "Calibrate now", "calibrate_module_failure_reason": "Calibrate the required modules(s) to continue", + "calibrate_now": "Calibrate now", "calibrate_pipette_before_module_calibration": "Calibrate pipette before running module calibration", "calibrate_pipette_failure_reason": "Calibrate the required pipette(s) to continue", "calibrate_tiprack_failure_reason": "Calibrate the required tip lengths to continue", diff --git a/app/src/atoms/StepMeter/index.tsx b/app/src/atoms/StepMeter/index.tsx index 999f0661da4..ddaf52565cf 100644 --- a/app/src/atoms/StepMeter/index.tsx +++ b/app/src/atoms/StepMeter/index.tsx @@ -32,15 +32,21 @@ export const StepMeter = (props: StepMeterProps): JSX.Element => { height: ${SPACING.spacing12}; } ` + + const ODD_ANIMATION_OPTIMIZATIONS = ` + backface-visibility: hidden; + perspective: 1000; + will-change: transform; + ` + const StepMeterBar = css` + ${ODD_ANIMATION_OPTIMIZATIONS} position: ${POSITION_ABSOLUTE}; top: 0; height: 100%; background-color: ${COLORS.blueEnabled}; width: ${percentComplete}; - webkit-transition: width 0.5s ease-in-out; - moz-transition: width 0.5s ease-in-out; - o-transition: width 0.5s ease-in-out; + transform: translateX(0); transition: width 0.5s ease-in-out; ` diff --git a/app/src/organisms/CommandText/TemperatureCommandText.tsx b/app/src/organisms/CommandText/TemperatureCommandText.tsx index 5040092166f..2d09926add2 100644 --- a/app/src/organisms/CommandText/TemperatureCommandText.tsx +++ b/app/src/organisms/CommandText/TemperatureCommandText.tsx @@ -5,7 +5,7 @@ import type { TCSetTargetBlockTemperatureCreateCommand, TCSetTargetLidTemperatureCreateCommand, HeaterShakerSetTargetTemperatureCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +} from '@opentrons/shared-data' type TemperatureCreateCommand = | TemperatureModuleSetTargetTemperatureCreateCommand diff --git a/app/src/organisms/CommandText/__tests__/CommandText.test.tsx b/app/src/organisms/CommandText/__tests__/CommandText.test.tsx index 23251329e40..28ef08f940f 100644 --- a/app/src/organisms/CommandText/__tests__/CommandText.test.tsx +++ b/app/src/organisms/CommandText/__tests__/CommandText.test.tsx @@ -1,24 +1,23 @@ import * as React from 'react' import { renderWithProviders } from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + PrepareToAspirateRunTimeCommand, +} from '@opentrons/shared-data' import { i18n } from '../../../i18n' import { CommandText } from '../' import { mockRobotSideAnalysis } from '../__fixtures__' -import type { MoveToWellRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/gantry' import type { BlowoutRunTimeCommand, - DispenseRunTimeCommand, ConfigureForVolumeRunTimeCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' -import type { + DispenseRunTimeCommand, + DropTipRunTimeCommand, + LabwareDefinition2, LoadLabwareRunTimeCommand, LoadLiquidRunTimeCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' -import { - LabwareDefinition2, + MoveToWellRunTimeCommand, RunTimeCommand, - DropTipRunTimeCommand, - FLEX_ROBOT_TYPE, } from '@opentrons/shared-data' describe('CommandText', () => { @@ -149,6 +148,24 @@ describe('CommandText', () => { )[0] getByText('Configure P300 Single-Channel GEN1 to aspirate 1 µL') }) + it('renders correct text for prepareToAspirate', () => { + const command = { + commandType: 'prepareToAspirate', + params: { + pipetteId: 'f6d1c83c-9d1b-4d0d-9de3-e6d649739cfb', + }, + } as PrepareToAspirateRunTimeCommand + + const { getByText } = renderWithProviders( + , + { i18nInstance: i18n } + )[0] + getByText('Preparing P300 Single-Channel GEN1 to aspirate') + }) it('renders correct text for dropTip', () => { const command = mockRobotSideAnalysis.commands.find( c => c.commandType === 'dropTip' diff --git a/app/src/organisms/CommandText/index.tsx b/app/src/organisms/CommandText/index.tsx index b978ab1fb3b..67e76526ab1 100644 --- a/app/src/organisms/CommandText/index.tsx +++ b/app/src/organisms/CommandText/index.tsx @@ -165,6 +165,23 @@ export function CommandText(props: Props): JSX.Element | null { ) } + case 'prepareToAspirate': { + const { pipetteId } = command.params + const pipetteName = robotSideAnalysis.pipettes.find( + pip => pip.id === pipetteId + )?.pipetteName + + return ( + + {t('prepare_to_aspirate', { + pipette: + pipetteName != null + ? getPipetteNameSpecs(pipetteName)?.displayName + : '', + })} + + ) + } case 'touchTip': case 'home': case 'savePosition': diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx index 2eab9282433..162db0ab1fe 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx @@ -5,15 +5,23 @@ import { useUpdateDeckConfigurationMutation, } from '@opentrons/react-api-client' import { i18n } from '../../../i18n' +import { useRunStatuses } from '../../Devices/hooks' import { DeckFixtureSetupInstructionsModal } from '../DeckFixtureSetupInstructionsModal' import { DeviceDetailsDeckConfiguration } from '../' jest.mock('@opentrons/components/src/hardware-sim/DeckConfigurator/index') jest.mock('@opentrons/react-api-client') jest.mock('../DeckFixtureSetupInstructionsModal') +jest.mock('../../Devices/hooks') const ROBOT_NAME = 'otie' const mockUpdateDeckConfiguration = jest.fn() +const RUN_STATUSES = { + isRunRunning: false, + isRunStill: false, + isRunTerminal: false, + isRunIdle: false, +} const mockUseDeckConfigurationQuery = useDeckConfigurationQuery as jest.MockedFunction< typeof useDeckConfigurationQuery @@ -27,6 +35,9 @@ const mockDeckFixtureSetupInstructionsModal = DeckFixtureSetupInstructionsModal const mockDeckConfigurator = DeckConfigurator as jest.MockedFunction< typeof DeckConfigurator > +const mockUseRunStatuses = useRunStatuses as jest.MockedFunction< + typeof useRunStatuses +> const render = ( props: React.ComponentProps @@ -51,6 +62,7 @@ describe('DeviceDetailsDeckConfiguration', () => {
mock DeckFixtureSetupInstructionsModal
) mockDeckConfigurator.mockReturnValue(
mock DeckConfigurator
) + mockUseRunStatuses.mockReturnValue(RUN_STATUSES) }) it('should render text and button', () => { @@ -67,4 +79,13 @@ describe('DeviceDetailsDeckConfiguration', () => { getByRole('button', { name: 'Setup Instructions' }).click() getByText('mock DeckFixtureSetupInstructionsModal') }) + + it('should render banner and make deck configurator disabled when running', () => { + RUN_STATUSES.isRunRunning = true + mockUseRunStatuses.mockReturnValue(RUN_STATUSES) + const [{ getByText, queryAllByRole }] = render(props) + getByText('Deck configuration is not available when run is in progress') + // Note (kk:10/27/2023) detects Setup Instructions buttons + expect(queryAllByRole('button').length).toBe(1) + }) }) diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx index fe48d301ecd..4bc2c582557 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx @@ -26,8 +26,10 @@ import { } from '@opentrons/shared-data' import { StyledText } from '../../atoms/text' +import { Banner } from '../../atoms/Banner' import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal' import { AddFixtureModal } from './AddFixtureModal' +import { useRunStatuses } from '../Devices/hooks' import type { Cutout } from '@opentrons/shared-data' @@ -53,6 +55,7 @@ export function DeviceDetailsDeckConfiguration({ const deckConfig = useDeckConfigurationQuery().data ?? [] const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation() + const { isRunRunning } = useRunStatuses() const handleClickAdd = (fixtureLocation: Cutout): void => { setTargetFixtureLocation(fixtureLocation) @@ -117,55 +120,65 @@ export function DeviceDetailsDeckConfiguration({ {t('setup_instructions')} - - - - - + {isRunRunning ? ( + + {t('deck_configuration_is_not_available')} + + ) : null} + + + + - {t('location')} - {t('fixture')} + + {t('location')} + {t('fixture')} + + {fixtureDisplayList.map(fixture => { + return ( + + {fixture.fixtureLocation} + + {getFixtureDisplayName(fixture.loadName)} + + + ) + })} - {fixtureDisplayList.map(fixture => { - return ( - - {fixture.fixtureLocation} - - {getFixtureDisplayName(fixture.loadName)} - - - ) - })} diff --git a/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx b/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx index 6f834f70aa6..76dc29468ce 100644 --- a/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx +++ b/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx @@ -20,7 +20,7 @@ import { HeaterShakerModule } from '../../../redux/modules/types' import { HeaterShakerModuleCard } from '../HeaterShakerWizard/HeaterShakerModuleCard' import { HEATERSHAKER_MODULE_TYPE } from '@opentrons/shared-data' -import type { HeaterShakerDeactivateShakerCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +import type { HeaterShakerDeactivateShakerCreateCommand } from '@opentrons/shared-data' interface HeaterShakerIsRunningModalProps { closeModal: () => void diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx index 039e02242ec..ab5fdaffd3c 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx @@ -25,7 +25,6 @@ import { import { Line } from '../../../atoms/structure' import { StyledText } from '../../../atoms/text' -import { useFeatureFlag } from '../../../redux/config' import { InfoMessage } from '../../../molecules/InfoMessage' import { useIsFlex, @@ -76,7 +75,7 @@ export function ProtocolRunSetup({ const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) const protocolData = robotProtocolAnalysis ?? storedProtocolAnalysis const modules = parseAllRequiredModuleModels(protocolData?.commands ?? []) - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') + // TODO(Jr, 10/4/23): stubbed in the fixtures for now - delete IMMEDIATELY // const loadedFixturesBySlot = parseInitialLoadedFixturesByCutout( // protocolData?.commands ?? [] @@ -175,11 +174,11 @@ export function ProtocolRunSetup({ let moduleDescription: string = t(`${MODULE_SETUP_KEY}_description`, { count: modules.length, }) - if (!hasModules && !enableDeckConfig) { + if (!hasModules && !isFlex) { moduleDescription = i18n.format(t('no_modules_specified'), 'capitalize') - } else if (isFlex && enableDeckConfig && (hasModules || hasFixtures)) { + } else if (isFlex && (hasModules || hasFixtures)) { moduleDescription = t('install_modules_and_fixtures') - } else if (isFlex && enableDeckConfig && !hasModules && !hasFixtures) { + } else if (isFlex && !hasModules && !hasFixtures) { moduleDescription = t('no_modules_or_fixtures') } @@ -278,15 +277,15 @@ export function ProtocolRunSetup({ ) : ( stepsKeysInOrder.map((stepKey, index) => { const setupStepTitle = t( - isFlex && stepKey === MODULE_SETUP_KEY && enableDeckConfig + isFlex && stepKey === MODULE_SETUP_KEY ? `module_and_deck_setup` : `${stepKey}_title` ) const showEmptySetupStep = (stepKey === 'liquid_setup_step' && !hasLiquids) || (stepKey === 'module_setup_step' && - ((!enableDeckConfig && !hasModules) || - (enableDeckConfig && !hasModules && !hasFixtures))) + ((!isFlex && !hasModules) || + (isFlex && !hasModules && !hasFixtures))) return ( {showEmptySetupStep ? ( diff --git a/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx b/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx index e01abe60dc6..86bf857246f 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx @@ -20,7 +20,7 @@ import { PipetteWizardFlows } from '../../PipetteWizardFlows' import { FLOWS } from '../../PipetteWizardFlows/constants' import { SetupCalibrationItem } from './SetupCalibrationItem' import type { PipetteData } from '@opentrons/api-client' -import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data' import type { Mount } from '../../../redux/pipettes/types' interface SetupInstrumentCalibrationItemProps { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx index 53d6748be0f..d91a0673d32 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx @@ -26,16 +26,11 @@ import { getModuleDisplayName, getModuleType, HEATERSHAKER_MODULE_TYPE, - LabwareDefinition2, - LoadModuleRunTimeCommand, MAGNETIC_MODULE_TYPE, - ModuleType, TC_MODULE_LOCATION_OT2, TC_MODULE_LOCATION_OT3, THERMOCYCLER_MODULE_TYPE, THERMOCYCLER_MODULE_V2, - RunTimeCommand, - LoadLabwareRunTimeCommand, } from '@opentrons/shared-data' import { ToggleButton } from '../../../../atoms/buttons' @@ -45,7 +40,12 @@ import { SecureLabwareModal } from './SecureLabwareModal' import type { HeaterShakerCloseLatchCreateCommand, HeaterShakerOpenLatchCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' + RunTimeCommand, + ModuleType, + LabwareDefinition2, + LoadModuleRunTimeCommand, + LoadLabwareRunTimeCommand, +} from '@opentrons/shared-data' import type { ModuleRenderInfoForProtocol } from '../../hooks' import type { LabwareSetupItem } from '../../../../pages/Protocols/utils' import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx index 0ed1116599c..0225ff8e575 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx @@ -8,7 +8,6 @@ import { Box, DIRECTION_COLUMN, SPACING, - EXTENDED_DECK_CONFIG_FIXTURE, } from '@opentrons/components' import { FLEX_ROBOT_TYPE, @@ -18,7 +17,6 @@ import { } from '@opentrons/shared-data' import { getLabwareSetupItemGroups } from '../../../../pages/Protocols/utils' -import { useFeatureFlag } from '../../../../redux/config' import { getDeckConfigFromProtocolCommands } from '../../../../resources/deck_configuration/utils' import { getAttachedProtocolModuleMatches } from '../../../ProtocolSetupModulesAndDeck/utils' import { useAttachedModules } from '../../hooks' @@ -49,8 +47,6 @@ export function SetupLabwareMap({ refetchInterval: ATTACHED_MODULE_POLL_MS, }) ?? [] - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') - // early return null if no protocol analysis if (protocolAnalysis == null) return null @@ -111,10 +107,9 @@ export function SetupLabwareMap({ const { offDeckItems } = getLabwareSetupItemGroups(commands) - // hide the extended deck behind feature flag to avoid confustion - const deckConfig = enableDeckConfig - ? EXTENDED_DECK_CONFIG_FIXTURE - : getDeckConfigFromProtocolCommands(protocolAnalysis.commands) + const deckConfig = getDeckConfigFromProtocolCommands( + protocolAnalysis.commands + ) const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx index 15b399c8161..32abc2f92eb 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx @@ -7,13 +7,13 @@ import { mockDefinition } from '../../../../../redux/custom-labware/__fixtures__ import { SetupLabwareList } from '../SetupLabwareList' import { LabwareListItem } from '../LabwareListItem' import type { - ProtocolAnalysisFile, + CompletedProtocolAnalysis, RunTimeCommand, } from '@opentrons/shared-data' jest.mock('../LabwareListItem') -const protocolWithTC = (_uncastedProtocolWithTC as unknown) as ProtocolAnalysisFile +const protocolWithTC = (_uncastedProtocolWithTC as unknown) as CompletedProtocolAnalysis const mockLabwareListItem = LabwareListItem as jest.MockedFunction< typeof LabwareListItem diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx index a8174c13cad..0e672f4852c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx @@ -8,7 +8,7 @@ import { LabwarePositionCheck } from '../../../../LabwarePositionCheck' import { useLPCDisabledReason } from '../../../hooks' import { CurrentOffsetsTable } from '../CurrentOffsetsTable' import { getLatestCurrentOffsets } from '../utils' -import type { ProtocolAnalysisFile } from '@opentrons/shared-data' +import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { LabwareOffset } from '@opentrons/api-client' jest.mock('../../../hooks') @@ -43,7 +43,7 @@ const render = (props: React.ComponentProps) => { i18nInstance: i18n, })[0] } -const protocolWithTC = (_uncastedProtocolWithTC as unknown) as ProtocolAnalysisFile +const protocolWithTC = (_uncastedProtocolWithTC as unknown) as CompletedProtocolAnalysis const mockCurrentOffsets: LabwareOffset[] = [ { createdAt: '2022-12-20T14:06:23.562082+00:00', diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx index 113051f74df..0f52f417742 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesList.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import map from 'lodash/map' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' + import { BORDERS, Box, @@ -26,17 +27,17 @@ import { TC_MODULE_LOCATION_OT2, TC_MODULE_LOCATION_OT3, } from '@opentrons/shared-data' + import { Banner } from '../../../../atoms/Banner' -import { StyledText } from '../../../../atoms/text' -import { useChainLiveCommands } from '../../../../resources/runs/hooks' -import { StatusLabel } from '../../../../atoms/StatusLabel' import { TertiaryButton } from '../../../../atoms/buttons' +import { StatusLabel } from '../../../../atoms/StatusLabel' +import { StyledText } from '../../../../atoms/text' import { Tooltip } from '../../../../atoms/Tooltip' -import { useFeatureFlag } from '../../../../redux/config' +import { useChainLiveCommands } from '../../../../resources/runs/hooks' +import { ModuleSetupModal } from '../../../ModuleCard/ModuleSetupModal' +import { ModuleWizardFlows } from '../../../ModuleWizardFlows' import { getModulePrepCommands } from '../../getModulePrepCommands' import { getModuleTooHot } from '../../getModuleTooHot' -import { UnMatchedModuleWarning } from './UnMatchedModuleWarning' -import { MultipleModulesModal } from './MultipleModulesModal' import { ModuleRenderInfoForProtocol, useIsFlex, @@ -44,9 +45,9 @@ import { useUnmatchedModulesForProtocol, useRunCalibrationStatus, } from '../../hooks' -import { ModuleSetupModal } from '../../../ModuleCard/ModuleSetupModal' -import { ModuleWizardFlows } from '../../../ModuleWizardFlows' import { LocationConflictModal } from './LocationConflictModal' +import { MultipleModulesModal } from './MultipleModulesModal' +import { UnMatchedModuleWarning } from './UnMatchedModuleWarning' import { getModuleImage } from './utils' import type { Cutout, ModuleModel, Fixture } from '@opentrons/shared-data' @@ -230,7 +231,6 @@ export function ModulesListItem({ showLocationConflictModal, setShowLocationConflictModal, ] = React.useState(false) - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') const [showModuleWizard, setShowModuleWizard] = React.useState(false) const { chainLiveCommands, isCommandMutationLoading } = useChainLiveCommands() @@ -412,7 +412,7 @@ export function ModulesListItem({ flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing10} > - {conflictedFixture != null && enableDeckConfig ? ( + {conflictedFixture != null && isFlex ? ( const mockUseRunHasStarted = useRunHasStarted as jest.MockedFunction< typeof useRunHasStarted > @@ -57,9 +58,6 @@ const mockSetupFixtureList = SetupFixtureList as jest.MockedFunction< const mockSetupModulesMap = SetupModulesMap as jest.MockedFunction< typeof SetupModulesMap > -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const MOCK_ROBOT_NAME = 'otie' const MOCK_RUN_ID = '1' @@ -89,12 +87,10 @@ describe('SetupModuleAndDeck', () => { missingModuleIds: [], remainingAttachedModules: [], }) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) when(mockUseModuleCalibrationStatus) .calledWith(MOCK_ROBOT_NAME, MOCK_RUN_ID) .mockReturnValue({ complete: true }) + when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(false) }) it('renders the list and map view buttons', () => { @@ -143,10 +139,8 @@ describe('SetupModuleAndDeck', () => { getByText('Mock setup modules list') }) - it('should render the SetupModulesList and SetupFixtureList component when clicking List View and ff is on', () => { - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) + it('should render the SetupModulesList and SetupFixtureList component when clicking List View for Flex', () => { + when(mockUseIsFlex).calledWith(MOCK_ROBOT_NAME).mockReturnValue(true) props.loadedFixturesBySlot = mockLoadedFixturesBySlot const { getByRole, getByText } = render(props) const button = getByRole('button', { name: 'List View' }) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx index 2b5bd7ebfa7..4e2fc01c73c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx @@ -12,7 +12,6 @@ import { mockMagneticModuleGen2, mockThermocycler, } from '../../../../../redux/modules/__fixtures__' -import { useFeatureFlag } from '../../../../../redux/config' import { useChainLiveCommands } from '../../../../../resources/runs/hooks' import { ModuleSetupModal } from '../../../../ModuleCard/ModuleSetupModal' import { ModuleWizardFlows } from '../../../../ModuleWizardFlows' @@ -75,9 +74,7 @@ const mockUseChainLiveCommands = useChainLiveCommands as jest.MockedFunction< const mockLocationConflictModal = LocationConflictModal as jest.MockedFunction< typeof LocationConflictModal > -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> + const ROBOT_NAME = 'otie' const RUN_ID = '1' const MOCK_MAGNETIC_MODULE_COORDS = [10, 20, 0] @@ -146,9 +143,6 @@ describe('SetupModulesList', () => { .mockReturnValue({ complete: true, }) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) mockModuleWizardFlows.mockReturnValue(
mock ModuleWizardFlows
) mockUseChainLiveCommands.mockReturnValue({ chainLiveCommands: mockChainLiveCommands, @@ -453,9 +447,7 @@ describe('SetupModulesList', () => { getByText('mockModuleSetupModal') }) it('shoulde render a magnetic block with a conflicted fixture', () => { - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) + when(mockUseIsFlex).calledWith(ROBOT_NAME).mockReturnValue(true) mockUseModuleRenderInfoForProtocolById.mockReturnValue({ [mockMagneticBlock.id]: { moduleId: mockMagneticBlock.id, diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx index 65d028c6ab4..3ef1a8c7603 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx @@ -10,8 +10,8 @@ import { } from '@opentrons/components' import { useToggleGroup } from '../../../../molecules/ToggleGroup/useToggleGroup' import { Tooltip } from '../../../../atoms/Tooltip' -import { useFeatureFlag } from '../../../../redux/config' import { + useIsFlex, useRunHasStarted, useUnmatchedModulesForProtocol, useModuleCalibrationStatus, @@ -41,8 +41,8 @@ export const SetupModuleAndDeck = ({ t('list_view'), t('map_view') ) - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') + const isFlex = useIsFlex(robotName) const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) const runHasStarted = useRunHasStarted(runId) const [targetProps, tooltipProps] = useHoverTooltip() @@ -58,8 +58,7 @@ export const SetupModuleAndDeck = ({ {hasModules ? ( ) : null} - {Object.keys(loadedFixturesBySlot).length > 0 && - enableDeckConfig ? ( + {Object.keys(loadedFixturesBySlot).length > 0 && isFlex ? ( ) : null} diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx index 2e73ab8b1d7..63aafc70d1d 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx @@ -6,15 +6,11 @@ import { partialComponentPropsMatcher, renderWithProviders, } from '@opentrons/components' -import { - ProtocolAnalysisOutput, - protocolHasLiquids, -} from '@opentrons/shared-data' +import { ProtocolAnalysisOutput } from '@opentrons/shared-data' import noModulesProtocol from '@opentrons/shared-data/protocol/fixtures/4/simpleV4.json' import withModulesProtocol from '@opentrons/shared-data/protocol/fixtures/4/testModulesProtocol.json' import { i18n } from '../../../../i18n' -import { useFeatureFlag } from '../../../../redux/config' import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__' import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { @@ -79,15 +75,9 @@ const mockSetupModuleAndDeck = SetupModuleAndDeck as jest.MockedFunction< const mockSetupLiquids = SetupLiquids as jest.MockedFunction< typeof SetupLiquids > -const mockProtocolHasLiquids = protocolHasLiquids as jest.MockedFunction< - typeof protocolHasLiquids -> const mockEmptySetupStep = EmptySetupStep as jest.MockedFunction< typeof EmptySetupStep > -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const ROBOT_NAME = 'otie' const RUN_ID = '1' const MOCK_ROTOCOL_LIQUID_KEY = { liquids: [] } @@ -150,9 +140,6 @@ describe('ProtocolRunSetup', () => { when(mockSetupModuleAndDeck).mockReturnValue(
Mock SetupModules
) when(mockSetupLiquids).mockReturnValue(
Mock SetupLiquids
) when(mockEmptySetupStep).mockReturnValue(
Mock EmptySetupStep
) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) }) afterEach(() => { resetAllWhenMocks() @@ -246,24 +233,6 @@ describe('ProtocolRunSetup', () => { }) }) - describe('when liquids are in the protocol', () => { - it('renders correct text for liquids', () => { - when(mockUseMostRecentCompletedAnalysis) - .calledWith(RUN_ID) - .mockReturnValue({ - ...noModulesProtocol, - liquids: [{ displayName: 'water', description: 'liquid H2O' }], - } as any) - mockProtocolHasLiquids.mockReturnValue(true) - - const { getByText } = render() - getByText('STEP 5') - getByText('Liquids') - getByText('View liquid starting locations and volumes') - getByText('Mock SetupLiquids') - }) - }) - describe('when modules are in the protocol', () => { beforeEach(() => { when(mockParseAllRequiredModuleModels).mockReturnValue([ @@ -303,7 +272,7 @@ describe('ProtocolRunSetup', () => { const { getByText } = render() getByText('STEP 2') - getByText('Modules') + getByText('Modules & deck') getByText('Calibration needed') }) @@ -378,9 +347,6 @@ describe('ProtocolRunSetup', () => { it('renders correct text contents for modules and fixtures', () => { when(mockUseIsFlex).calledWith(ROBOT_NAME).mockReturnValue(true) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) when(mockUseMostRecentCompletedAnalysis) .calledWith(RUN_ID) .mockReturnValue({ diff --git a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareOffsetLocation.test.tsx b/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareOffsetLocation.test.tsx index ded213efeb0..6c8b4157b80 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareOffsetLocation.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareOffsetLocation.test.tsx @@ -1,5 +1,8 @@ import { when, resetAllWhenMocks } from 'jest-when' -import { getLabwareDefURI } from '@opentrons/shared-data' +import { + CompletedProtocolAnalysis, + getLabwareDefURI, +} from '@opentrons/shared-data' import _uncastedProtocolWithTC from '@opentrons/shared-data/protocol/fixtures/6/multipleTipracksWithTC.json' import fixture_adapter from '@opentrons/shared-data/labware/definitions/2/opentrons_96_pcr_adapter/1.json' import { getLabwareOffsetLocation } from '../getLabwareOffsetLocation' @@ -8,14 +11,13 @@ import { getModuleInitialLoadInfo } from '../getModuleInitialLoadInfo' import type { LoadedLabware, LoadedModule, - ProtocolAnalysisFile, LabwareDefinition2, } from '@opentrons/shared-data' jest.mock('../getLabwareLocation') jest.mock('../getModuleInitialLoadInfo') -const protocolWithTC = (_uncastedProtocolWithTC as unknown) as ProtocolAnalysisFile +const protocolWithTC = (_uncastedProtocolWithTC as unknown) as CompletedProtocolAnalysis const mockAdapterDef = fixture_adapter as LabwareDefinition2 const mockAdapterId = 'mockAdapterId' const TCModelInProtocol = 'thermocyclerModuleV1' @@ -32,7 +34,7 @@ const mockGetModuleInitialLoadInfo = getModuleInitialLoadInfo as jest.MockedFunc describe('getLabwareOffsetLocation', () => { let MOCK_LABWARE_ID: string - let MOCK_COMMANDS: ProtocolAnalysisFile['commands'] + let MOCK_COMMANDS: CompletedProtocolAnalysis['commands'] let MOCK_MODULES: LoadedModule[] let MOCK_LABWARE: LoadedLabware[] beforeEach(() => { diff --git a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareRenderInfo.test.ts b/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareRenderInfo.test.ts index f34f99fba80..f865b679b0c 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareRenderInfo.test.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getLabwareRenderInfo.test.ts @@ -1,10 +1,12 @@ import _protocolWithMagTempTC from '@opentrons/shared-data/protocol/fixtures/6/transferSettings.json' import _standardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot2_standard.json' import { getLabwareRenderInfo } from '../getLabwareRenderInfo' -import type { ProtocolAnalysisFile } from '@opentrons/shared-data' -import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { + CompletedProtocolAnalysis, + LoadLabwareRunTimeCommand, +} from '@opentrons/shared-data' -const protocolWithMagTempTC = (_protocolWithMagTempTC as unknown) as ProtocolAnalysisFile +const protocolWithMagTempTC = (_protocolWithMagTempTC as unknown) as CompletedProtocolAnalysis const standardDeckDef = _standardDeckDef as any describe('getLabwareRenderInfo', () => { diff --git a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getModuleInitialLoadInfo.test.ts b/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getModuleInitialLoadInfo.test.ts index 231780a3999..811d7a18e69 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getModuleInitialLoadInfo.test.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getModuleInitialLoadInfo.test.ts @@ -1,9 +1,9 @@ import _protocolWithMagTempTC from '@opentrons/shared-data/protocol/fixtures/6/transferSettings.json' import { getModuleInitialLoadInfo } from '../getModuleInitialLoadInfo' -import { ProtocolAnalysisFile } from '@opentrons/shared-data' -import type { LoadModuleRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import { CompletedProtocolAnalysis } from '@opentrons/shared-data' +import type { LoadModuleRunTimeCommand } from '@opentrons/shared-data' -const protocolWithMagTempTC = (_protocolWithMagTempTC as unknown) as ProtocolAnalysisFile +const protocolWithMagTempTC = (_protocolWithMagTempTC as unknown) as CompletedProtocolAnalysis describe('getModuleInitialLoadInfo', () => { it('should gather protocol module info for tc if id in params', () => { diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts b/app/src/organisms/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts index 7ee6cce04bc..ba0e5a694ea 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts @@ -1,9 +1,9 @@ import { FIXED_TRASH_ID } from '@opentrons/shared-data/js' -import type { RunTimeCommand } from '@opentrons/shared-data' import type { LoadLabwareRunTimeCommand, LabwareLocation, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' + RunTimeCommand, +} from '@opentrons/shared-data' export const getInitialLabwareLocation = ( labwareId: string, diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getLabwareLocation.ts b/app/src/organisms/Devices/ProtocolRun/utils/getLabwareLocation.ts index cbe1ff380b5..09def7cf201 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getLabwareLocation.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getLabwareLocation.ts @@ -1,8 +1,8 @@ -import type { RunTimeCommand } from '@opentrons/shared-data' import type { LoadLabwareRunTimeCommand, LabwareLocation, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' + RunTimeCommand, +} from '@opentrons/shared-data' const TRASH_ID = 'fixedTrash' diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo.ts b/app/src/organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo.ts index 28bfc0570c3..86ae0ec6146 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo.ts @@ -1,12 +1,11 @@ -import { +import { getSlotHasMatingSurfaceUnitVector } from '@opentrons/shared-data' +import type { CompletedProtocolAnalysis, DeckDefinition, - getSlotHasMatingSurfaceUnitVector, LabwareDefinition2, - ProtocolAnalysisFile, + LoadLabwareRunTimeCommand, ProtocolAnalysisOutput, } from '@opentrons/shared-data' -import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' const getSlotPosition = ( deckDef: DeckDefinition, @@ -46,10 +45,7 @@ export interface LabwareRenderInfoById { } export const getLabwareRenderInfo = ( - protocolData: - | ProtocolAnalysisFile<{}> - | CompletedProtocolAnalysis - | ProtocolAnalysisOutput, + protocolData: CompletedProtocolAnalysis | ProtocolAnalysisOutput, deckDef: DeckDefinition ): LabwareRenderInfoById => protocolData.commands diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getModuleInitialLoadInfo.ts b/app/src/organisms/Devices/ProtocolRun/utils/getModuleInitialLoadInfo.ts index 066c0e5a872..d92b35fc88e 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getModuleInitialLoadInfo.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getModuleInitialLoadInfo.ts @@ -1,8 +1,8 @@ -import type { RunTimeCommand } from '@opentrons/shared-data' import type { LoadModuleRunTimeCommand, ModuleLocation, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' + RunTimeCommand, +} from '@opentrons/shared-data' interface ModuleInitialLoadInfo { location: ModuleLocation diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getPickUpTipCommandsWithPipette.ts b/app/src/organisms/Devices/ProtocolRun/utils/getPickUpTipCommandsWithPipette.ts index 781f70180d4..4c739830852 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getPickUpTipCommandsWithPipette.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getPickUpTipCommandsWithPipette.ts @@ -1,5 +1,7 @@ -import type { PickUpTipRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' -import type { RunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7' +import type { + PickUpTipRunTimeCommand, + RunTimeCommand, +} from '@opentrons/shared-data' export const getPickUpTipCommandsWithPipette = ( commands: RunTimeCommand[], diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getPipetteMount.ts b/app/src/organisms/Devices/ProtocolRun/utils/getPipetteMount.ts index 20aa5189bb5..77eb93c0ade 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getPipetteMount.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getPipetteMount.ts @@ -1,5 +1,7 @@ -import type { RunTimeCommand } from '@opentrons/shared-data' -import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { + LoadPipetteRunTimeCommand, + RunTimeCommand, +} from '@opentrons/shared-data' export const getPipetteMount = ( pipetteId: string, diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo.ts b/app/src/organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo.ts index b7451a356cf..0b0f3bc23b0 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo.ts @@ -4,13 +4,13 @@ import { getLoadedLabwareDefinitionsByUri, } from '@opentrons/shared-data' import { getModuleInitialLoadInfo } from './getModuleInitialLoadInfo' -import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' import type { + CompletedProtocolAnalysis, DeckDefinition, LabwareDefinition2, + LoadLabwareRunTimeCommand, ModuleDefinition, ProtocolAnalysisOutput, - CompletedProtocolAnalysis, } from '@opentrons/shared-data' export interface ProtocolModuleInfo { diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getTipracksVisited.ts b/app/src/organisms/Devices/ProtocolRun/utils/getTipracksVisited.ts index dec809dc89a..5f0882e6958 100644 --- a/app/src/organisms/Devices/ProtocolRun/utils/getTipracksVisited.ts +++ b/app/src/organisms/Devices/ProtocolRun/utils/getTipracksVisited.ts @@ -1,4 +1,4 @@ -import type { PickUpTipRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' +import type { PickUpTipRunTimeCommand } from '@opentrons/shared-data' export const getTipracksVisited = ( pickupTipCommands: PickUpTipRunTimeCommand[] diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx b/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx index c815dcf3ab3..136af1d856f 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx +++ b/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx @@ -31,8 +31,8 @@ import { useDispatchStartRobotUpdate } from '../../../../redux/robot-update/hook import { useRobotUpdateInfo } from './useRobotUpdateInfo' import successIcon from '../../../../assets/images/icon_success.png' +import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' import type { State } from '../../../../redux/types' -import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/incidental' import type { RobotUpdateSession } from '../../../../redux/robot-update/types' import type { UpdateStep } from './useRobotUpdateInfo' diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx b/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx index c161160fdb1..6f5c99566d3 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx +++ b/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx @@ -11,11 +11,10 @@ import { useRobotUpdateInfo } from '../useRobotUpdateInfo' import { getRobotSessionIsManualFile } from '../../../../../redux/robot-update' import { useDispatchStartRobotUpdate } from '../../../../../redux/robot-update/hooks' -import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/incidental' +import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' import type { RobotUpdateSession } from '../../../../../redux/robot-update/types' jest.mock('@opentrons/react-api-client') -jest.mock('@opentrons/shared-data/protocol/types/schemaV7/command/incidental') jest.mock('../useRobotUpdateInfo') jest.mock('../../../../../redux/robot-update') jest.mock('../../../../../redux/robot-update/hooks') diff --git a/app/src/organisms/Devices/hooks/useRunPipetteInfoByMount.ts b/app/src/organisms/Devices/hooks/useRunPipetteInfoByMount.ts index f465848e913..9445bb3ab8b 100644 --- a/app/src/organisms/Devices/hooks/useRunPipetteInfoByMount.ts +++ b/app/src/organisms/Devices/hooks/useRunPipetteInfoByMount.ts @@ -12,17 +12,17 @@ import { useStoredProtocolAnalysis, } from '.' import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' -import type { PickUpTipRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' +import type { + PickUpTipRunTimeCommand, + LoadPipetteRunTimeCommand, + LabwareDefinition2, + PipetteNameSpecs, +} from '@opentrons/shared-data' import type { Mount, AttachedPipette, TipRackCalibrationData, } from '../../../redux/pipettes/types' -import type { - LabwareDefinition2, - PipetteNameSpecs, -} from '@opentrons/shared-data' const EMPTY_MOUNTS = { left: null, right: null } diff --git a/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts b/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts index 43129e25758..f2ce105fd73 100644 --- a/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts +++ b/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts @@ -5,17 +5,15 @@ import { } from '@opentrons/shared-data' import type { + CompletedProtocolAnalysis, CreateCommand, - RunTimeCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7' -import type { SetupRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' -import type { HeaterShakerCloseLatchCreateCommand, HeaterShakerDeactivateShakerCreateCommand, + HomeCreateCommand, + RunTimeCommand, + SetupRunTimeCommand, TCOpenLidCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' -import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' -import type { HomeCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/gantry' +} from '@opentrons/shared-data' type LPCPrepCommand = | HomeCreateCommand diff --git a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx index bd78c05cab1..4a96d8bc2e3 100644 --- a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx +++ b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx @@ -33,8 +33,11 @@ import { FatalErrorModal } from './FatalErrorModal' import { RobotMotionLoader } from './RobotMotionLoader' import { getLabwarePositionCheckSteps } from './getLabwarePositionCheckSteps' import type { LabwareOffset, CommandData } from '@opentrons/api-client' -import type { DropTipCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' -import type { CreateCommand, RobotType } from '@opentrons/shared-data' +import type { + CreateCommand, + DropTipCreateCommand, + RobotType, +} from '@opentrons/shared-data' import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' import type { RegisterPositionAction, WorkingOffset } from './types' diff --git a/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx b/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx index 543e7ef0103..6fdf7b5b79b 100644 --- a/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx +++ b/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx @@ -5,10 +5,7 @@ import { useSelector } from 'react-redux' import { DIRECTION_COLUMN, JUSTIFY_SPACE_BETWEEN, - LabwareRender, - Module, RESPONSIVENESS, - RobotWorkSpace, SPACING, Flex, DIRECTION_ROW, @@ -16,16 +13,15 @@ import { TYPOGRAPHY, JUSTIFY_FLEX_END, PrimaryButton, + BaseDeck, + ALIGN_FLEX_START, } from '@opentrons/components' import { - inferModuleOrientationFromXCoordinate, CompletedProtocolAnalysis, - getModuleDef2, LabwareDefinition2, THERMOCYCLER_MODULE_TYPE, getModuleType, RobotType, - getDeckDefFromRobotType, } from '@opentrons/shared-data' import { getIsOnDevice } from '../../redux/config' @@ -33,26 +29,17 @@ import { SmallButton } from '../../atoms/buttons' import { NeedHelpLink } from '../CalibrationPanels' import type { CheckLabwareStep } from './types' +import { useDeckConfigurationQuery } from '@opentrons/react-api-client' const LPC_HELP_LINK_URL = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' -const DECK_MAP_VIEWBOX = '-80 -20 550 466' -const DECK_LAYER_BLOCKLIST = [ - 'calibrationMarkings', - 'fixedBase', - 'doorStops', - 'metalFrame', - 'removalHandle', - 'removableDeckOutline', - 'screwHoles', -] - const TILE_CONTAINER_STYLE = css` flex-direction: ${DIRECTION_COLUMN}; justify-content: ${JUSTIFY_SPACE_BETWEEN}; padding: ${SPACING.spacing32}; height: 24.625rem; + flex: 1; @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { height: 29.5rem; } @@ -81,101 +68,53 @@ interface PrepareSpaceProps extends Omit { } export const PrepareSpace = (props: PrepareSpaceProps): JSX.Element | null => { const { i18n, t } = useTranslation(['labware_position_check', 'shared']) - const { - location, - moduleId, - labwareDef, - protocolData, - header, - body, - robotType, - } = props + const { location, labwareDef, protocolData, header, body, robotType } = props const isOnDevice = useSelector(getIsOnDevice) + const deckConfig = useDeckConfigurationQuery().data ?? [] if (protocolData == null || robotType == null) return null - const deckDef = getDeckDefFromRobotType(robotType) return ( - + {header} {body} - - - {({ deckSlotsById }) => { - const deckSlot = deckSlotsById[location.slotName] - const [x, y] = deckSlot.position - let labwareToPrepare = null - if ('moduleModel' in location && location.moduleModel != null) { - labwareToPrepare = ( - - - - ) - } else { - labwareToPrepare = ( - - - - ) - } - return ( - <> - {protocolData.modules.map(module => { - const [modX, modY] = deckSlotsById[ - module.location.slotName - ].position - - // skip the focused module as it will be rendered above with the labware - return module.id === moduleId ? null : ( - - ) - })} - {labwareToPrepare} - - ) - }} - + + ({ + moduleModel: mod.model, + moduleLocation: mod.location, + nestedLabwareDef: + 'moduleModel' in location && location.moduleModel != null + ? labwareDef + : null, + innerProps: + 'moduleModel' in location && + location.moduleModel != null && + getModuleType(location.moduleModel) === THERMOCYCLER_MODULE_TYPE + ? { lidMotorState: 'open' } + : {}, + }))} + labwareLocations={[ + { + labwareLocation: location, + definition: labwareDef, + }, + ]} + deckConfig={deckConfig} + /> {isOnDevice ? ( diff --git a/app/src/organisms/LabwarePositionCheck/utils/__tests__/doesPipetteVisitAllTipracks.test.ts b/app/src/organisms/LabwarePositionCheck/utils/__tests__/doesPipetteVisitAllTipracks.test.ts index 6f880784b78..e5074491ceb 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/__tests__/doesPipetteVisitAllTipracks.test.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/__tests__/doesPipetteVisitAllTipracks.test.ts @@ -2,10 +2,10 @@ import { doesPipetteVisitAllTipracks } from '../doesPipetteVisitAllTipracks' import _uncastedProtocolMultipleTipracks from '@opentrons/shared-data/protocol/fixtures/6/multipleTipracks.json' import _uncastedProtocolOneTiprack from '@opentrons/shared-data/protocol/fixtures/6/oneTiprack.json' import type { - ProtocolAnalysisOutput, LoadedLabware, + ProtocolAnalysisOutput, + RunTimeCommand, } from '@opentrons/shared-data' -import type { RunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7' // TODO: update these fixtures to be v6 protocols const protocolMultipleTipracks = (_uncastedProtocolMultipleTipracks as unknown) as ProtocolAnalysisOutput diff --git a/app/src/organisms/LabwarePositionCheck/utils/__tests__/getPrimaryPipetteId.test.ts b/app/src/organisms/LabwarePositionCheck/utils/__tests__/getPrimaryPipetteId.test.ts index 28150030c37..03e18437fac 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/__tests__/getPrimaryPipetteId.test.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/__tests__/getPrimaryPipetteId.test.ts @@ -1,6 +1,8 @@ import { getPrimaryPipetteId } from '../getPrimaryPipetteId' -import type { LoadedPipette } from '@opentrons/shared-data' -import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { + LoadedPipette, + LoadPipetteRunTimeCommand, +} from '@opentrons/shared-data' describe('getPrimaryPipetteId', () => { it('should return the one and only pipette if there is only one pipette in the protocol', () => { diff --git a/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts b/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts index eb9f9d691cc..526451c467f 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts @@ -1,8 +1,7 @@ import { getIsTiprack, LabwareDefinition2 } from '@opentrons/shared-data' import { getPickUpTipCommandsWithPipette } from '../../Devices/ProtocolRun/utils/getPickUpTipCommandsWithPipette' import { getTipracksVisited } from '../../Devices/ProtocolRun/utils/getTipracksVisited' -import type { RunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7' -import type { LoadedLabware } from '@opentrons/shared-data' +import type { LoadedLabware, RunTimeCommand } from '@opentrons/shared-data' export const doesPipetteVisitAllTipracks = ( pipetteId: string, diff --git a/app/src/organisms/LabwarePositionCheck/utils/getPrimaryPipetteId.ts b/app/src/organisms/LabwarePositionCheck/utils/getPrimaryPipetteId.ts index 0ebdfa0a991..caa799f6dad 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/getPrimaryPipetteId.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/getPrimaryPipetteId.ts @@ -1,7 +1,9 @@ import { getPipetteNameSpecs } from '@opentrons/shared-data' -import type { LoadedPipette } from '@opentrons/shared-data' -import type { RunTimeCommand } from '@opentrons/shared-data/protocol' -import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { + LoadedPipette, + LoadPipetteRunTimeCommand, + RunTimeCommand, +} from '@opentrons/shared-data' export const getPrimaryPipetteId = ( pipettesById: { [id: string]: LoadedPipette }, diff --git a/app/src/organisms/LabwarePositionCheck/utils/labware.ts b/app/src/organisms/LabwarePositionCheck/utils/labware.ts index 0cec4b835d8..fdaa37ba2ee 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/labware.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/labware.ts @@ -2,20 +2,18 @@ import reduce from 'lodash/reduce' import { getIsTiprack, getTiprackVolume, - LabwareDefinition2, getLabwareDefURI, - CompletedProtocolAnalysis, } from '@opentrons/shared-data' import { getModuleInitialLoadInfo } from '../../Devices/ProtocolRun/utils/getModuleInitialLoadInfo' import type { - PickUpTipRunTimeCommand, + CompletedProtocolAnalysis, + LabwareDefinition2, + LabwareLocation, LoadLabwareRunTimeCommand, -} from '@opentrons/shared-data' -import type { + PickUpTipRunTimeCommand, ProtocolAnalysisOutput, RunTimeCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7' -import type { LabwareLocation } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +} from '@opentrons/shared-data' import type { LabwareToOrder } from '../types' export const tipRackOrderSort = ( diff --git a/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx b/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx index 89f3bc25c72..6528b2ddbea 100644 --- a/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx +++ b/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx @@ -20,7 +20,7 @@ import { SubmitPrimaryButton } from '../../atoms/buttons' import { StyledText } from '../../atoms/text' import type { HeaterShakerModule } from '../../redux/modules/types' -import type { HeaterShakerSetTargetTemperatureCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +import type { HeaterShakerSetTargetTemperatureCreateCommand } from '@opentrons/shared-data' interface HeaterShakerSlideoutProps { module: HeaterShakerModule diff --git a/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx b/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx index 4b2a42dd30b..53d56604623 100644 --- a/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx @@ -28,8 +28,10 @@ import { SubmitPrimaryButton } from '../../atoms/buttons' import type { TFunctionResult } from 'i18next' import type { MagneticModule } from '../../redux/modules/types' -import type { MagneticModuleModel } from '@opentrons/shared-data' -import type { MagneticModuleEngageMagnetCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +import type { + MagneticModuleEngageMagnetCreateCommand, + MagneticModuleModel, +} from '@opentrons/shared-data' interface ModelContents { version: string diff --git a/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx b/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx index 6334c07197c..0b02270c931 100644 --- a/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx @@ -18,7 +18,7 @@ import { Slideout } from '../../atoms/Slideout' import { SubmitPrimaryButton } from '../../atoms/buttons' import { InputField } from '../../atoms/InputField' import { StyledText } from '../../atoms/text' -import type { TemperatureModuleSetTargetTemperatureCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +import type { TemperatureModuleSetTargetTemperatureCreateCommand } from '@opentrons/shared-data' import type { TemperatureModule } from '../../redux/modules/types' interface TemperatureModuleSlideoutProps { diff --git a/app/src/organisms/ModuleCard/TestShakeSlideout.tsx b/app/src/organisms/ModuleCard/TestShakeSlideout.tsx index 02ed2417fcb..62343841bce 100644 --- a/app/src/organisms/ModuleCard/TestShakeSlideout.tsx +++ b/app/src/organisms/ModuleCard/TestShakeSlideout.tsx @@ -40,10 +40,10 @@ import { ModuleSetupModal } from './ModuleSetupModal' import type { HeaterShakerModule, LatchStatus } from '../../redux/modules/types' import type { - HeaterShakerSetAndWaitForShakeSpeedCreateCommand, - HeaterShakerDeactivateShakerCreateCommand, HeaterShakerCloseLatchCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' + HeaterShakerDeactivateShakerCreateCommand, + HeaterShakerSetAndWaitForShakeSpeedCreateCommand, +} from '@opentrons/shared-data' interface TestShakeSlideoutProps { module: HeaterShakerModule diff --git a/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx b/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx index a03e1fce885..beda7a2207d 100644 --- a/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx @@ -25,7 +25,7 @@ import type { ThermocyclerModule } from '../../redux/modules/types' import type { TCSetTargetBlockTemperatureCreateCommand, TCSetTargetLidTemperatureCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +} from '@opentrons/shared-data' interface ThermocyclerModuleSlideoutProps { module: ThermocyclerModule diff --git a/app/src/organisms/ModuleCard/hooks.tsx b/app/src/organisms/ModuleCard/hooks.tsx index e91c56be3a6..eb5e835de7c 100644 --- a/app/src/organisms/ModuleCard/hooks.tsx +++ b/app/src/organisms/ModuleCard/hooks.tsx @@ -19,15 +19,15 @@ import { useCurrentRunId } from '../ProtocolUpload/hooks' import type { HeaterShakerCloseLatchCreateCommand, HeaterShakerDeactivateHeaterCreateCommand, - HeaterShakerOpenLatchCreateCommand, HeaterShakerDeactivateShakerCreateCommand, + HeaterShakerOpenLatchCreateCommand, MagneticModuleDisengageCreateCommand, + TCCloseLidCreateCommand, TCDeactivateBlockCreateCommand, TCDeactivateLidCreateCommand, - TemperatureModuleDeactivateCreateCommand, TCOpenLidCreateCommand, - TCCloseLidCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' + TemperatureModuleDeactivateCreateCommand, +} from '@opentrons/shared-data' import type { AttachedModule } from '../../redux/modules/types' diff --git a/app/src/organisms/Navigation/NavigationMenu.tsx b/app/src/organisms/Navigation/NavigationMenu.tsx index 670c787e057..54847e9fd4d 100644 --- a/app/src/organisms/Navigation/NavigationMenu.tsx +++ b/app/src/organisms/Navigation/NavigationMenu.tsx @@ -17,7 +17,6 @@ import { MenuList } from '../../atoms/MenuList' import { MenuItem } from '../../atoms/MenuList/MenuItem' import { home, ROBOT } from '../../redux/robot-controls' import { useLights } from '../Devices/hooks' -import { useFeatureFlag } from '../../redux/config' import { RestartRobotConfirmationModal } from './RestartRobotConfirmationModal' import type { Dispatch } from '../../redux/types' @@ -38,7 +37,6 @@ export function NavigationMenu(props: NavigationMenuProps): JSX.Element { setShowRestartRobotConfirmationModal, ] = React.useState(false) - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') const history = useHistory() const handleRestart = (): void => { @@ -96,23 +94,21 @@ export function NavigationMenu(props: NavigationMenuProps): JSX.Element { - {enableDeckConfig ? ( - history.push('/deck-configuration')} - > - - - - {t('deck_configuration')} - - - - ) : null} + history.push('/deck-configuration')} + > + + + + {t('deck_configuration')} + + + -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -56,9 +51,6 @@ describe('NavigationMenu', () => { mockRestartRobotConfirmationModal.mockReturnValue(
mock RestartRobotConfirmationModal
) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) }) afterEach(() => { @@ -99,19 +91,13 @@ describe('NavigationMenu', () => { getByText('Lights off') }) - it('should render the deck configuration menu item when enableDeckConfiguration is on', () => { - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) + it('should render the deck configuration menu item', () => { const { getByText, getByLabelText } = render(props) getByText('Deck configuration') getByLabelText('deck-map_icon') }) it('should call a mock function when tapping deck configuration', () => { - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) const { getByText } = render(props) getByText('Deck configuration').click() expect(mockPush).toHaveBeenCalledWith('/deck-configuration') diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts b/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts index 0eb2b7d2fb9..c73a3d39efa 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts +++ b/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts @@ -1,7 +1,5 @@ import { useTranslation } from 'react-i18next' -import { useFeatureFlag } from '../../../../redux/config' - import type { ProtocolHardware } from '../../../../pages/Protocols/hooks' export function useHardwareStatusText( @@ -9,8 +7,6 @@ export function useHardwareStatusText( conflictedSlots: string[] ): string { const { t, i18n } = useTranslation('device_details') - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') - const missingProtocolHardwareType = missingProtocolHardware.map( hardware => hardware.hardwareType ) @@ -22,7 +18,7 @@ export function useHardwareStatusText( const countMissingPipettes = countMissingHardwareType('pipette') const countMissingModules = countMissingHardwareType('module') let chipText: string = t('ready_to_run') - if (enableDeckConfig && conflictedSlots.length > 0) { + if (conflictedSlots.length > 0) { chipText = t('location_conflicts') } else if (countMissingPipettes === 0 && countMissingModules > 0) { if (countMissingModules === 1) { diff --git a/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx b/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx index 34f8e32e66c..13459af3cbf 100644 --- a/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx +++ b/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx @@ -21,7 +21,7 @@ import { Portal } from '../../App/portal' import { LabwareDetails } from '../LabwareDetails' import { useMenuHandleClickOutside } from '../../atoms/MenuList/hooks' -import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data' import type { LabwareDefAndDate } from '../../pages/Labware/hooks' interface ProtocolLabwareDetailsProps { diff --git a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx b/app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx index b51355d5a69..125c4f9d90a 100644 --- a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx +++ b/app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx @@ -3,7 +3,7 @@ import { renderWithProviders } from '@opentrons/components' import { i18n } from '../../../i18n' import { ProtocolLabwareDetails } from '../ProtocolLabwareDetails' -import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data' const mockRequiredLabwareDetails = [ { diff --git a/app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx b/app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx index a413a95c728..d5158a9b792 100644 --- a/app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx +++ b/app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx @@ -3,7 +3,7 @@ import { renderWithProviders } from '@opentrons/components' import { OT2_STANDARD_MODEL, FLEX_STANDARD_MODEL } from '@opentrons/shared-data' import { i18n } from '../../../i18n' import { RobotConfigurationDetails } from '../RobotConfigurationDetails' -import type { LoadModuleRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' +import type { LoadModuleRunTimeCommand } from '@opentrons/shared-data' const mockRequiredModuleDetails = [ { diff --git a/app/src/organisms/ProtocolDetails/index.tsx b/app/src/organisms/ProtocolDetails/index.tsx index 273564e9819..da13f2147e0 100644 --- a/app/src/organisms/ProtocolDetails/index.tsx +++ b/app/src/organisms/ProtocolDetails/index.tsx @@ -197,7 +197,6 @@ export function ProtocolDetails( const { protocolKey, srcFileNames, mostRecentAnalysis, modified } = props const { t, i18n } = useTranslation(['protocol_details', 'shared']) const enableProtocolStats = useFeatureFlag('protocolStats') - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') const [currentTab, setCurrentTab] = React.useState< 'robot_config' | 'labware' | 'liquids' | 'stats' >('robot_config') @@ -253,7 +252,7 @@ export function ProtocolDetails( status: 'succeeded', } const requiredFixtureDetails = - enableDeckConfig && mostRecentAnalysis?.commands != null + mostRecentAnalysis?.commands != null ? [ ...map( parseInitialLoadedFixturesByCutout(mostRecentAnalysis.commands) diff --git a/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx b/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx index b606eee186c..b56629a6876 100644 --- a/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx +++ b/app/src/organisms/ProtocolSetupLabware/LabwareMapViewModal.tsx @@ -1,12 +1,11 @@ import * as React from 'react' import map from 'lodash/map' import { useTranslation } from 'react-i18next' -import { BaseDeck, EXTENDED_DECK_CONFIG_FIXTURE } from '@opentrons/components' +import { BaseDeck } from '@opentrons/components' import { FLEX_ROBOT_TYPE, THERMOCYCLER_MODULE_V1 } from '@opentrons/shared-data' import { Modal } from '../../molecules/Modal' import { getDeckConfigFromProtocolCommands } from '../../resources/deck_configuration/utils' -import { useFeatureFlag } from '../../redux/config' import { getStandardDeckViewLayerBlockList } from '../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' import { getLabwareRenderInfo } from '../Devices/ProtocolRun/utils/getLabwareRenderInfo' import { AttachedProtocolModuleMatch } from '../ProtocolSetupModulesAndDeck/utils' @@ -43,10 +42,9 @@ export function LabwareMapViewModal( mostRecentAnalysis, } = props const { t } = useTranslation('protocol_setup') - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') - const deckConfig = enableDeckConfig - ? EXTENDED_DECK_CONFIG_FIXTURE - : getDeckConfigFromProtocolCommands(mostRecentAnalysis?.commands ?? []) + const deckConfig = getDeckConfigFromProtocolCommands( + mostRecentAnalysis?.commands ?? [] + ) const labwareRenderInfo = mostRecentAnalysis != null ? getLabwareRenderInfo(mostRecentAnalysis, deckDef) diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx b/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx index 391ec650de9..5c9bf19d6ed 100644 --- a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx +++ b/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapViewModal.test.tsx @@ -11,7 +11,6 @@ import deckDefFixture from '@opentrons/shared-data/deck/fixtures/3/deckExample.j import fixture_tiprack_300_ul from '@opentrons/shared-data/labware/fixtures/2/fixture_tiprack_300_ul.json' import { i18n } from '../../../i18n' import { getDeckConfigFromProtocolCommands } from '../../../resources/deck_configuration/utils' -import { useFeatureFlag } from '../../../redux/config' import { getLabwareRenderInfo } from '../../Devices/ProtocolRun/utils/getLabwareRenderInfo' import { getStandardDeckViewLayerBlockList } from '../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' import { mockProtocolModuleInfo } from '../__fixtures__' @@ -36,9 +35,6 @@ const mockGetLabwareRenderInfo = getLabwareRenderInfo as jest.MockedFunction< const mockGetDeckConfigFromProtocolCommands = getDeckConfigFromProtocolCommands as jest.MockedFunction< typeof getDeckConfigFromProtocolCommands > -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const mockBaseDeck = BaseDeck as jest.MockedFunction const MOCK_300_UL_TIPRACK_COORDS = [30, 40, 0] @@ -58,9 +54,6 @@ describe('LabwareMapViewModal', () => { beforeEach(() => { mockGetLabwareRenderInfo.mockReturnValue({}) mockGetDeckConfigFromProtocolCommands.mockReturnValue([]) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) }) afterEach(() => { diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx index bc25ebf3537..7f5c0ba2542 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx @@ -27,7 +27,6 @@ import { import { LocationConflictModal } from '../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' import { StyledText } from '../../atoms/text' import { Chip } from '../../atoms/Chip' -import { useFeatureFlag } from '../../redux/config' import type { CompletedProtocolAnalysis, @@ -51,7 +50,6 @@ export function FixtureTable({ setProvidedFixtureOptions, }: FixtureTableProps): JSX.Element { const { t, i18n } = useTranslation('protocol_setup') - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') const STUBBED_LOAD_FIXTURE: LoadFixtureRunTimeCommand = { id: 'stubbed_load_fixture', commandType: 'loadFixture', @@ -72,7 +70,7 @@ export function FixtureTable({ ] = React.useState(false) const requiredFixtureDetails = - enableDeckConfig && mostRecentAnalysis?.commands != null + mostRecentAnalysis?.commands != null ? [ // parseInitialLoadedFixturesByCutout(mostRecentAnalysis.commands), STUBBED_LOAD_FIXTURE, diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/ModulesAndDeckMapViewModal.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/ModulesAndDeckMapViewModal.tsx new file mode 100644 index 00000000000..5045795d771 --- /dev/null +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/ModulesAndDeckMapViewModal.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' + +import { BaseDeck } from '@opentrons/components' +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' + +import { Modal } from '../../molecules/Modal' +import { ModuleInfo } from '../Devices/ModuleInfo' +import { getDeckConfigFromProtocolCommands } from '../../resources/deck_configuration/utils' +import { getStandardDeckViewLayerBlockList } from '../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' + +import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' +import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' +import type { AttachedProtocolModuleMatch } from './utils' + +// Note (kk:10/26/2023) once we are ready for removing ff, we will be able to update props +interface ModulesAndDeckMapViewModalProps { + setShowDeckMapModal: (showDeckMapModal: boolean) => void + attachedProtocolModuleMatches: AttachedProtocolModuleMatch[] + runId: string + protocolAnalysis: CompletedProtocolAnalysis | null +} + +export function ModulesAndDeckMapViewModal({ + setShowDeckMapModal, + attachedProtocolModuleMatches, + runId, + protocolAnalysis, +}: ModulesAndDeckMapViewModalProps): JSX.Element | null { + const { t } = useTranslation('protocol_setup') + + const modalHeader: ModalHeaderBaseProps = { + title: t('map_view'), + hasExitIcon: true, + } + + if (protocolAnalysis == null) return null + + const deckConfig = getDeckConfigFromProtocolCommands( + protocolAnalysis.commands + ) + + const moduleLocations = attachedProtocolModuleMatches.map(module => ({ + moduleModel: module.moduleDef.model, + moduleLocation: { slotName: module.slotName }, + moduleChildren: ( + + ), + })) + + return ( + setShowDeckMapModal(false)} + > + + + ) +} diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx index 3b0be52640b..ff8a3da51ee 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx @@ -1,5 +1,4 @@ import * as React from 'react' -import { when } from 'jest-when' import { renderWithProviders } from '@opentrons/components' import { STAGING_AREA_LOAD_NAME, @@ -8,18 +7,13 @@ import { import { i18n } from '../../../i18n' import { useLoadedFixturesConfigStatus } from '../../../resources/deck_configuration/hooks' -import { useFeatureFlag } from '../../../redux/config' import { LocationConflictModal } from '../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' import { FixtureTable } from '../FixtureTable' import type { LoadFixtureRunTimeCommand } from '@opentrons/shared-data' -jest.mock('../../../redux/config') jest.mock('../../../resources/deck_configuration/hooks') jest.mock('../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal') -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const mockUseLoadedFixturesConfigStatus = useLoadedFixturesConfigStatus as jest.MockedFunction< typeof useLoadedFixturesConfigStatus > @@ -73,9 +67,6 @@ describe('FixtureTable', () => { setFixtureLocation: mockSetFixtureLocation, setProvidedFixtureOptions: mockSetProvidedFixtureOptions, } - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) mockUseLoadedFixturesConfigStatus.mockReturnValue([ { ...mockLoadedFixture, configurationStatus: 'configured' }, ]) diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapViewModal.test.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapViewModal.test.tsx new file mode 100644 index 00000000000..1882f9947cc --- /dev/null +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapViewModal.test.tsx @@ -0,0 +1,125 @@ +import * as React from 'react' +import { when, resetAllWhenMocks } from 'jest-when' + +import { + renderWithProviders, + BaseDeck, + EXTENDED_DECK_CONFIG_FIXTURE, +} from '@opentrons/components' + +import { i18n } from '../../../i18n' +import { getDeckConfigFromProtocolCommands } from '../../../resources/deck_configuration/utils' +import { ModulesAndDeckMapViewModal } from '../ModulesAndDeckMapViewModal' + +jest.mock('@opentrons/components/src/hardware-sim/BaseDeck') +jest.mock('@opentrons/api-client') +jest.mock('../../../redux/config') +jest.mock('../../Devices/hooks') +jest.mock('../../../resources/deck_configuration/utils') +jest.mock('../../Devices/ModuleInfo') +jest.mock('../../Devices/ProtocolRun/utils/getLabwareRenderInfo') + +const mockRunId = 'mockRunId' +const mockSetShowDeckMapModal = jest.fn() +const PROTOCOL_ANALYSIS = { + id: 'fake analysis', + status: 'completed', + labware: [], +} as any + +const mockAttachedProtocolModuleMatches = [ + { + moduleId: 'mockModuleId', + x: 328, + y: 107, + z: 0, + moduleDef: { + $otSharedSchema: 'module/schemas/3', + moduleType: 'magneticBlockType', + model: 'magneticBlockV1', + labwareOffset: { + x: 0, + y: 0, + z: 38, + }, + dimensions: { + bareOverallHeight: 45, + overLabwareHeight: 0, + xDimension: 136, + yDimension: 94, + footprintXDimension: 127.75, + footprintYDimension: 85.75, + labwareInterfaceXDimension: 128, + labwareInterfaceYDimension: 86, + }, + cornerOffsetFromSlot: { + x: -4.125, + y: -4.125, + z: 0, + }, + calibrationPoint: { + x: 0, + y: 0, + z: 0, + }, + config: {}, + gripperOffsets: {}, + displayName: 'Magnetic Block GEN1', + quirks: [], + slotTransforms: { + ot2_standard: {}, + ot2_short_trash: {}, + ot3_standard: {}, + }, + compatibleWith: [], + twoDimensionalRendering: {}, + }, + nestedLabwareDef: null, + nestedLabwareDisplayName: null, + nestedLabwareId: null, + protocolLoadOrder: 0, + slotName: 'C3', + attachedModuleMatch: null, + }, +] as any + +const render = ( + props: React.ComponentProps +) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +const mockBaseDeck = BaseDeck as jest.MockedFunction +const mockGetDeckConfigFromProtocolCommands = getDeckConfigFromProtocolCommands as jest.MockedFunction< + typeof getDeckConfigFromProtocolCommands +> + +describe('ModulesAndDeckMapViewModal', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + setShowDeckMapModal: mockSetShowDeckMapModal, + attachedProtocolModuleMatches: mockAttachedProtocolModuleMatches, + runId: mockRunId, + protocolAnalysis: PROTOCOL_ANALYSIS, + } + when(mockGetDeckConfigFromProtocolCommands).mockReturnValue( + EXTENDED_DECK_CONFIG_FIXTURE + ) + mockBaseDeck.mockReturnValue(
mock BaseDeck
) + }) + + afterEach(() => { + jest.resetAllMocks() + resetAllWhenMocks() + }) + + it('should render BaseDeck map view', () => { + const { getByText } = render(props) + getByText('Map View') + getByText('mock BaseDeck') + }) +}) diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx index 8b74f1e48f0..d5dd9c2d787 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx @@ -27,7 +27,6 @@ import { mockApiHeaterShaker } from '../../../redux/modules/__fixtures__' import { mockProtocolModuleInfo } from '../../ProtocolSetupInstruments/__fixtures__' import { getLocalRobot } from '../../../redux/discovery' import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' -import { useFeatureFlag } from '../../../redux/config' import { getAttachedProtocolModuleMatches, getUnmatchedModulesForProtocol, @@ -36,13 +35,13 @@ import { LocationConflictModal } from '../../Devices/ProtocolRun/SetupModuleAndD import { ModuleWizardFlows } from '../../ModuleWizardFlows' import { SetupInstructionsModal } from '../SetupInstructionsModal' import { FixtureTable } from '../FixtureTable' +import { ModulesAndDeckMapViewModal } from '../ModulesAndDeckMapViewModal' import { ProtocolSetupModulesAndDeck } from '..' jest.mock('@opentrons/react-api-client') jest.mock('../../../resources/runs/hooks') jest.mock('@opentrons/shared-data/js/helpers') jest.mock('../../../redux/discovery') -jest.mock('../../../redux/config') jest.mock('../../../organisms/Devices/hooks') jest.mock( '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' @@ -53,6 +52,7 @@ jest.mock('../SetupInstructionsModal') jest.mock('../../ModuleWizardFlows') jest.mock('../FixtureTable') jest.mock('../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal') +jest.mock('../ModulesAndDeckMapViewModal') const mockGetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction< typeof getDeckDefFromRobotType @@ -87,9 +87,6 @@ const mockModuleWizardFlows = ModuleWizardFlows as jest.MockedFunction< const mockUseChainLiveCommands = useChainLiveCommands as jest.MockedFunction< typeof useChainLiveCommands > -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const mockFixtureTable = FixtureTable as jest.MockedFunction< typeof FixtureTable > @@ -99,6 +96,9 @@ const mockUseDeckConfigurationQuery = useDeckConfigurationQuery as jest.MockedFu const mockLocationConflictModal = LocationConflictModal as jest.MockedFunction< typeof LocationConflictModal > +const mockModulesAndDeckMapViewModal = ModulesAndDeckMapViewModal as jest.MockedFunction< + typeof ModulesAndDeckMapViewModal +> const ROBOT_NAME = 'otie' const RUN_ID = '1' @@ -172,9 +172,9 @@ describe('ProtocolSetupModulesAndDeck', () => { mockLocationConflictModal.mockReturnValue(
mock location conflict modal
) - mockUseDeckConfigurationQuery.mockReturnValue({ - data: [mockFixture], - } as UseQueryResult) + mockUseDeckConfigurationQuery.mockReturnValue(({ + data: [], + } as unknown) as UseQueryResult) when(mockUseRunCalibrationStatus) .calledWith(ROBOT_NAME, RUN_ID) .mockReturnValue({ @@ -184,10 +184,10 @@ describe('ProtocolSetupModulesAndDeck', () => { mockUseChainLiveCommands.mockReturnValue({ chainLiveCommands: mockChainLiveCommands, } as any) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) mockFixtureTable.mockReturnValue(
mock FixtureTable
) + mockModulesAndDeckMapViewModal.mockReturnValue( +
mock ModulesAndDeckMapViewModal
+ ) }) afterEach(() => { @@ -218,6 +218,7 @@ describe('ProtocolSetupModulesAndDeck', () => { }) it('should render module information when a protocol has module - connected', () => { + // TODO: connected not location conflict when(mockGetUnmatchedModulesForProtocol) .calledWith(calibratedMockApiHeaterShaker as any, mockProtocolModuleInfo) .mockReturnValue({ @@ -236,6 +237,7 @@ describe('ProtocolSetupModulesAndDeck', () => { }) it('should render module information when a protocol has module - disconnected', () => { + // TODO: disconnected not location conflict when(mockGetUnmatchedModulesForProtocol) .calledWith(mockApiHeaterShaker as any, mockProtocolModuleInfo) .mockReturnValue({ @@ -253,6 +255,7 @@ describe('ProtocolSetupModulesAndDeck', () => { }) it('should render module information with calibrate button when a protocol has module', async () => { + // TODO: not location conflict when(mockGetUnmatchedModulesForProtocol) .calledWith(mockApiHeaterShaker as any, mockProtocolModuleInfo) .mockReturnValue({ @@ -352,19 +355,25 @@ describe('ProtocolSetupModulesAndDeck', () => { getByText('Calibration required Calibrate pipette first') }) - it('should render mock Fixture table and module location conflict when is enableDeckConfiguration on', () => { + it('should render mock Fixture table and module location conflict', () => { + mockUseDeckConfigurationQuery.mockReturnValue({ + data: [mockFixture], + } as UseQueryResult) mockGetAttachedProtocolModuleMatches.mockReturnValue([ { ...mockProtocolModuleInfo[0], attachedModuleMatch: calibratedMockApiHeaterShaker, }, ]) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) const [{ getByText }] = render() getByText('mock FixtureTable') getByText('Location conflict').click() getByText('mock location conflict modal') }) + + it('should render ModulesAndDeckMapViewModal when tapping map view button', () => { + const [{ getByText }] = render() + getByText('Map View').click() + getByText('mock ModulesAndDeckMapViewModal') + }) }) diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx index 26004f65f70..d56431d4cd7 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx +++ b/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx @@ -11,9 +11,6 @@ import { Icon, JUSTIFY_SPACE_BETWEEN, LocationIcon, - Module, - RobotWorkSpace, - SlotLabels, SPACING, TYPOGRAPHY, } from '@opentrons/components' @@ -22,7 +19,6 @@ import { getDeckDefFromRobotType, getModuleDisplayName, getModuleType, - inferModuleOrientationFromXCoordinate, NON_CONNECTING_MODULE_TYPES, STANDARD_SLOT_LOAD_NAME, TC_MODULE_LOCATION_OT3, @@ -33,19 +29,16 @@ import { Portal } from '../../App/portal' import { FloatingActionButton, SmallButton } from '../../atoms/buttons' import { Chip } from '../../atoms/Chip' import { InlineNotification } from '../../atoms/InlineNotification' -import { Modal } from '../../molecules/Modal' import { StyledText } from '../../atoms/text' import { ChildNavigation } from '../../organisms/ChildNavigation' import { useAttachedModules, useRunCalibrationStatus, } from '../../organisms/Devices/hooks' -import { ModuleInfo } from '../../organisms/Devices/ModuleInfo' import { MultipleModulesModal } from '../Devices/ProtocolRun/SetupModuleAndDeck/MultipleModulesModal' import { getProtocolModulesInfo } from '../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' import { useMostRecentCompletedAnalysis } from '../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' import { ROBOT_MODEL_OT3, getLocalRobot } from '../../redux/discovery' -import { useFeatureFlag } from '../../redux/config' import { useChainLiveCommands } from '../../resources/runs/hooks' import { getModulePrepCommands, @@ -61,24 +54,16 @@ import { ModuleWizardFlows } from '../ModuleWizardFlows' import { LocationConflictModal } from '../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' import { getModuleTooHot } from '../Devices/getModuleTooHot' import { FixtureTable } from './FixtureTable' +import { ModulesAndDeckMapViewModal } from './ModulesAndDeckMapViewModal' import type { CommandData } from '@opentrons/api-client' import type { Cutout, Fixture, FixtureLoadName } from '@opentrons/shared-data' import type { SetupScreens } from '../../pages/OnDeviceDisplay/ProtocolSetup' -import type { ModalHeaderBaseProps } from '../../molecules/Modal/types' import type { ProtocolCalibrationStatus } from '../../organisms/Devices/hooks' import type { AttachedProtocolModuleMatch } from './utils' const ATTACHED_MODULE_POLL_MS = 5000 -const OT3_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [ - 'DECK_BASE', - 'BARCODE_COVERS', - 'SLOT_SCREWS', - 'SLOT_10_EXPANSION', - 'CALIBRATION_CUTOUTS', -] - interface RenderModuleStatusProps { isModuleReady: boolean isDuplicateModuleModel: boolean @@ -104,7 +89,6 @@ function RenderModuleStatus({ conflictedFixture, }: RenderModuleStatusProps): JSX.Element { const { makeSnackbar } = useToaster() - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') const { i18n, t } = useTranslation(['protocol_setup', 'module_setup_wizard']) const handleCalibrate = (): void => { @@ -136,7 +120,7 @@ function RenderModuleStatus({ {isDuplicateModuleModel ? : null} ) - if (conflictedFixture != null && enableDeckConfig) { + if (conflictedFixture != null) { moduleStatus = ( 0 && missingModuleIds.length > 0 - const modalHeader: ModalHeaderBaseProps = { - title: t('map_view'), - hasExitIcon: true, - } - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') - return ( <> @@ -406,50 +384,16 @@ export function ProtocolSetupModulesAndDeck({ /> ) : null} {showDeckMapModal ? ( - setShowDeckMapModal(false)} - > - - {() => ( - <> - {attachedProtocolModuleMatches.map(module => ( - - - - ))} - - - )} - - + ) : null} setSetupScreen('prepare to run')} buttonText={i18n.format(t('setup_instructions'), 'titleCase')} buttonType="tertiaryLowLight" @@ -517,14 +461,12 @@ export function ProtocolSetupModulesAndDeck({ ) })} - {enableDeckConfig ? ( - - ) : null} +
setShowDeckMapModal(true)} /> diff --git a/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx b/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx index 5b5423c6c90..47333226f01 100644 --- a/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx +++ b/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx @@ -16,7 +16,6 @@ import { RecentProtocolRuns } from '../../../organisms/Devices/RecentProtocolRun import { EstopBanner } from '../../../organisms/Devices/EstopBanner' import { DISENGAGED, useEstopContext } from '../../../organisms/EmergencyStop' import { useIsFlex } from '../../../organisms/Devices/hooks' -import { useFeatureFlag } from '../../../redux/config' interface DeviceDetailsComponentProps { robotName: string @@ -30,7 +29,6 @@ export function DeviceDetailsComponent({ enabled: isFlex, }) const { isEmergencyStopModalDismissed } = useEstopContext() - const enableDeckConfiguration = useFeatureFlag('enableDeckConfiguration') return (
- {isFlex && enableDeckConfiguration ? ( - - ) : null} + {isFlex ? : null} ) diff --git a/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx b/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx index b2ee1b62a67..4ca21ca603b 100644 --- a/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx +++ b/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx @@ -8,7 +8,6 @@ import { import { useEstopQuery } from '@opentrons/react-api-client' import { i18n } from '../../../../i18n' -import { useFeatureFlag } from '../../../../redux/config' import { InstrumentsAndModules } from '../../../../organisms/Devices/InstrumentsAndModules' import { RecentProtocolRuns } from '../../../../organisms/Devices/RecentProtocolRuns' import { RobotOverview } from '../../../../organisms/Devices/RobotOverview' @@ -24,7 +23,6 @@ jest.mock('../../../../organisms/Devices/RecentProtocolRuns') jest.mock('../../../../organisms/Devices/RobotOverview') jest.mock('../../../../organisms/DeviceDetailsDeckConfiguration') jest.mock('../../../../redux/discovery') -jest.mock('../../../../redux/config') const ROBOT_NAME = 'otie' const mockEstopStatus = { @@ -47,9 +45,6 @@ const mockRecentProtocolRuns = RecentProtocolRuns as jest.MockedFunction< const mockUseEstopQuery = useEstopQuery as jest.MockedFunction< typeof useEstopQuery > -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const mockDeviceDetailsDeckConfiguration = DeviceDetailsDeckConfiguration as jest.MockedFunction< typeof DeviceDetailsDeckConfiguration > @@ -76,9 +71,6 @@ describe('DeviceDetailsComponent', () => { .calledWith(componentPropsMatcher({ robotName: ROBOT_NAME })) .mockReturnValue(
Mock RecentProtocolRuns
) mockUseEstopQuery.mockReturnValue({ data: mockEstopStatus } as any) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) mockDeviceDetailsDeckConfiguration.mockReturnValue(
Mock DeviceDetailsDeckConfiguration
) @@ -104,10 +96,7 @@ describe('DeviceDetailsComponent', () => { getByText('Mock RecentProtocolRuns') }) - it('renders Deck Configuratin when a robot is flex and enableDeckConfiguration is on', () => { - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) + it('renders Deck Configuration when a robot is flex', () => { when(mockUseIsFlex).calledWith(ROBOT_NAME).mockReturnValue(true) const [{ getByText }] = render() getByText('Mock DeviceDetailsDeckConfiguration') diff --git a/app/src/pages/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetup.test.tsx b/app/src/pages/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetup.test.tsx index 1ba07c166fe..a3ae6771433 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetup.test.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetup.test.tsx @@ -43,7 +43,6 @@ import { } from '../../../../organisms/RunTimeControl/hooks' import { useIsHeaterShakerInProtocol } from '../../../../organisms/ModuleCard/hooks' import { ConfirmAttachedModal } from '../ConfirmAttachedModal' -import { useFeatureFlag } from '../../../../redux/config' import { ProtocolSetup } from '..' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' @@ -77,7 +76,6 @@ jest.mock('../../../../organisms/OnDeviceDisplay/RunningProtocol') jest.mock('../../../../organisms/RunTimeControl/hooks') jest.mock('../../../../organisms/ProtocolSetupLiquids') jest.mock('../../../../organisms/ModuleCard/hooks') -jest.mock('../../../../redux/config') jest.mock('../../../../redux/discovery/selectors') jest.mock('../ConfirmAttachedModal') jest.mock('../../../../organisms/ToasterOven') @@ -144,9 +142,6 @@ const mockUseDoorQuery = useDoorQuery as jest.MockedFunction< typeof useDoorQuery > const mockUseToaster = useToaster as jest.MockedFunction -const mockUseFeatureFlag = useFeatureFlag as jest.MockedFunction< - typeof useFeatureFlag -> const mockUseModuleCalibrationStatus = useModuleCalibrationStatus as jest.MockedFunction< typeof useModuleCalibrationStatus > @@ -310,9 +305,6 @@ describe('ProtocolSetup', () => { .mockReturnValue(({ makeSnackbar: MOCK_MAKE_SNACKBAR, } as unknown) as any) - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(false) }) afterEach(() => { @@ -324,7 +316,7 @@ describe('ProtocolSetup', () => { const [{ getByText }] = render(`/runs/${RUN_ID}/setup/`) getByText('Prepare to run') getByText('Instruments') - getByText('Modules') + getByText('Modules & deck') getByText('Labware') getByText('Labware Position Check') getByText('Liquids') @@ -358,7 +350,7 @@ describe('ProtocolSetup', () => { .mockReturnValue({ missingModuleIds: [], remainingAttachedModules: [] }) const [{ getByText, queryByText }] = render(`/runs/${RUN_ID}/setup/`) expect(queryByText('Mock ProtocolSetupModulesAndDeck')).toBeNull() - queryByText('Modules')?.click() + queryByText('Modules & deck')?.click() getByText('Mock ProtocolSetupModulesAndDeck') }) @@ -418,12 +410,4 @@ describe('ProtocolSetup', () => { 'Close the robot door before starting the run.' ) }) - - it('should render modules & deck when enableDeckConfiguration is on', () => { - when(mockUseFeatureFlag) - .calledWith('enableDeckConfiguration') - .mockReturnValue(true) - const [{ getByText }] = render(`/runs/${RUN_ID}/setup/`) - getByText('Modules & deck') - }) }) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolSetup/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolSetup/index.tsx index 78b223b1633..e0ddab2d4f7 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolSetup/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolSetup/index.tsx @@ -71,10 +71,7 @@ import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, } from '../../../redux/analytics' -import { - getIsHeaterShakerAttached, - useFeatureFlag, -} from '../../../redux/config' +import { getIsHeaterShakerAttached } from '../../../redux/config' import { ConfirmAttachedModal } from './ConfirmAttachedModal' import { getLatestCurrentOffsets } from '../../../organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/utils' import { CloseButton, PlayButton } from './Buttons' @@ -394,8 +391,6 @@ function PrepareToRun({ doorStatus?.data.status === 'open' && doorStatus?.data.doorRequiredClosedForProtocol - const enableDeckConfig = useFeatureFlag('enableDeckConfiguration') - return ( <> {/* Empty box to detect scrolling */} @@ -470,7 +465,7 @@ function PrepareToRun({ /> setSetupScreen('modules')} - title={enableDeckConfig ? t('modules_and_deck') : t('modules')} + title={t('modules_and_deck')} detail={modulesDetail()} status={modulesStatus} disabled={protocolModulesInfo.length === 0} diff --git a/app/src/pages/OnDeviceDisplay/RobotDashboard/WelcomeModal.tsx b/app/src/pages/OnDeviceDisplay/RobotDashboard/WelcomeModal.tsx index 70e38354d5d..97c6f6e5907 100644 --- a/app/src/pages/OnDeviceDisplay/RobotDashboard/WelcomeModal.tsx +++ b/app/src/pages/OnDeviceDisplay/RobotDashboard/WelcomeModal.tsx @@ -17,7 +17,7 @@ import { Modal } from '../../../molecules/Modal' import welcomeModalImage from '../../../assets/images/on-device-display/welcome_dashboard_modal.png' -import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/incidental' +import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' interface WelcomeModalProps { setShowAnalyticsOptInModal: (showAnalyticsOptInModal: boolean) => void diff --git a/app/src/pages/OnDeviceDisplay/RobotDashboard/__tests__/WelcomeModal.test.tsx b/app/src/pages/OnDeviceDisplay/RobotDashboard/__tests__/WelcomeModal.test.tsx index 81287a3987c..8af2ef61d56 100644 --- a/app/src/pages/OnDeviceDisplay/RobotDashboard/__tests__/WelcomeModal.test.tsx +++ b/app/src/pages/OnDeviceDisplay/RobotDashboard/__tests__/WelcomeModal.test.tsx @@ -6,11 +6,10 @@ import { i18n } from '../../../../i18n' import { WelcomeModal } from '../WelcomeModal' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7/command/incidental' +import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' jest.mock('../../../../redux/config') jest.mock('@opentrons/react-api-client') -jest.mock('@opentrons/shared-data/protocol/types/schemaV7/command/incidental') const mockUseCreateLiveCommandMutation = useCreateLiveCommandMutation as jest.MockedFunction< typeof useCreateLiveCommandMutation diff --git a/app/src/pages/Protocols/hooks/index.ts b/app/src/pages/Protocols/hooks/index.ts index b0a48ab1b17..8d9e22d8d41 100644 --- a/app/src/pages/Protocols/hooks/index.ts +++ b/app/src/pages/Protocols/hooks/index.ts @@ -9,7 +9,6 @@ import { import { STANDARD_SLOT_LOAD_NAME } from '@opentrons/shared-data' import { getLabwareSetupItemGroups } from '../utils' import { getProtocolUsesGripper } from '../../../organisms/ProtocolSetupInstruments/utils' -import { useFeatureFlag } from '../../../redux/config' import type { CompletedProtocolAnalysis, @@ -82,9 +81,6 @@ export const useRequiredProtocolHardware = ( const attachedInstruments = attachedInstrumentsData?.data ?? [] const { data: deckConfig } = useDeckConfigurationQuery() - const enableDeckConfigurationFeatureFlag = useFeatureFlag( - 'enableDeckConfiguration' - ) if (analysis == null || analysis?.status !== 'completed') { return { requiredProtocolHardware: [], isLoading: true } @@ -176,20 +172,13 @@ export const useRequiredProtocolHardware = ( }, ] return { - requiredProtocolHardware: enableDeckConfigurationFeatureFlag - ? [ - ...requiredPipettes, - ...requiredModules, - ...requiredGripper, - // ...requiredFixture, - ...STUBBED_FIXTURES, - ] - : [ - ...requiredPipettes, - ...requiredModules, - ...requiredGripper, - // ...requiredFixture, - ], + requiredProtocolHardware: [ + ...requiredPipettes, + ...requiredModules, + ...requiredGripper, + // ...requiredFixture, + ...STUBBED_FIXTURES, + ], isLoading: isLoadingInstruments || isLoadingModules, } } diff --git a/app/src/pages/Protocols/utils/index.ts b/app/src/pages/Protocols/utils/index.ts index 56f54ccd36c..ebc31e37ccc 100644 --- a/app/src/pages/Protocols/utils/index.ts +++ b/app/src/pages/Protocols/utils/index.ts @@ -3,14 +3,12 @@ import { getLabwareDisplayName } from '@opentrons/shared-data' import type { LabwareDefinition2, - ModuleModel, - RunTimeCommand, -} from '@opentrons/shared-data' -import type { LabwareLocation, LoadModuleRunTimeCommand, ModuleLocation, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' + ModuleModel, + RunTimeCommand, +} from '@opentrons/shared-data' export interface LabwareSetupItem { definition: LabwareDefinition2 diff --git a/app/src/redux/config/constants.ts b/app/src/redux/config/constants.ts index d5accd9d70a..6725900bbdb 100644 --- a/app/src/redux/config/constants.ts +++ b/app/src/redux/config/constants.ts @@ -2,10 +2,7 @@ import type { DevInternalFlag } from './types' export const CONFIG_VERSION_LATEST: 1 = 1 -export const DEV_INTERNAL_FLAGS: DevInternalFlag[] = [ - 'enableDeckConfiguration', - 'protocolStats', -] +export const DEV_INTERNAL_FLAGS: DevInternalFlag[] = ['protocolStats'] // action type constants export const INITIALIZED: 'config:INITIALIZED' = 'config:INITIALIZED' diff --git a/app/src/redux/config/schema-types.ts b/app/src/redux/config/schema-types.ts index 431a654c743..64a892c13c3 100644 --- a/app/src/redux/config/schema-types.ts +++ b/app/src/redux/config/schema-types.ts @@ -7,7 +7,7 @@ export type UpdateChannel = 'latest' | 'beta' | 'alpha' export type DiscoveryCandidates = string[] -export type DevInternalFlag = 'enableDeckConfiguration' | 'protocolStats' +export type DevInternalFlag = 'protocolStats' export type FeatureFlags = Partial> diff --git a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx index d8afaa6f800..9b866322a9c 100644 --- a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx +++ b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx @@ -61,6 +61,7 @@ interface BaseDeckProps { }> deckConfig?: DeckConfiguration deckLayerBlocklist?: string[] + showExpansion?: boolean lightFill?: string darkFill?: string children?: React.ReactNode @@ -77,6 +78,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { // TODO(bh, 2023-10-09): remove deck config fixture for Flex after migration to v4 // deckConfig = EXTENDED_DECK_CONFIG_FIXTURE, deckConfig = STANDARD_SLOT_DECK_CONFIG_FIXTURE, + showExpansion = true, children, } = props const deckDef = getDeckDefFromRobotType(robotType) @@ -109,6 +111,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { deckDefinition={deckDef} slotClipColor={darkFill} fixtureBaseColor={lightFill} + showExpansion={showExpansion} /> ))} {stagingAreaFixtures.map(fixture => ( diff --git a/components/src/hardware-sim/BaseDeck/SingleSlotFixture.tsx b/components/src/hardware-sim/BaseDeck/SingleSlotFixture.tsx index 2dbbe04f915..7e763a0fb7f 100644 --- a/components/src/hardware-sim/BaseDeck/SingleSlotFixture.tsx +++ b/components/src/hardware-sim/BaseDeck/SingleSlotFixture.tsx @@ -11,7 +11,7 @@ interface SingleSlotFixtureProps extends React.SVGProps { moduleType?: ModuleType fixtureBaseColor?: React.SVGProps['fill'] slotClipColor?: React.SVGProps['stroke'] - showExtensions?: boolean + showExpansion?: boolean } export function SingleSlotFixture( @@ -22,7 +22,7 @@ export function SingleSlotFixture( deckDefinition, fixtureBaseColor, slotClipColor, - showExtensions, + showExpansion = false, ...restProps } = props @@ -42,7 +42,7 @@ export function SingleSlotFixture( } = { A1: ( <> - {showExtensions ? ( + {showExpansion ? ( ))} {stagingAreaFixtures.map(fixture => ( diff --git a/g-code-testing/g_code_parsing/g_code_engine.py b/g-code-testing/g_code_parsing/g_code_engine.py index 92e2f427bed..30aaeacff8c 100644 --- a/g-code-testing/g_code_parsing/g_code_engine.py +++ b/g-code-testing/g_code_parsing/g_code_engine.py @@ -171,6 +171,7 @@ async def run_protocol( deck_type.for_simulation(robot_type=robot_type) ), ), + load_fixed_trash=deck_type.should_load_fixed_trash(config), ), hardware_api=hardware, # type: ignore ) diff --git a/hardware-testing/Makefile b/hardware-testing/Makefile index 21e66d93491..1206e24b175 100755 --- a/hardware-testing/Makefile +++ b/hardware-testing/Makefile @@ -129,6 +129,7 @@ test-gravimetric-96: .PHONY: test-gravimetric test-gravimetric: -$(MAKE) apply-patches-gravimetric + $(python) -m hardware_testing.gravimetric.daily_setup --simulate $(MAKE) test-gravimetric-single $(MAKE) test-gravimetric-multi $(MAKE) test-gravimetric-96 diff --git a/hardware-testing/hardware_testing/drivers/radwag/driver.py b/hardware-testing/hardware_testing/drivers/radwag/driver.py index 3e9e25e696f..15f7e742a36 100644 --- a/hardware-testing/hardware_testing/drivers/radwag/driver.py +++ b/hardware-testing/hardware_testing/drivers/radwag/driver.py @@ -14,6 +14,8 @@ ) from .responses import RadwagResponse, RadwagResponseCodes, radwag_response_parse +from hardware_testing.data import get_testing_data_directory + class RadwagScaleBase(ABC): """Base class if Radwag scale driver.""" @@ -40,6 +42,11 @@ def read_serial_number(self) -> str: """Read the serial number.""" ... + @abstractmethod + def read_max_capacity(self) -> float: + """Read the max capacity.""" + ... + @abstractmethod def continuous_transmission(self, enable: bool) -> None: """Enable/disable continuous transmission.""" @@ -87,7 +94,8 @@ class RadwagScale(RadwagScaleBase): def __init__(self, connection: Serial) -> None: """Constructor.""" self._connection = connection - self._raw_log = open("/data/testing_data/scale_raw.txt", "w") + _raw_file_path = get_testing_data_directory() / "scale_raw.txt" + self._raw_log = open(_raw_file_path, "w") @classmethod def create( @@ -156,6 +164,22 @@ def disconnect(self) -> None: self._connection.close() self._raw_log.close() + def read_max_capacity(self) -> float: + """Read the max capacity.""" + cmd = RadwagCommand.GET_MAX_CAPACITY + res = self._write_command_and_read_response(cmd) + # NOTE: very annoying, different scales give different response codes + # where some will just not have a response code at all... + if len(res.response_list) == 3: + expected_code = RadwagResponseCodes.IN_PROGRESS + elif len(res.response_list) == 2: + expected_code = RadwagResponseCodes.NONE + else: + raise RuntimeError(f"unexpected reponse list: {res.response_list}") + assert res.code == expected_code, f"Unexpected response code: {res.code}" + assert res.message is not None + return float(res.message) + def read_serial_number(self) -> str: """Read serial number.""" cmd = RadwagCommand.GET_SERIAL_NUMBER @@ -220,6 +244,18 @@ def automatic_internal_adjustment(self, enable: bool) -> None: res.code == RadwagResponseCodes.CARRIED_OUT ), f"Unexpected response code: {res.code}" + def zero(self) -> None: + """Sero the scale.""" + cmd = RadwagCommand.ZERO + res = self._write_command_and_read_response(cmd) + assert ( + res.code == RadwagResponseCodes.IN_PROGRESS + ), f"Unexpected response code: {res.code}" + res = self._read_response(cmd, timeout=60) + assert ( + res.code == RadwagResponseCodes.CARRIED_OUT_AFTER_IN_PROGRESS + ), f"Unexpected response code: {res.code}" + def internal_adjustment(self) -> None: """Run internal adjustment.""" cmd = RadwagCommand.INTERNAL_ADJUST_PERFORMANCE @@ -269,6 +305,10 @@ def disconnect(self) -> None: """Disconnect.""" return + def read_max_capacity(self) -> float: + """Read the max capacity.""" + return 220.0 # :shrug: might as well simulate as low-rez scale + def read_serial_number(self) -> str: """Read serial number.""" return "radwag-sim-serial-num" @@ -297,6 +337,10 @@ def automatic_internal_adjustment(self, enable: bool) -> None: """Automatic internal adjustment.""" return + def zero(self) -> None: + """Zero.""" + return + def internal_adjustment(self) -> None: """Internal adjustment.""" return diff --git a/hardware-testing/hardware_testing/drivers/radwag/responses.py b/hardware-testing/hardware_testing/drivers/radwag/responses.py index 50af16688ca..3555b9f19da 100644 --- a/hardware-testing/hardware_testing/drivers/radwag/responses.py +++ b/hardware-testing/hardware_testing/drivers/radwag/responses.py @@ -98,10 +98,22 @@ def _on_serial_number( return data +def _on_max_capacity( + command: RadwagCommand, response_list: List[str] +) -> RadwagResponse: + data = RadwagResponse.build(command, response_list) + if 2 <= len(response_list) <= 3: + data.message = response_list[-1].replace('"', "") + else: + raise RuntimeError(f"unexpected response list: {response_list}") + return data + + HANDLERS = { RadwagCommand.GET_MEASUREMENT_BASIC_UNIT: _on_unstable_measurement, RadwagCommand.GET_MEASUREMENT_CURRENT_UNIT: _on_unstable_measurement, RadwagCommand.GET_SERIAL_NUMBER: _on_serial_number, + RadwagCommand.GET_MAX_CAPACITY: _on_max_capacity, } diff --git a/hardware-testing/hardware_testing/gravimetric/daily_setup.py b/hardware-testing/hardware_testing/gravimetric/daily_setup.py new file mode 100644 index 00000000000..4e3eb26ba0d --- /dev/null +++ b/hardware-testing/hardware_testing/gravimetric/daily_setup.py @@ -0,0 +1,277 @@ +"""Daily Setup.""" +import argparse +from time import time, sleep + +from opentrons.types import Point +from opentrons.hardware_control import SyncHardwareAPI +from opentrons.hardware_control.types import StatusBarState, OT3Mount + +from hardware_testing.data import create_run_id, create_datetime_string, ui +from hardware_testing.gravimetric.measurement.record import ( + GravimetricRecorder, + GravimetricRecorderConfig, +) +from hardware_testing.gravimetric.config import GANTRY_MAX_SPEED +from hardware_testing.gravimetric.measurement.scale import Scale # type: ignore[import] +from hardware_testing.gravimetric import helpers, workarounds +from hardware_testing.gravimetric.__main__ import API_LEVEL + +TEST_NAME = "gravimetric-daily-setup" + +STABLE_CHECK_SECONDS = 3.0 +STABLE_ATTEMPTS = 10 + +MAX_ALLOWED_ACCURACY_PERCENT_D = 0.01 # percent + +MOVE_X_MM = 450 +MOVE_Y_MM = 350 +MOVE_MINI_MM = 5 + +WALKING_SECONDS = 15 + +COLORS = { + "white": StatusBarState.IDLE, + "green": StatusBarState.RUNNING, + "yellow": StatusBarState.SOFTWARE_ERROR, + "red-flashing": StatusBarState.HARDWARE_ERROR, + "blue-pulsing": StatusBarState.PAUSED, + "green-pulsing": StatusBarState.RUN_COMPLETED, + "white-pulsing": StatusBarState.UPDATING, + "blue-quick": StatusBarState.ACTIVATION, + "green-quick": StatusBarState.CONFIRMATION, + "disco-quick": StatusBarState.DISCO, +} +COLOR_STATES = { + "idle": COLORS["white"], + "interact": COLORS["white-pulsing"], + "stable": COLORS["yellow"], + "walking": COLORS["blue-pulsing"], + "fail": COLORS["red-flashing"], + "pass": COLORS["green"], +} + + +def _get_real_weight() -> float: + try: + inp = input("enter weight's TRUE grams: ").strip() + return float(inp) + except ValueError as e: + print(e) + return _get_real_weight() + + +def _test_stability(recorder: GravimetricRecorder, hw: SyncHardwareAPI) -> None: + def _check_unstable_count(tag: str) -> None: + segment = recorder.recording.get_tagged_samples(tag) + stable_segment = segment.get_stable_samples() + num_unstable = len(segment) - len(stable_segment) + if num_unstable: + raise RuntimeError( + f"unstable samples during {tag} " + f"({num_unstable}x out of {len(segment)}x total)" + ) + + hw.set_status_bar_state(COLOR_STATES["stable"]) + + # BIG MOVES + tag = "BIG-MOVES" + with recorder.samples_of_tag(tag): + hw.move_rel( + OT3Mount.LEFT, Point(x=-MOVE_X_MM, y=-MOVE_Y_MM), speed=GANTRY_MAX_SPEED + ) + hw.move_rel(OT3Mount.LEFT, Point(y=MOVE_Y_MM), speed=GANTRY_MAX_SPEED) + hw.move_rel(OT3Mount.LEFT, Point(y=-MOVE_Y_MM), speed=GANTRY_MAX_SPEED) + hw.move_rel(OT3Mount.LEFT, Point(x=MOVE_X_MM), speed=GANTRY_MAX_SPEED) + hw.move_rel(OT3Mount.LEFT, Point(x=-MOVE_X_MM), speed=GANTRY_MAX_SPEED) + _check_unstable_count(tag) + + # LITTLE MOVES + tag = "LITTLE-MOVES" + with recorder.samples_of_tag(tag): + for _ in range(5): + hw.move_rel(OT3Mount.LEFT, Point(y=MOVE_MINI_MM), speed=GANTRY_MAX_SPEED) + hw.move_rel(OT3Mount.LEFT, Point(y=-MOVE_MINI_MM), speed=GANTRY_MAX_SPEED) + hw.move_rel(OT3Mount.LEFT, Point(x=MOVE_MINI_MM), speed=GANTRY_MAX_SPEED) + hw.move_rel(OT3Mount.LEFT, Point(x=-MOVE_MINI_MM), speed=GANTRY_MAX_SPEED) + _check_unstable_count(tag) + + # GO BACK HOME + tag = "HOMING" + with recorder.samples_of_tag(tag): + hw.move_rel( + OT3Mount.LEFT, Point(x=MOVE_X_MM, y=MOVE_Y_MM), speed=GANTRY_MAX_SPEED + ) + _check_unstable_count(tag) + + hw.set_status_bar_state(COLOR_STATES["idle"]) + + # WALKING + ui.print_info( + "Instructions for next test:\n" + "\t 1) walk around robot\n" + "\t 2) move as if you were working normally" + ) + if not hw.is_simulator: + ui.get_user_ready("prepare to WALK") + tag = "WALKING" + with recorder.samples_of_tag(tag): + num_disco_cycles = int(WALKING_SECONDS / 5) + for _ in range(num_disco_cycles): + hw.set_status_bar_state(COLOR_STATES["walking"]) + if not hw.is_simulator: + sleep(5) + _check_unstable_count(tag) + + +def _wait_for_stability( + recorder: GravimetricRecorder, hw: SyncHardwareAPI, tag: str +) -> float: + prev_light_state = hw.get_status_bar_state() + hw.set_status_bar_state(COLOR_STATES["stable"]) + for i in range(STABLE_ATTEMPTS): + attempt = i + 1 + ui.print_info( + f"attempting {STABLE_CHECK_SECONDS} seconds of stability " + f"(attempt {attempt}/{STABLE_ATTEMPTS})" + ) + tag_detailed = f"{tag}-wait-for-stable-attempt-{attempt}" + with recorder.samples_of_tag(tag_detailed): + if hw.is_simulator: + # NOTE: give a bit of time during simulation, so some fake data can be stored + sleep(0.1) + else: + sleep(STABLE_CHECK_SECONDS) + segment = recorder.recording.get_tagged_samples(tag_detailed) + if hw.is_simulator and len(segment) == 1: + segment.append(segment[0]) + stable_only = segment.get_stable_samples() + if len(segment) == len(stable_only): + ui.print_info(f"stable after {attempt}x attempts") + hw.set_status_bar_state(prev_light_state) + return stable_only.average + raise RuntimeError( + f"unable to reach scale stability after {STABLE_ATTEMPTS}x attempts" + ) + + +def _run( + hw_api: SyncHardwareAPI, recorder: GravimetricRecorder, skip_stability: bool +) -> None: + ui.print_title("GRAVIMETRIC DAILY SETUP") + ui.print_info(f"Scale: {recorder.max_capacity}g (SN:{recorder.serial_number})") + + def _record() -> None: + recorder.set_tag(create_datetime_string()) + recorder.record(in_thread=True) + + def _zero() -> None: + hw_api.set_status_bar_state(COLOR_STATES["stable"]) + recorder.stop() + ui.print_info("zeroing scale...") + recorder.zero_scale() + _record() + hw_api.set_status_bar_state(COLOR_STATES["idle"]) + + def _calibrate() -> None: + hw_api.set_status_bar_state(COLOR_STATES["stable"]) + recorder.stop() + ui.print_info("calibrating scale, this may take up to 1 minute...") + ui.print_info("DO NOT MOVE NEAR SCALE UNTIL CALIBRATION IS COMPLETE!!") + recorder.calibrate_scale() + _record() + hw_api.set_status_bar_state(COLOR_STATES["idle"]) + + ui.print_header("SETUP SCALE") + _record() + hw_api.set_status_bar_state(COLOR_STATES["interact"]) + if not hw_api.is_simulator: + ui.get_user_ready("INSTALL Radwag's default weighing pan") + ui.get_user_ready("REMOVE all weights, vials, and labware from scale") + ui.get_user_ready("CLOSE door and step away from fixture") + hw_api.set_status_bar_state(COLOR_STATES["idle"]) + + ui.print_header("TEST STABILITY") + if not skip_stability: + hw_api.home() + _wait_for_stability(recorder, hw_api, tag="stability") + _test_stability(recorder, hw_api) + else: + ui.print_info("skipping") + + ui.print_header("ZERO SCALE") + if not hw_api.is_simulator: + ui.get_user_ready("about to ZERO the scale:") + _wait_for_stability(recorder, hw_api, tag="zero") + _zero() + + ui.print_header("CALIBRATE SCALE") + if hw_api.is_simulator or ui.get_user_answer("calibrate (ADJUST) this scale"): + if not hw_api.is_simulator: + ui.get_user_ready("about to CALIBRATE the scale:") + _wait_for_stability(recorder, hw_api, tag="calibrate") + _calibrate() + + ui.print_header("TEST ACCURACY") + if hw_api.is_simulator: + recorder.set_simulation_mass(0.0) + start_grams = _wait_for_stability(recorder, hw_api, tag="accuracy-start") + ui.print_info(f"start grams: {start_grams}") + weight_grams = 20 if recorder.max_capacity < 200 else 200 + if not hw_api.is_simulator: + real_weight = _get_real_weight() + hw_api.set_status_bar_state(COLOR_STATES["interact"]) + ui.get_user_ready(f"ADD {weight_grams} gram WEIGHT to scale") + ui.get_user_ready("CLOSE door and step away from fixture") + hw_api.set_status_bar_state(COLOR_STATES["idle"]) + else: + real_weight = float(weight_grams) + recorder.set_simulation_mass(float(weight_grams)) + ui.print_info(f"real grams: {real_weight}") + end_grams = _wait_for_stability(recorder, hw_api, tag="accuracy-end") + ui.print_info(f"end grams: {end_grams}") + found_grams = end_grams - start_grams + + # CALCULATE ACCURACY + accuracy_d = ((found_grams - real_weight) / real_weight) * 100.0 + ui.print_info(f"found weight: {found_grams} grams ({round(accuracy_d, 5)} %D)") + ui.print_info(f"%D must be less than {MAX_ALLOWED_ACCURACY_PERCENT_D} %") + if abs(accuracy_d) > MAX_ALLOWED_ACCURACY_PERCENT_D: + raise RuntimeError(f"accuracy failed: {accuracy_d} %D") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--simulate", action="store_true") + parser.add_argument("--skip-stability", action="store_true") + args = parser.parse_args() + _ctx = helpers.get_api_context( + API_LEVEL, # type: ignore[attr-defined] + is_simulating=args.simulate, + ) + _hw = workarounds.get_sync_hw_api(_ctx) + _hw.set_status_bar_state(COLOR_STATES["idle"]) + _rec = GravimetricRecorder( + GravimetricRecorderConfig( + test_name=TEST_NAME, + run_id=create_run_id(), + start_time=time(), + duration=0, + frequency=1000 if _hw.is_simulator else 5, + stable=False, + ), + scale=Scale.build(simulate=_hw.is_simulator), + simulate=_hw.is_simulator, + ) + try: + _run(_hw, _rec, args.skip_stability) + _hw.set_status_bar_state(COLOR_STATES["pass"]) + ui.print_header("Result: PASS") + except Exception as e: + _hw.set_status_bar_state(COLOR_STATES["fail"]) + ui.print_header("Result: FAIL") + raise e + finally: + if not args.simulate: + ui.get_user_ready("test done") + _rec.stop() + _hw.set_status_bar_state(COLOR_STATES["idle"]) diff --git a/hardware-testing/hardware_testing/gravimetric/measurement/record.py b/hardware-testing/hardware_testing/gravimetric/measurement/record.py index 7e69e2ca4f3..d1e4ab7e4d4 100644 --- a/hardware-testing/hardware_testing/gravimetric/measurement/record.py +++ b/hardware-testing/hardware_testing/gravimetric/measurement/record.py @@ -2,6 +2,7 @@ from contextlib import contextmanager from dataclasses import dataclass from statistics import stdev +from subprocess import Popen from threading import Thread, Event from time import sleep, time from typing import List, Optional, Callable, Generator @@ -10,6 +11,7 @@ dump_data_to_file, append_data_to_file, create_file_name, + ui, ) from .scale import Scale @@ -17,8 +19,7 @@ SLEEP_TIME_IN_RECORD_LOOP = 0.05 SLEEP_TIME_IN_RECORD_LOOP_SIMULATING = 0.01 -SERVER_PORT = 8080 -SERVER_CMD = "{0} -m hardware_testing.tools.plot --test-name gravimetric-ot3 --port {1}" +SERVER_CMD = "python3 -m hardware_testing.tools.plot" @dataclass @@ -216,6 +217,16 @@ def get_time_slice( f"(start={start}, duration={duration})" ) + def get_tagged_samples(self, tag: str) -> "GravimetricRecording": + """Get samples with given tag.""" + return GravimetricRecording( + [sample for sample in self if sample.tag and sample.tag == tag] + ) + + def get_stable_samples(self) -> "GravimetricRecording": + """Get stable samples.""" + return GravimetricRecording([sample for sample in self if sample.stable]) + class GravimetricRecorderConfig: """Recording config.""" @@ -281,9 +292,21 @@ def __init__( self._thread: Optional[Thread] = None self._sample_tag: str = "" self._scale_serial: str = "" + self._scale_max_capacity: float = 0.0 super().__init__() self.activate() + def _start_graph_server_process(self) -> None: + if self.is_simulator: + return + assert self._cfg.test_name + # NOTE: it's ok if this fails b/c prior process is already using port + # the server just needs to be running, doesn't matter when it started + Popen(f"nohup {SERVER_CMD} --test-name {self._cfg.test_name} &", shell=True) + if not self.is_simulator: + sleep(2) # small delay so nohup output isn't confusing + ui.get_user_ready("open WEBPAGE to port 8080") + @property def tag(self) -> str: """Tag.""" @@ -309,6 +332,11 @@ def scale(self) -> Scale: """Scale.""" return self._scale + @property + def max_capacity(self) -> float: + """Max capacity.""" + return self._scale_max_capacity + @property def serial_number(self) -> str: """Serial number.""" @@ -324,14 +352,15 @@ def add_simulation_mass(self, mass: float) -> None: def activate(self) -> None: """Activate.""" + self._start_graph_server_process() # Some Radwag settings cannot be controlled remotely. # Listed below are the things the must be done using the touchscreen: # 1) Set profile to USER # 2) Set screensaver to NONE self._scale.connect() self._scale.initialize() - self._scale.tare(0.0) self._scale_serial = self._scale.read_serial_number() + self._scale_max_capacity = self._scale.read_max_capacity() def deactivate(self) -> None: """Deactivate.""" @@ -371,6 +400,10 @@ def clear_sample_tag(self) -> None: """Clear the sample tag.""" self._sample_tag = "" + def zero_scale(self) -> None: + """Zero scale.""" + self._scale.zero() + def calibrate_scale(self) -> None: """Calibrate scale.""" self._scale.calibrate() diff --git a/hardware-testing/hardware_testing/gravimetric/measurement/scale.py b/hardware-testing/hardware_testing/gravimetric/measurement/scale.py index 7dc5a31489d..e194a9a42b5 100644 --- a/hardware-testing/hardware_testing/gravimetric/measurement/scale.py +++ b/hardware-testing/hardware_testing/gravimetric/measurement/scale.py @@ -112,10 +112,18 @@ def tare(self, grams: float) -> None: """Tare.""" self._scale.set_tare(grams) + def read_max_capacity(self) -> float: + """Read max capacity.""" + return self._scale.read_max_capacity() + def read_serial_number(self) -> str: """Read serial number.""" return self._scale.read_serial_number() + def zero(self) -> None: + """Zero.""" + self._scale.zero() + def calibrate(self) -> None: """Calibrate.""" self._scale.internal_adjustment() diff --git a/hardware-testing/hardware_testing/gravimetric/overrides/api.patch b/hardware-testing/hardware_testing/gravimetric/overrides/api.patch index b145e16cd59..07b69b55ec7 100644 --- a/hardware-testing/hardware_testing/gravimetric/overrides/api.patch +++ b/hardware-testing/hardware_testing/gravimetric/overrides/api.patch @@ -27,7 +27,7 @@ index 174a8f76e4..01b81cd6a0 100644 def ok_to_push_out(self, push_out_dist_mm: float) -> bool: return push_out_dist_mm <= ( diff --git a/api/src/opentrons/protocol_api/core/legacy/deck.py b/api/src/opentrons/protocol_api/core/legacy/deck.py -index 0cddcabd9f..4111cf1cf2 100644 +index b0ca38d294..f213febabd 100644 --- a/api/src/opentrons/protocol_api/core/legacy/deck.py +++ b/api/src/opentrons/protocol_api/core/legacy/deck.py @@ -47,11 +47,11 @@ class DeckItem(Protocol): @@ -36,11 +36,11 @@ index 0cddcabd9f..4111cf1cf2 100644 - def __init__(self, deck_type: str) -> None: + def __init__( -+ self, deck_type: str, version: int = DEFAULT_DECK_DEFINITION_VERSION ++ self, deck_type: str, version: int = DEFAULT_LEGACY_DECK_DEFINITION_VERSION + ) -> None: super().__init__() - self._definition = load_deck( -- name=deck_type, version=DEFAULT_DECK_DEFINITION_VERSION +- name=deck_type, version=DEFAULT_LEGACY_DECK_DEFINITION_VERSION - ) + self._definition = load_deck(name=deck_type, version=version) self._positions = {} diff --git a/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py b/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py index f64d6b79a41..656a8387456 100644 --- a/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py +++ b/hardware-testing/hardware_testing/production_qc/pipette_assembly_qc_ot3/__main__.py @@ -1202,7 +1202,7 @@ async def _jog(_step: float) -> None: async def _matches_state(_state: TipStateType) -> bool: try: await asyncio.sleep(0.2) - await api._backend.check_for_tip_presence(mount, _state) + await api.verify_tip_presence(mount, _state) return True except FailedTipStateCheck: return False diff --git a/hardware-testing/hardware_testing/scripts/gravimetric_rnd.py b/hardware-testing/hardware_testing/scripts/gravimetric_rnd.py index 1b352e26384..1ad0351df76 100644 --- a/hardware-testing/hardware_testing/scripts/gravimetric_rnd.py +++ b/hardware-testing/hardware_testing/scripts/gravimetric_rnd.py @@ -30,7 +30,11 @@ def _run(is_simulating: bool) -> None: scale=Scale.build(simulate=is_simulating), simulate=is_simulating, ) + print(f"Scale: {_rec.max_capacity}g (SN:{_rec.serial_number})") if CALIBRATE_SCALE: + input("Press ENTER to ZERO the scale:") + _rec.zero_scale() + input("Press ENTER to CALIBRATE the scale:") _rec.calibrate_scale() while True: input("Press ENTER to Record:") diff --git a/hardware-testing/hardware_testing/tools/plot/index.html b/hardware-testing/hardware_testing/tools/plot/index.html index 98ffafe4fb3..6b8389645be 100644 --- a/hardware-testing/hardware_testing/tools/plot/index.html +++ b/hardware-testing/hardware_testing/tools/plot/index.html @@ -15,7 +15,7 @@ border: solid 2px black; position: absolute; left: 5px; - top: 55px; + top: 105px; display: inline-block; } #testnamecontainer { @@ -24,13 +24,28 @@ top: 5px; display: inline-block; } + #buttoncontainer { + position: absolute; + border: 1px solid blue; + left: 8px; + top: 35px; + display: inline-block; + width: 500px; + height: 50px; + overflow-x: scroll; + white-space: nowrap; + } -

- Test Name: - -

+
+

+ Test Name: + +

+

+

+
diff --git a/hardware-testing/hardware_testing/tools/plot/index.js b/hardware-testing/hardware_testing/tools/plot/index.js index 72594e05fed..36bacd86e3f 100644 --- a/hardware-testing/hardware_testing/tools/plot/index.js +++ b/hardware-testing/hardware_testing/tools/plot/index.js @@ -1,3 +1,17 @@ +// NOTE: removed "gravimetric-" from string so buttons can be smaller +const testNames = [ + 'rnd', + 'daily-setup', + 'ot3-p50-multi', + 'ot3-p50-multi-50ul-tip-increment', + 'ot3-p50-single', + 'ot3-p1000-96', + 'ot3-p1000-multi', + 'ot3-p1000-multi-50ul-tip-increment', + 'ot3-p1000-multi-200ul-tip-increment', + 'ot3-p1000-multi-1000ul-tip-increment', + 'ot3-p1000-single', +] function getEmptyGravData() { return [ { @@ -104,7 +118,9 @@ function parseGravimetricCSV(CSVData, retData) { window.addEventListener('load', function (evt) { const _updateTimeoutMillis = 100 + const _reloadTimeoutMillis = 1000 let _timeout + let _timeoutReload const layout = { title: 'Untitled', uirevision: true, @@ -112,6 +128,22 @@ window.addEventListener('load', function (evt) { yaxis: { autorange: true }, } const name_input_div = document.getElementById('testname') + const button_input_div = document.getElementById('buttoncontainer') + const allButtons = [] + for (let i = 0; i < testNames.length; i++) { + const btn = document.createElement('input') + btn.type = 'button' + btn.value = testNames[i] + btn.onclick = function () { + name_input_div.value = 'gravimetric-' + btn.value + _setTestNameOfServer(null) + } + btn.style.backgroundColor = 'grey' + btn.style.marginRight = '5px' + button_input_div.appendChild(btn) + allButtons.push(btn) + } + const plotlyDivName = 'plotly' function _clearTimeout() { @@ -119,12 +151,22 @@ window.addEventListener('load', function (evt) { clearTimeout(_timeout) _timeout = undefined } + if (_timeoutReload) { + clearTimeout(_timeoutReload) + _timeoutReload = undefined + } } function _onScreenSizeUpdate(evt) { const div = document.getElementById(plotlyDivName) div.style.width = window.innerWidth - 50 + 'px' div.style.height = window.innerHeight - 100 + 'px' + button_input_div.style.width = window.innerWidth - 50 + 'px' + if (window.innerWidth - 160 > 400) { + name_input_div.style.width = window.innerWidth - 160 + 'px' + } else { + name_input_div.style.width = 400 + 'px' + } } function _initializePlot() { @@ -134,16 +176,31 @@ window.addEventListener('load', function (evt) { } function _onTestNameResponse() { + _clearTimeout() const responseData = JSON.parse(this.responseText) name_input_div.value = responseData.name - _clearTimeout() - _timeout = setTimeout(_getLatestDataFromServer, _updateTimeoutMillis) + let btn_val + for (let i = 0; i < allButtons.length; i++) { + btn_val = 'gravimetric-' + allButtons[i].value + if (btn_val === responseData.name) { + allButtons[i].style.backgroundColor = 'yellow' + } else { + allButtons[i].style.backgroundColor = '#bbb' + } + } _getLatestDataFromServer() } + function _onServerError(evt) { + document.body.style.backgroundColor = 'red' + document.body.innerHTML = '

Lost Connection (refresh)

' + location.reload() + } + function _getLatestDataFromServer(evt) { _clearTimeout() const oReq = new XMLHttpRequest() + oReq.addEventListener('error', _onServerError) oReq.addEventListener('load', function () { const responseData = JSON.parse(this.responseText) let newData = getEmptyPlotlyData() @@ -157,10 +214,12 @@ window.addEventListener('load', function (evt) { }) oReq.open('GET', 'http://' + window.location.host + '/data/latest') oReq.send() + _timeoutReload = setTimeout(_onServerError, _reloadTimeoutMillis) } function _getTestNameFromServer(evt) { const oReq = new XMLHttpRequest() + oReq.addEventListener('error', _onServerError) oReq.addEventListener('load', _onTestNameResponse) oReq.open('GET', 'http://' + window.location.host + '/name') oReq.send() @@ -170,6 +229,7 @@ window.addEventListener('load', function (evt) { _clearTimeout() _initializePlot() const oReq = new XMLHttpRequest() + oReq.addEventListener('error', _onServerError) oReq.addEventListener('load', _onTestNameResponse) oReq.open( 'GET', diff --git a/hardware/opentrons_hardware/hardware_control/tip_presence/__init__.py b/hardware/opentrons_hardware/hardware_control/tip_presence/__init__.py new file mode 100644 index 00000000000..87a64e4f9ec --- /dev/null +++ b/hardware/opentrons_hardware/hardware_control/tip_presence/__init__.py @@ -0,0 +1,6 @@ +"""Tip presence package.""" + +from . import types +from .detector import TipDetector + +__all__ = ["TipDetector", "types"] diff --git a/hardware/opentrons_hardware/hardware_control/tip_presence/detector.py b/hardware/opentrons_hardware/hardware_control/tip_presence/detector.py new file mode 100644 index 00000000000..3884f3406f3 --- /dev/null +++ b/hardware/opentrons_hardware/hardware_control/tip_presence/detector.py @@ -0,0 +1,142 @@ +"""Tip detector.""" + +import asyncio +import logging +from typing import List, Callable, Set + +from opentrons_hardware.drivers.can_bus.can_messenger import ( + CanMessenger, + WaitableCallback, +) + +from opentrons_hardware.firmware_bindings.constants import SensorId +from opentrons_hardware.firmware_bindings.messages import MessageDefinition +from opentrons_hardware.firmware_bindings.messages.message_definitions import ( + TipStatusQueryRequest, + PushTipPresenceNotification, +) +from opentrons_hardware.firmware_bindings import NodeId, ArbitrationId +from opentrons_shared_data.errors.exceptions import CommandTimedOutError + +from .types import TipChangeListener, TipNotification + +log = logging.getLogger(__name__) + + +def _parse_tip_status(response: MessageDefinition) -> TipNotification: + assert isinstance(response, PushTipPresenceNotification) + sensor_id = SensorId(response.payload.sensor_id.value) + status = bool(response.payload.ejector_flag_status.value) + return TipNotification(sensor_id, status) + + +class TipDetector: + """Class to listen to tip presence notifications from pipette firmware.""" + + def __init__( + self, + messenger: CanMessenger, + node: NodeId, + number_of_sensors: int = 1, + debounce_interval: float = 0.5, + ) -> None: + """Initialize a tip detector for a pipette mount.""" + self._messenger = messenger + self._node = node + self._number_of_responses = number_of_sensors + self._debounce_interval = debounce_interval + self._subscribers: Set[TipChangeListener] = set() + self._task = asyncio.create_task(self._main_task()) + self._latest = None + + def add_subscriber(self, subscriber: TipChangeListener) -> Callable[[], None]: + """Add listener to tip change notification.""" + self._subscribers.add(subscriber) + + def remove_subscriber() -> None: + self._subscribers.discard(subscriber) + + return remove_subscriber + + def remove_subscriber(self, subscriber: TipChangeListener) -> None: + """Remove listener to tip change notification.""" + if subscriber in self._subscribers: + self._subscribers.remove(subscriber) + + def _filter(self, arb_id: ArbitrationId) -> bool: + return (arb_id.parts.message_id == PushTipPresenceNotification.message_id) and ( + NodeId(arb_id.parts.originating_node_id) == self._node + ) + + def cleanup(self) -> None: + """Clean up.""" + if not self._task.done(): + self._task.cancel() + + async def _main_task(self) -> None: + try: + await self._main_task_protected() + except Exception: + raise + + async def _main_task_protected(self) -> None: + with WaitableCallback(self._messenger, self._filter) as wc: + await self._debounce_task(wc) + + async def _update_subscribers(self, update: TipNotification) -> None: + for s in self._subscribers: + s(update) + + async def _debounce_task(self, callback: WaitableCallback) -> None: + async for response, _ in callback: + update = _parse_tip_status(response) + + async def _keep_updating() -> None: + nonlocal update + async for response, _ in callback: + update = _parse_tip_status(response) + + try: + await asyncio.wait_for( + _keep_updating(), timeout=self._debounce_interval + ) + except asyncio.TimeoutError: + pass + finally: + await self._update_subscribers(update) + + async def request_tip_status( + self, + timeout: float = 1.0, + ) -> List[TipNotification]: + """Explicitly send a request to get tip status value from a node.""" + + async def gather_responses( + reader: WaitableCallback, + ) -> List[TipNotification]: + data: List[TipNotification] = [] + async for response, _ in reader: + assert isinstance(response, PushTipPresenceNotification) + update = _parse_tip_status(response) + # if we've already received a message from the same sensor, + # replace it with the latest udpate + data = [d for d in data if d.sensor != update.sensor] + data.append(update) + if len(data) == self._number_of_responses: + return data + raise StopAsyncIteration + + with WaitableCallback( + self._messenger, + self._filter, + ) as mc: + await self._messenger.send( + node_id=self._node, message=TipStatusQueryRequest() + ) + try: + status = await asyncio.wait_for(gather_responses(mc), timeout) + except (asyncio.TimeoutError, StopAsyncIteration) as te: + msg = f"Tip presence poll of {self._node} timed out" + log.warning(msg) + raise CommandTimedOutError(message=msg) from te + return status diff --git a/hardware/opentrons_hardware/hardware_control/tip_presence/types.py b/hardware/opentrons_hardware/hardware_control/tip_presence/types.py new file mode 100644 index 00000000000..49f6e22f8e7 --- /dev/null +++ b/hardware/opentrons_hardware/hardware_control/tip_presence/types.py @@ -0,0 +1,16 @@ +"""Tip presence types.""" +import dataclasses +from typing import Callable + +from opentrons_hardware.firmware_bindings.constants import SensorId + + +@dataclasses.dataclass +class TipNotification: + """Represents a tip update received from a pipette.""" + + sensor: SensorId + presence: bool + + +TipChangeListener = Callable[[TipNotification], None] diff --git a/hardware/tests/opentrons_hardware/hardware_control/test_tip_presence.py b/hardware/tests/opentrons_hardware/hardware_control/test_tip_presence.py index bce2af29619..92a0aef664e 100644 --- a/hardware/tests/opentrons_hardware/hardware_control/test_tip_presence.py +++ b/hardware/tests/opentrons_hardware/hardware_control/test_tip_presence.py @@ -2,10 +2,9 @@ import pytest from mock import AsyncMock -from typing import List, Tuple, cast -from typing_extensions import Literal +from typing import List, Tuple -from opentrons_hardware.hardware_control.tip_presence import get_tip_ejector_state +from opentrons_hardware.hardware_control.tip_presence import TipDetector, types from opentrons_hardware.firmware_bindings.messages import ( MessageDefinition, message_definitions, @@ -25,6 +24,7 @@ async def test_get_tip_ejector_state( ) -> None: """Test that get tip ejector state sends the correct request and receives a response.""" node = NodeId.pipette_left + detector = TipDetector(mock_messenger, node) def responder( node_id: NodeId, message: MessageDefinition @@ -47,28 +47,40 @@ def responder( message_send_loopback.add_responder(responder) - res = await get_tip_ejector_state( - mock_messenger, - cast(Literal[NodeId.pipette_left, NodeId.pipette_right], node), - 1, - ) + res = await detector.request_tip_status() # We should have sent a request mock_messenger.send.assert_called_once_with( node_id=node, message=message_definitions.TipStatusQueryRequest() ) - assert res + assert res == [types.TipNotification(SensorId.S0, True)] async def test_tip_ejector_state_times_out(mock_messenger: AsyncMock) -> None: """Test that a timeout is handled.""" node = NodeId.pipette_left + detector = TipDetector(mock_messenger, node, 2) + + def responder( + node_id: NodeId, message: MessageDefinition + ) -> List[Tuple[NodeId, MessageDefinition, NodeId]]: + """Mock send method.""" + if isinstance(message, message_definitions.TipStatusQueryRequest): + return [ + ( + NodeId.host, + message_definitions.PushTipPresenceNotification( + payload=PushTipPresenceNotificationPayload( + ejector_flag_status=UInt8Field(1), + sensor_id=SensorIdField(SensorId.S0), + ) + ), + node, + ) + ] + return [] with pytest.raises(CommandTimedOutError): - res = await get_tip_ejector_state( - mock_messenger, - cast(Literal[NodeId.pipette_left, NodeId.pipette_right], node), - 1, - ) + res = await detector.request_tip_status() assert not res diff --git a/hardware/tests/opentrons_hardware/hardware_control/tip_presence/__init__.py b/hardware/tests/opentrons_hardware/hardware_control/tip_presence/__init__.py new file mode 100644 index 00000000000..a7e7da7da8e --- /dev/null +++ b/hardware/tests/opentrons_hardware/hardware_control/tip_presence/__init__.py @@ -0,0 +1 @@ +"""Tip presence tests.""" diff --git a/hardware/tests/opentrons_hardware/hardware_control/tip_presence/test_detector.py b/hardware/tests/opentrons_hardware/hardware_control/tip_presence/test_detector.py new file mode 100644 index 00000000000..687063e5c8f --- /dev/null +++ b/hardware/tests/opentrons_hardware/hardware_control/tip_presence/test_detector.py @@ -0,0 +1,160 @@ +"""Tests for Tip Detector.""" +from typing import AsyncGenerator, List, Tuple + +from opentrons_hardware.firmware_bindings import NodeId +from opentrons_hardware.firmware_bindings.messages import ( + MessageDefinition, + payloads, + message_definitions, +) +from opentrons_hardware.firmware_bindings.messages.fields import SensorIdField +from opentrons_hardware.firmware_bindings.utils import UInt8Field +from opentrons_hardware.firmware_bindings.constants import SensorId +from tests.conftest import CanLoopback + +import pytest +from mock import AsyncMock + +from opentrons_hardware.hardware_control.tip_presence.detector import TipDetector +from opentrons_hardware.hardware_control.tip_presence.types import TipNotification +from opentrons_shared_data.errors.exceptions import CommandTimedOutError + + +@pytest.fixture +async def subject(mock_messenger: AsyncMock) -> AsyncGenerator[TipDetector, None]: + """A tip detector subject.""" + detector = TipDetector(mock_messenger, NodeId.pipette_left) + try: + yield detector + finally: + detector.cleanup() + + +def create_push_tip_response( + ejector_value: int, + sensor_id: int, +) -> MessageDefinition: + """Create a PushTipPresenceNotification.""" + return message_definitions.PushTipPresenceNotification( + payload=payloads.PushTipPresenceNotificationPayload( + ejector_flag_status=UInt8Field(ejector_value), + sensor_id=SensorIdField(sensor_id), + ) + ) + + +async def test_tip_request_listens_to_only_one_node( + subject: TipDetector, + mock_messenger: AsyncMock, + message_send_loopback: CanLoopback, +) -> None: + """Test that the tip status request receives the correct response.""" + + def responder( + node_id: NodeId, message: MessageDefinition + ) -> List[Tuple[NodeId, MessageDefinition, NodeId]]: + if isinstance(message, message_definitions.TipStatusQueryRequest): + return [ + # responds with push tip notifications from different nodes + (NodeId.host, create_push_tip_response(1, 1), NodeId.pipette_left), + (NodeId.host, create_push_tip_response(30, 30), NodeId.pipette_right), + (NodeId.host, create_push_tip_response(20, 20), NodeId.gantry_x), + ] + return [] + + message_send_loopback.add_responder(responder) + + update = await subject.request_tip_status() + # ensure a tip status request was sent via CAN + mock_messenger.send.assert_called_once_with( + node_id=NodeId.pipette_left, message=message_definitions.TipStatusQueryRequest() + ) + assert len(update) == 1 + assert update[0] == TipNotification( + sensor=SensorId.S1, + presence=True, + ) + + +@pytest.mark.parametrize( + "responses", + [ + [ # only from one message + (NodeId.host, create_push_tip_response(0, 0), NodeId.pipette_left) + ], + [ # two messages but from only one sensor + (NodeId.host, create_push_tip_response(0, 0), NodeId.pipette_left), + (NodeId.host, create_push_tip_response(1, 0), NodeId.pipette_left), + ], + ], +) +async def test_high_throughput_tip_detector_timeout( + subject: TipDetector, + mock_messenger: AsyncMock, + message_send_loopback: CanLoopback, + responses: List[Tuple[NodeId, MessageDefinition, NodeId]], +) -> None: + """Test that a timeout is handled.""" + + def responder( + node_id: NodeId, message: MessageDefinition + ) -> List[Tuple[NodeId, MessageDefinition, NodeId]]: + if isinstance(message, message_definitions.TipStatusQueryRequest): + return responses + return [] + + message_send_loopback.add_responder(responder) + + # subject expects two responses for high throughput pipettes + subject._number_of_responses = 2 + with pytest.raises(CommandTimedOutError): + await subject.request_tip_status() + # ensure a tip status request was sent via CAN + mock_messenger.send.assert_called_once_with( + node_id=NodeId.pipette_left, + message=message_definitions.TipStatusQueryRequest(), + ) + + +@pytest.mark.parametrize( + "responses", + [ + [ # messages from both sensors + (NodeId.host, create_push_tip_response(0, 0), NodeId.pipette_left), + (NodeId.host, create_push_tip_response(0, 1), NodeId.pipette_left), + ], + [ # two messages from one sensor, and one from the other + (NodeId.host, create_push_tip_response(1, 0), NodeId.pipette_left), + (NodeId.host, create_push_tip_response(0, 0), NodeId.pipette_left), + (NodeId.host, create_push_tip_response(0, 1), NodeId.pipette_left), + ], + ], +) +async def test_high_throughput_tip_detector_success( + subject: TipDetector, + mock_messenger: AsyncMock, + message_send_loopback: CanLoopback, + responses: List[Tuple[NodeId, MessageDefinition, NodeId]], +) -> None: + """Test that only the latest message from each sensor is read.""" + + def responder( + node_id: NodeId, message: MessageDefinition + ) -> List[Tuple[NodeId, MessageDefinition, NodeId]]: + if isinstance(message, message_definitions.TipStatusQueryRequest): + return responses + return [] + + message_send_loopback.add_responder(responder) + + # subject expects two responses for high throughput pipettes + subject._number_of_responses = 2 + update = await subject.request_tip_status(3.0) + # ensure a tip status request was sent via CAN + mock_messenger.send.assert_called_once_with( + node_id=NodeId.pipette_left, message=message_definitions.TipStatusQueryRequest() + ) + + assert len(update) == 2 + assert TipNotification(SensorId.S0, False) in update + assert TipNotification(SensorId.S1, False) in update diff --git a/protocol-designer/cypress/integration/migrations.spec.js b/protocol-designer/cypress/integration/migrations.spec.js index 7788a9acf1e..ec21b08994e 100644 --- a/protocol-designer/cypress/integration/migrations.spec.js +++ b/protocol-designer/cypress/integration/migrations.spec.js @@ -1,6 +1,7 @@ import 'cypress-file-upload' import cloneDeep from 'lodash/cloneDeep' import { expectDeepEqual } from '@opentrons/shared-data/js/cypressUtils' +import { FLEX_TRASH_DEF_URI, OT_2_TRASH_DEF_URI } from '../../src/constants' const semver = require('semver') // TODO: (sa 2022-03-31: change these migration fixtures to v6 protocols once the liquids key is added to PD protocols @@ -12,61 +13,54 @@ describe('Protocol fixtures migrate and match snapshots', () => { }) const testCases = [ - // TODO(jr, 9/26/23): when 7.1 is more developed, lets go back and fix these - // { - // title: 'example_1_1_0 (schema 1, PD version 1.1.1) -> PD 7.0.x, schema 7', - // importFixture: '../../fixtures/protocol/1/example_1_1_0.json', - // expectedExportFixture: - // '../../fixtures/protocol/7/example_1_1_0MigratedFromV1_0_0.json', - // unusedPipettes: true, - // migrationModal: 'newLabwareDefs', - // }, - // { - // title: 'doItAllV3 (schema 3, PD version 4.0.0) -> PD 7.0.x, schema 7', - // importFixture: '../../fixtures/protocol/4/doItAllV3.json', - // expectedExportFixture: - // '../../fixtures/protocol/7/doItAllV3MigratedToV7.json', - // unusedPipettes: false, - // migrationModal: 'generic', - // }, - // { - // title: 'doItAllV4 (schema 4, PD version 4.0.0) -> PD 7.0.x, schema 7', - // importFixture: '../../fixtures/protocol/4/doItAllV4.json', - // expectedExportFixture: - // '../../fixtures/protocol/7/doItAllV4MigratedToV7.json', - // unusedPipettes: false, - // migrationModal: 'generic', - // }, - // { - // title: 'doItAllV6 (schema 6, PD version 6.1.0) -> PD 7.0.x, schema 7', - // importFixture: '../../fixtures/protocol/6/doItAllV4MigratedToV6.json', - // expectedExportFixture: - // '../../fixtures/protocol/7/doItAllV4MigratedToV7.json', - // unusedPipettes: false, - // migrationModal: 'generic', - // }, + { + title: 'example_1_1_0 (schema 1, PD version 1.1.1) -> PD 8.0.x, schema 8', + importFixture: '../../fixtures/protocol/1/example_1_1_0.json', + expectedExportFixture: + '../../fixtures/protocol/8/example_1_1_0MigratedToV8.json', + unusedPipettes: true, + migrationModal: 'newLabwareDefs', + }, + { + title: 'doItAllV3 (schema 3, PD version 4.0.0) -> PD 8.0.x, schema 8', + importFixture: '../../fixtures/protocol/4/doItAllV3.json', + expectedExportFixture: + '../../fixtures/protocol/8/doItAllV3MigratedToV8.json', + unusedPipettes: false, + migrationModal: 'generic', + }, + { + title: 'doItAllV4 (schema 4, PD version 4.0.0) -> PD 8.0.x, schema 8', + importFixture: '../../fixtures/protocol/4/doItAllV4.json', + expectedExportFixture: + '../../fixtures/protocol/8/doItAllV4MigratedToV8.json', + unusedPipettes: false, + migrationModal: 'generic', + }, + // TODO(jr, 11/1/23): add a test for v8 migrated to v8 with the deck config commands // { // title: - // 'doItAllV7 (schema 7, PD version 7.0.0) -> import and re-export should preserve data', + // 'doItAllV8 (schema 7, PD version 8.0.0) -> import and re-export should preserve data', // importFixture: '../../fixtures/protocol/7/doItAllV4MigratedToV7.json', // expectedExportFixture: // '../../fixtures/protocol/7/doItAllV4MigratedToV7.json', // unusedPipettes: false, // migrationModal: null, // }, - // { - // title: - // 'mix 5.0.x (schema 3, PD version 5.0.0) -> should migrate to 7.0.x, schema 7', - // importFixture: '../../fixtures/protocol/5/mix_5_0_x.json', - // expectedExportFixture: '../../fixtures/protocol/7/mix_7_0_0.json', - // migrationModal: 'generic', - // unusedPipettes: false, - // }, { - title: 'doItAllV7 Flex robot (schema 7, PD version 7.1.0)', + title: + 'mix 5.0.x (schema 3, PD version 5.0.0) -> should migrate to 8.0.x, schema 8', + importFixture: '../../fixtures/protocol/5/mix_5_0_x.json', + expectedExportFixture: '../../fixtures/protocol/8/mix_8_0_0.json', + migrationModal: 'generic', + unusedPipettes: false, + }, + { + title: 'doItAll7MigratedToV8 flex robot (schema 8, PD version 8.0.x)', importFixture: '../../fixtures/protocol/7/doItAllV7.json', - expectedExportFixture: '../../fixtures/protocol/7/doItAllV7.json', - migrationModal: null, + expectedExportFixture: + '../../fixtures/protocol/8/doItAllV7MigratedToV8.json', + migrationModal: 'generic', unusedPipettes: false, }, ] @@ -160,10 +154,71 @@ describe('Protocol fixtures migrate and match snapshots', () => { f.metadata.lastModified = 123 f.designerApplication.data._internalAppBuildDate = 'Foo Date' f.designerApplication.version = 'x.x.x' + + // NOTE: labwareLocationUpdates, trash stubs can be removed for the release after 8.0.0 + // currently stubbed because of the newly created trash id for movable trash support + const labwareLocationUpdate = + f.designerApplication.data.savedStepForms + .__INITIAL_DECK_SETUP_STEP__.labwareLocationUpdate + + Object.entries(labwareLocationUpdate).forEach( + ([labwareId, slot]) => { + if ( + labwareId.includes(OT_2_TRASH_DEF_URI) || + labwareId.includes(FLEX_TRASH_DEF_URI) + ) { + const trashId = 'trashId' + labwareLocationUpdate[trashId] = slot + delete labwareLocationUpdate[labwareId] + } + } + ) + + Object.values( + f.designerApplication.data.savedStepForms + ).forEach(stepForm => { + if (stepForm.stepType === 'moveLiquid') { + stepForm.dropTip_location = 'trash drop tip location' + if ( + stepForm.blowout_location.includes(OT_2_TRASH_DEF_URI) || + stepForm.blowout_location.includes(FLEX_TRASH_DEF_URI) + ) { + stepForm.blowout_location = 'trash blowout location' + } + } + if (stepForm.stepType === 'mix') { + if ( + stepForm.labware.includes(OT_2_TRASH_DEF_URI) || + stepForm.labware.includes(FLEX_TRASH_DEF_URI) + ) { + stepForm.labware = 'trash' + } + stepForm.dropTip_location = 'trash drop tip location' + stepForm.blowout_location = 'trash blowout location' + } + }) f.commands.forEach(command => { if ('key' in command) { command.key = '123' } + if ( + command.commandType === 'loadLabware' && + command.params.displayName === 'Opentrons Fixed Trash' + ) { + command.params.labwareId = 'loadTrash' + } + if (command.commandType === 'dropTip') { + command.params.labwareId = 'dropTipLabwareId' + } + if ( + (command.commandType === 'aspirate' || + command.commandType === 'dispense' || + command.commandType === 'blowout') && + (command.params.labwareId.includes(OT_2_TRASH_DEF_URI) || + command.params.labwareId.includes(FLEX_TRASH_DEF_URI)) + ) { + command.params.labwareId = 'trash' + } }) }) diff --git a/protocol-designer/fixtures/protocol/7/doItAllV7.json b/protocol-designer/fixtures/protocol/7/doItAllV7.json index 49a5a41844d..b22c925dafb 100644 --- a/protocol-designer/fixtures/protocol/7/doItAllV7.json +++ b/protocol-designer/fixtures/protocol/7/doItAllV7.json @@ -11,7 +11,7 @@ }, "designerApplication": { "name": "opentrons/protocol-designer", - "version": "7.1.0", + "version": "7.0.0", "data": { "_internalAppBuildDate": "Tue, 26 Sep 2023 18:58:15 GMT", "defaultValues": { @@ -59,7 +59,7 @@ "savedStepForms": { "__INITIAL_DECK_SETUP_STEP__": { "labwareLocationUpdate": { - "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1": "A3", + "fixedTrash": "12", "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1": "C1", "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType", "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType", @@ -174,7 +174,7 @@ "disposalVolume_checkbox": true, "disposalVolume_volume": "100", "blowout_checkbox": false, - "blowout_location": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "blowout_location": "fixedTrash", "preWetTip": false, "aspirate_airGap_checkbox": false, "aspirate_airGap_volume": "0", @@ -189,8 +189,7 @@ "id": "f9a294f1-f42b-4cae-893a-592405349d56", "stepType": "moveLiquid", "stepName": "transfer", - "stepDetails": "", - "dropTip_location": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1" + "stepDetails": "" }, "5fdb9a12-fab4-42fd-886f-40af107b15d6": { "times": "2", @@ -199,7 +198,7 @@ "mix_wellOrder_first": "t2b", "mix_wellOrder_second": "l2r", "blowout_checkbox": false, - "blowout_location": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "blowout_location": "fixedTrash", "mix_mmFromBottom": 0.5, "pipette": "6d1e53c3-2db3-451b-ad60-3fe13781a193", "volume": "10", @@ -215,8 +214,7 @@ "id": "5fdb9a12-fab4-42fd-886f-40af107b15d6", "stepType": "mix", "stepName": "mix", - "stepDetails": "", - "dropTip_location": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1" + "stepDetails": "" }, "3901f6f9-cecd-4d2a-8d85-40d85f9f8b4f": { "labware": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", @@ -3857,18 +3855,6 @@ } } }, - { - "key": "63f8a3e1-9508-46e9-9f57-01c5c0d86fe6", - "commandType": "loadLabware", - "params": { - "displayName": "Opentrons Fixed Trash", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", - "loadName": "opentrons_1_trash_3200ml_fixed", - "namespace": "opentrons", - "version": 1, - "location": { "slotName": "A3" } - } - }, { "key": "84129019-df06-4786-8284-6247d102b82f", "commandType": "loadLabware", @@ -4059,7 +4045,7 @@ "key": "1a6fc406-0502-4580-893b-f81ffd7731d7", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4101,7 +4087,7 @@ "key": "44851df5-60c4-413b-86a3-9e318782adae", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4143,7 +4129,7 @@ "key": "419e1de0-f1f8-4f43-833d-06ce3c32d20d", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4185,7 +4171,7 @@ "key": "84f31ede-7edd-447c-9449-6f494c91a621", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4227,7 +4213,7 @@ "key": "916c823e-3c94-4cf9-8346-81eed082f8db", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4269,7 +4255,7 @@ "key": "63a99616-95ea-465b-af11-79b8f35537ac", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4311,7 +4297,7 @@ "key": "bd16d128-3817-40b1-8c3d-5c9f7f0e560f", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4353,7 +4339,7 @@ "key": "49324cc8-34c2-4398-b30a-2a052deab15e", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4395,7 +4381,7 @@ "key": "518237bc-d1b8-4fc8-87fe-3ca23e2cc33d", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4437,7 +4423,7 @@ "key": "e3339560-6b34-485e-a61b-e80cbcf803be", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4479,7 +4465,7 @@ "key": "ad2f40ed-df71-4227-9c3b-0558275f93c0", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4521,7 +4507,7 @@ "key": "a37ba6ab-824b-43c4-97cc-19dc199032b9", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4563,7 +4549,7 @@ "key": "1a483fdc-0575-45fb-9e92-975222f043b6", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4605,7 +4591,7 @@ "key": "50eccd27-1248-4d3d-9170-d9ab7c34355b", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4647,7 +4633,7 @@ "key": "ff218f5b-4af0-4ac5-bdea-a65fbe0c9baf", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4689,7 +4675,7 @@ "key": "7404ee18-8e3b-49f0-a6f8-661c9200722f", "params": { "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, @@ -4763,7 +4749,7 @@ "key": "fa3753fd-aa3f-4eb3-be8c-a01c15e2c81a", "params": { "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", - "labwareId": "89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1", + "labwareId": "fixedTrash", "wellName": "A1" } }, diff --git a/protocol-designer/fixtures/protocol/8/doItAllV3MigratedToV8.json b/protocol-designer/fixtures/protocol/8/doItAllV3MigratedToV8.json new file mode 100644 index 00000000000..535f5cdae9c --- /dev/null +++ b/protocol-designer/fixtures/protocol/8/doItAllV3MigratedToV8.json @@ -0,0 +1,3019 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Do it all v3", + "author": "Fixture", + "description": "Test all v3 commands", + "created": 1585930833548, + "lastModified": 1698855895745, + "category": null, + "subcategory": null, + "tags": [] + }, + "designerApplication": { + "name": "opentrons/protocol-designer", + "version": "8.0.0", + "data": { + "_internalAppBuildDate": "Wed, 01 Nov 2023 16:22:39 GMT", + "defaultValues": { + "aspirate_mmFromBottom": 1, + "dispense_mmFromBottom": 0.5, + "touchTip_mmFromTop": -1, + "blowout_mmFromTop": 0 + }, + "pipetteTiprackAssignments": { + "0b3f2210-75c7-11ea-b42f-4b64e50f43e5": "opentrons/opentrons_96_tiprack_300ul/1" + }, + "dismissedWarnings": { "form": {}, "timeline": {} }, + "ingredients": { + "0": { + "name": "Water", + "description": null, + "serialize": false, + "liquidGroupId": "0" + } + }, + "ingredLocations": { + "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1": { + "A1": { "0": { "volume": 100 } }, + "B1": { "0": { "volume": 100 } }, + "C1": { "0": { "volume": 100 } }, + "D1": { "0": { "volume": 100 } }, + "E1": { "0": { "volume": 100 } }, + "F1": { "0": { "volume": 100 } }, + "G1": { "0": { "volume": 100 } }, + "H1": { "0": { "volume": 100 } }, + "A2": { "0": { "volume": 100 } }, + "B2": { "0": { "volume": 100 } }, + "C2": { "0": { "volume": 100 } }, + "D2": { "0": { "volume": 100 } }, + "E2": { "0": { "volume": 100 } }, + "F2": { "0": { "volume": 100 } }, + "G2": { "0": { "volume": 100 } }, + "H2": { "0": { "volume": 100 } } + } + }, + "savedStepForms": { + "__INITIAL_DECK_SETUP_STEP__": { + "labwareLocationUpdate": { + "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1": "12", + "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1": "2", + "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1": "1", + "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1": "3" + }, + "pipetteLocationUpdate": { + "0b3f2210-75c7-11ea-b42f-4b64e50f43e5": "left" + }, + "moduleLocationUpdate": {}, + "stepType": "manualIntervention", + "id": "__INITIAL_DECK_SETUP_STEP__" + }, + "3961e4c0-75c7-11ea-b42f-4b64e50f43e5": { + "pipette": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": "40", + "changeTip": "always", + "path": "multiDispense", + "aspirate_wells_grouped": false, + "aspirate_flowRate": null, + "aspirate_labware": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "aspirate_wells": ["A1"], + "aspirate_wellOrder_first": "t2b", + "aspirate_wellOrder_second": "l2r", + "aspirate_mix_checkbox": true, + "aspirate_mix_times": "2", + "aspirate_mix_volume": "30", + "aspirate_mmFromBottom": 1, + "aspirate_touchTip_checkbox": true, + "aspirate_touchTip_mmFromBottom": 12.8, + "dispense_flowRate": null, + "dispense_labware": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "dispense_wells": ["A1", "A2"], + "dispense_wellOrder_first": "t2b", + "dispense_wellOrder_second": "l2r", + "dispense_mix_checkbox": false, + "dispense_mix_times": null, + "dispense_mix_volume": null, + "dispense_mmFromBottom": 0.5, + "dispense_touchTip_checkbox": true, + "dispense_touchTip_mmFromBottom": 40, + "disposalVolume_checkbox": true, + "disposalVolume_volume": "20", + "blowout_checkbox": false, + "blowout_location": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "preWetTip": false, + "aspirate_airGap_checkbox": false, + "aspirate_airGap_volume": null, + "aspirate_delay_checkbox": false, + "aspirate_delay_mmFromBottom": "1", + "aspirate_delay_seconds": "1", + "dispense_airGap_checkbox": false, + "dispense_airGap_volume": null, + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "dispense_delay_mmFromBottom": "0.5", + "dropTip_location": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "id": "3961e4c0-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "moveLiquid", + "stepName": "transfer", + "stepDetails": "" + }, + "54dc3200-75c7-11ea-b42f-4b64e50f43e5": { + "pauseAction": "untilResume", + "pauseHour": null, + "pauseMinute": null, + "pauseSecond": null, + "pauseMessage": "Wait until user intervention", + "moduleId": null, + "pauseTemperature": null, + "id": "54dc3200-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "5db07ad0-75c7-11ea-b42f-4b64e50f43e5": { + "pauseAction": "untilTime", + "pauseHour": null, + "pauseMinute": "1", + "pauseSecond": "2", + "pauseMessage": "", + "moduleId": null, + "pauseTemperature": null, + "id": "5db07ad0-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "a4cee9a0-75dc-11ea-b42f-4b64e50f43e5": { + "times": "3", + "changeTip": "always", + "labware": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "mix_wellOrder_first": "t2b", + "mix_wellOrder_second": "l2r", + "blowout_checkbox": true, + "blowout_location": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "mix_mmFromBottom": 0.5, + "pipette": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": "35", + "wells": ["D2", "E2"], + "aspirate_flowRate": 40, + "dispense_flowRate": 35, + "aspirate_delay_checkbox": false, + "aspirate_delay_seconds": "1", + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "mix_touchTip_checkbox": true, + "mix_touchTip_mmFromBottom": 11.8, + "dropTip_location": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "id": "a4cee9a0-75dc-11ea-b42f-4b64e50f43e5", + "stepType": "mix", + "stepName": "mix", + "stepDetails": "" + } + }, + "orderedStepIds": [ + "5db07ad0-75c7-11ea-b42f-4b64e50f43e5", + "3961e4c0-75c7-11ea-b42f-4b64e50f43e5", + "54dc3200-75c7-11ea-b42f-4b64e50f43e5", + "a4cee9a0-75dc-11ea-b42f-4b64e50f43e5" + ] + } + }, + "robot": { "model": "OT-2 Standard", "deckId": "ot2_standard" }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_96_tiprack_300ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "wells": { + "A1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 59.3, + "tipOverlap": 7.47, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_300ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": [ + "fixedTrash", + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 0, + "x": 82.84, + "y": 80, + "z": 82 + } + }, + "brand": { "brand": "Opentrons" }, + "groups": [{ "wells": ["A1"], "metadata": {} }], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "NEST", + "brandId": ["402501"], + "links": ["https://www.nest-biotech.com/pcr-plates/58773587.html"] + }, + "metadata": { + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "wells": { + "A1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + } + }, + "groups": [ + { + "metadata": { "wellBottomShape": "v" }, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": false, + "isMagneticModuleCompatible": true, + "magneticModuleEngageHeight": 20, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1": { + "ordering": [ + ["A1", "B1", "C1", "D1"], + ["A2", "B2", "C2", "D2"], + ["A3", "B3", "C3", "D3"], + ["A4", "B4", "C4", "D4"], + ["A5", "B5", "C5", "D5"], + ["A6", "B6", "C6", "D6"] + ], + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "metadata": { + "displayName": "Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap", + "displayVolumeUnits": "mL", + "displayCategory": "aluminumBlock", + "tags": [] + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 42 + }, + "parameters": { + "format": "irregular", + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_24_aluminumblock_generic_2ml_screwcap" + }, + "wells": { + "D1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 16.88, + "z": 6.7 + }, + "C1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 34.13, + "z": 6.7 + }, + "B1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 51.38, + "z": 6.7 + }, + "A1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 68.63, + "z": 6.7 + }, + "D2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 16.88, + "z": 6.7 + }, + "C2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 34.13, + "z": 6.7 + }, + "B2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 51.38, + "z": 6.7 + }, + "A2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 68.63, + "z": 6.7 + }, + "D3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 16.88, + "z": 6.7 + }, + "C3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 34.13, + "z": 6.7 + }, + "B3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 51.38, + "z": 6.7 + }, + "A3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 68.63, + "z": 6.7 + }, + "D4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 16.88, + "z": 6.7 + }, + "C4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 34.13, + "z": 6.7 + }, + "B4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 51.38, + "z": 6.7 + }, + "A4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 68.63, + "z": 6.7 + }, + "D5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 16.88, + "z": 6.7 + }, + "C5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 34.13, + "z": 6.7 + }, + "B5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 51.38, + "z": 6.7 + }, + "A5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 68.63, + "z": 6.7 + }, + "D6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 16.88, + "z": 6.7 + }, + "C6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 34.13, + "z": 6.7 + }, + "B6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 51.38, + "z": 6.7 + }, + "A6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 68.63, + "z": 6.7 + } + }, + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/hardware-modules/products/aluminum-block-set" + ] + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "A2", + "B2", + "C2", + "D2", + "A3", + "B3", + "C3", + "D3", + "A4", + "B4", + "C4", + "D4", + "A5", + "B5", + "C5", + "D5", + "A6", + "B6", + "C6", + "D6" + ], + "metadata": { + "displayName": "Generic 2 mL Screwcap", + "displayCategory": "tubeRack", + "wellBottomShape": "v" + }, + "brand": { "brand": "generic", "brandId": [], "links": [] } + } + ], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + } + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "0": { + "displayName": "Water", + "description": "", + "displayColor": "#b925ff" + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "key": "34c465b6-567b-4562-af3d-fa7c0ae2463a", + "commandType": "loadPipette", + "params": { + "pipetteName": "p300_single_gen2", + "mount": "left", + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5" + } + }, + { + "key": "1c3d76e3-4b7a-463d-9713-d30ba2f9be98", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons Fixed Trash", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "12" } + } + }, + { + "key": "d118489e-8376-4906-815c-0dbe228790ec", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "loadName": "opentrons_96_tiprack_300ul", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "2" } + } + }, + { + "key": "3a96b3ae-0980-4b03-97bd-3d8bf6ec146c", + "commandType": "loadLabware", + "params": { + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "1" } + } + }, + { + "key": "4f51e840-b2d0-4753-bce8-1f9d7243b04a", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap", + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "loadName": "opentrons_24_aluminumblock_generic_2ml_screwcap", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "3" } + } + }, + { + "commandType": "loadLiquid", + "key": "a200ffe3-2b99-4432-9aca-366dd6d02938", + "params": { + "liquidId": "0", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "volumeByWell": { + "A1": 100, + "B1": 100, + "C1": 100, + "D1": 100, + "E1": 100, + "F1": 100, + "G1": 100, + "H1": 100, + "A2": 100, + "B2": 100, + "C2": 100, + "D2": 100, + "E2": 100, + "F2": 100, + "G2": 100, + "H2": 100 + } + } + }, + { + "commandType": "waitForDuration", + "key": "5c00d76e-1cf3-4ce5-9444-d77fbad34e84", + "params": { "seconds": 62, "message": "" } + }, + { + "commandType": "pickUpTip", + "key": "d60cd221-137c-4272-bfe9-5c2fa0efd83b", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "wellName": "A1" + } + }, + { + "commandType": "aspirate", + "key": "d6e88ec6-9388-45ec-b738-e03715172cdc", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "dispense", + "key": "5715d558-7d18-41e9-ac59-affa60b84a2e", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "aspirate", + "key": "516260d4-87cf-4f97-a85e-e4b91143f251", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "dispense", + "key": "ea891e74-cf28-4a81-a17d-aa2e923bcc12", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "aspirate", + "key": "258c48c2-cf75-4562-9e6d-5ae573e9f477", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 100, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "touchTip", + "key": "2832e22f-4516-4fa0-9346-53443faa1326", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 12.8 } } + } + }, + { + "commandType": "dispense", + "key": "b0577ca1-79bb-4959-9a2c-3bc3164e7078", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 40, + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "touchTip", + "key": "ab515880-dbc3-4978-a0e1-d74c89fd5b62", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 40 } } + } + }, + { + "commandType": "dispense", + "key": "2fcc9508-c0d6-4ffe-aa95-5ce5c4062aca", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 40, + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "wellName": "A2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "touchTip", + "key": "1757c703-09b4-4576-ba3f-a30150464a11", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "wellName": "A2", + "wellLocation": { "origin": "bottom", "offset": { "z": 40 } } + } + }, + { + "commandType": "blowout", + "key": "5369e38c-b482-4aa3-9bb3-5736a0f80a24", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 46.43, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "d673d107-80a7-4367-a566-92285b3a9e27", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "waitForResume", + "key": "14178816-28cd-400e-8ef2-766bb0537f68", + "params": { "message": "Wait until user intervention" } + }, + { + "commandType": "pickUpTip", + "key": "eb2c9c4c-731f-4c5f-92a4-1dd67edb43e0", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "key": "609bf9a5-c94a-4f90-bf25-28af2a21bd17", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 40 + } + }, + { + "commandType": "dispense", + "key": "dfcf64ef-d71d-414e-8d3f-92fd59a57fe9", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 35 + } + }, + { + "commandType": "aspirate", + "key": "add3104c-71b3-461c-9250-f6f089760085", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 40 + } + }, + { + "commandType": "dispense", + "key": "81793def-491e-4dc6-8e78-e00d8e93cc0f", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 35 + } + }, + { + "commandType": "aspirate", + "key": "8d25f9d8-2b6c-4f80-a652-9399bc5af637", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 40 + } + }, + { + "commandType": "dispense", + "key": "1cd3aaba-1165-42ff-91e8-45cf5d9969f6", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 35 + } + }, + { + "commandType": "blowout", + "key": "344d8868-2e47-482b-9520-e6f1790ad3cc", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 35, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "touchTip", + "key": "4b3071a2-5120-494f-8f7e-4cbea6d0fc90", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "D2", + "wellLocation": { "origin": "bottom", "offset": { "z": 11.8 } } + } + }, + { + "commandType": "dropTip", + "key": "a7f80136-b151-4ba3-b3c1-79fb21fd977a", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "1c9c3d91-1fd4-46e5-a416-81515f351e41", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "wellName": "C1" + } + }, + { + "commandType": "aspirate", + "key": "9320e70d-f2b1-41ea-85df-809f72245d4f", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 40 + } + }, + { + "commandType": "dispense", + "key": "d84de4b2-ebab-4740-8602-6fccfc2f7057", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 35 + } + }, + { + "commandType": "aspirate", + "key": "58cd9595-97a4-4423-a348-c8892badb6a8", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 40 + } + }, + { + "commandType": "dispense", + "key": "ee9ec831-643f-4024-9b49-c51b6e7276bf", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 35 + } + }, + { + "commandType": "aspirate", + "key": "8bc7c0d2-b566-4a96-8c76-f6b130d68f4f", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 40 + } + }, + { + "commandType": "dispense", + "key": "e5afcb2f-d7bb-46b9-af9b-b5a4880ce371", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 35, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 35 + } + }, + { + "commandType": "blowout", + "key": "f7c27507-6442-48f4-b632-e4c27cfd3449", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 35, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "touchTip", + "key": "439d2f90-7825-4503-8015-4b5b5e57bce6", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "E2", + "wellLocation": { "origin": "bottom", "offset": { "z": 11.8 } } + } + }, + { + "commandType": "dropTip", + "key": "4b999639-2e8d-4ce9-9828-50dce0939a3a", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "85358485-aa9b-4518-8ca9-9b74c5c0beac:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [] +} diff --git a/protocol-designer/fixtures/protocol/8/doItAllV4MigratedToV8.json b/protocol-designer/fixtures/protocol/8/doItAllV4MigratedToV8.json new file mode 100644 index 00000000000..29cf0f3eebd --- /dev/null +++ b/protocol-designer/fixtures/protocol/8/doItAllV4MigratedToV8.json @@ -0,0 +1,2828 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Do it all v4", + "author": "Fixture", + "description": "Test all v4 commands", + "created": 1585930833548, + "lastModified": 1698855792001, + "category": null, + "subcategory": null, + "tags": [] + }, + "designerApplication": { + "name": "opentrons/protocol-designer", + "version": "8.0.0", + "data": { + "_internalAppBuildDate": "Wed, 01 Nov 2023 16:22:39 GMT", + "defaultValues": { + "aspirate_mmFromBottom": 1, + "dispense_mmFromBottom": 0.5, + "touchTip_mmFromTop": -1, + "blowout_mmFromTop": 0 + }, + "pipetteTiprackAssignments": { + "0b3f2210-75c7-11ea-b42f-4b64e50f43e5": "opentrons/opentrons_96_tiprack_300ul/1" + }, + "dismissedWarnings": { "form": {}, "timeline": {} }, + "ingredients": { + "0": { + "name": "Water", + "description": null, + "serialize": false, + "liquidGroupId": "0" + } + }, + "ingredLocations": { + "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1": { + "A1": { "0": { "volume": 100 } }, + "B1": { "0": { "volume": 100 } }, + "C1": { "0": { "volume": 100 } }, + "D1": { "0": { "volume": 100 } }, + "E1": { "0": { "volume": 100 } }, + "F1": { "0": { "volume": 100 } }, + "G1": { "0": { "volume": 100 } }, + "H1": { "0": { "volume": 100 } }, + "A2": { "0": { "volume": 100 } }, + "B2": { "0": { "volume": 100 } }, + "C2": { "0": { "volume": 100 } }, + "D2": { "0": { "volume": 100 } }, + "E2": { "0": { "volume": 100 } }, + "F2": { "0": { "volume": 100 } }, + "G2": { "0": { "volume": 100 } }, + "H2": { "0": { "volume": 100 } } + } + }, + "savedStepForms": { + "__INITIAL_DECK_SETUP_STEP__": { + "labwareLocationUpdate": { + "f59af982-7eeb-4c76-bb74-6b167b77964b:opentrons/opentrons_1_trash_1100ml_fixed/1": "12", + "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1": "2", + "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType", + "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType" + }, + "pipetteLocationUpdate": { + "0b3f2210-75c7-11ea-b42f-4b64e50f43e5": "left" + }, + "moduleLocationUpdate": { + "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType": "1", + "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType": "3" + }, + "stepType": "manualIntervention", + "id": "__INITIAL_DECK_SETUP_STEP__" + }, + "2e48b500-75c7-11ea-b42f-4b64e50f43e5": { + "moduleId": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType", + "magnetAction": "engage", + "engageHeight": "6", + "id": "2e48b500-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "magnet", + "stepName": "magnet", + "stepDetails": "" + }, + "3153f930-75c7-11ea-b42f-4b64e50f43e5": { + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "setTemperature": "true", + "targetTemperature": "25", + "id": "3153f930-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "temperature", + "stepName": "temperature", + "stepDetails": "" + }, + "364a4480-75c7-11ea-b42f-4b64e50f43e5": { + "pauseAction": "untilTemperature", + "pauseHour": null, + "pauseMinute": null, + "pauseSecond": null, + "pauseMessage": "", + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "pauseTemperature": "25", + "id": "364a4480-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "3961e4c0-75c7-11ea-b42f-4b64e50f43e5": { + "pipette": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": "30", + "changeTip": "always", + "path": "single", + "aspirate_wells_grouped": false, + "aspirate_flowRate": null, + "aspirate_labware": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "aspirate_wells": ["A1", "B1"], + "aspirate_wellOrder_first": "t2b", + "aspirate_wellOrder_second": "l2r", + "aspirate_mix_checkbox": false, + "aspirate_mix_times": null, + "aspirate_mix_volume": null, + "aspirate_mmFromBottom": 1, + "aspirate_touchTip_checkbox": false, + "aspirate_touchTip_mmFromBottom": null, + "dispense_flowRate": null, + "dispense_labware": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "dispense_wells": ["A1"], + "dispense_wellOrder_first": "t2b", + "dispense_wellOrder_second": "l2r", + "dispense_mix_checkbox": false, + "dispense_mix_times": null, + "dispense_mix_volume": null, + "dispense_mmFromBottom": 0.5, + "dispense_touchTip_checkbox": false, + "dispense_touchTip_mmFromBottom": null, + "disposalVolume_checkbox": true, + "disposalVolume_volume": "20", + "blowout_checkbox": false, + "blowout_location": "f59af982-7eeb-4c76-bb74-6b167b77964b:opentrons/opentrons_1_trash_1100ml_fixed/1", + "preWetTip": false, + "aspirate_airGap_checkbox": false, + "aspirate_airGap_volume": null, + "aspirate_delay_checkbox": false, + "aspirate_delay_mmFromBottom": "1", + "aspirate_delay_seconds": "1", + "dispense_airGap_checkbox": false, + "dispense_airGap_volume": null, + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "dispense_delay_mmFromBottom": "0.5", + "dropTip_location": "f59af982-7eeb-4c76-bb74-6b167b77964b:opentrons/opentrons_1_trash_1100ml_fixed/1", + "id": "3961e4c0-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "moveLiquid", + "stepName": "transfer", + "stepDetails": "" + }, + "4f4057e0-75c7-11ea-b42f-4b64e50f43e5": { + "moduleId": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType", + "magnetAction": "disengage", + "engageHeight": "6", + "id": "4f4057e0-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "magnet", + "stepName": "magnet", + "stepDetails": "" + }, + "54dc3200-75c7-11ea-b42f-4b64e50f43e5": { + "pauseAction": "untilResume", + "pauseHour": null, + "pauseMinute": null, + "pauseSecond": null, + "pauseMessage": "Wait until user intervention", + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "pauseTemperature": null, + "id": "54dc3200-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "5db07ad0-75c7-11ea-b42f-4b64e50f43e5": { + "pauseAction": "untilTime", + "pauseHour": null, + "pauseMinute": "1", + "pauseSecond": "2", + "pauseMessage": "", + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "pauseTemperature": null, + "id": "5db07ad0-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "80c00130-75c7-11ea-b42f-4b64e50f43e5": { + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "setTemperature": "false", + "targetTemperature": null, + "id": "80c00130-75c7-11ea-b42f-4b64e50f43e5", + "stepType": "temperature", + "stepName": "temperature", + "stepDetails": "" + } + }, + "orderedStepIds": [ + "2e48b500-75c7-11ea-b42f-4b64e50f43e5", + "3153f930-75c7-11ea-b42f-4b64e50f43e5", + "5db07ad0-75c7-11ea-b42f-4b64e50f43e5", + "3961e4c0-75c7-11ea-b42f-4b64e50f43e5", + "364a4480-75c7-11ea-b42f-4b64e50f43e5", + "4f4057e0-75c7-11ea-b42f-4b64e50f43e5", + "54dc3200-75c7-11ea-b42f-4b64e50f43e5", + "80c00130-75c7-11ea-b42f-4b64e50f43e5" + ] + } + }, + "robot": { "model": "OT-2 Standard", "deckId": "ot2_standard" }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_96_tiprack_300ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "wells": { + "A1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "shape": "circular", + "diameter": 5.23, + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 59.3, + "tipOverlap": 7.47, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_300ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "NEST", + "brandId": ["402501"], + "links": ["https://www.nest-biotech.com/pcr-plates/58773587.html"] + }, + "metadata": { + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "wells": { + "A1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + } + }, + "groups": [ + { + "metadata": { "wellBottomShape": "v" }, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": false, + "isMagneticModuleCompatible": true, + "magneticModuleEngageHeight": 20, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1": { + "ordering": [ + ["A1", "B1", "C1", "D1"], + ["A2", "B2", "C2", "D2"], + ["A3", "B3", "C3", "D3"], + ["A4", "B4", "C4", "D4"], + ["A5", "B5", "C5", "D5"], + ["A6", "B6", "C6", "D6"] + ], + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "metadata": { + "displayName": "Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap", + "displayVolumeUnits": "mL", + "displayCategory": "aluminumBlock", + "tags": [] + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 42 + }, + "parameters": { + "format": "irregular", + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_24_aluminumblock_generic_2ml_screwcap" + }, + "wells": { + "D1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 16.88, + "z": 6.7 + }, + "C1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 34.13, + "z": 6.7 + }, + "B1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 51.38, + "z": 6.7 + }, + "A1": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 20.75, + "y": 68.63, + "z": 6.7 + }, + "D2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 16.88, + "z": 6.7 + }, + "C2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 34.13, + "z": 6.7 + }, + "B2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 51.38, + "z": 6.7 + }, + "A2": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 38, + "y": 68.63, + "z": 6.7 + }, + "D3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 16.88, + "z": 6.7 + }, + "C3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 34.13, + "z": 6.7 + }, + "B3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 51.38, + "z": 6.7 + }, + "A3": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 55.25, + "y": 68.63, + "z": 6.7 + }, + "D4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 16.88, + "z": 6.7 + }, + "C4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 34.13, + "z": 6.7 + }, + "B4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 51.38, + "z": 6.7 + }, + "A4": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 72.5, + "y": 68.63, + "z": 6.7 + }, + "D5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 16.88, + "z": 6.7 + }, + "C5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 34.13, + "z": 6.7 + }, + "B5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 51.38, + "z": 6.7 + }, + "A5": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 89.75, + "y": 68.63, + "z": 6.7 + }, + "D6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 16.88, + "z": 6.7 + }, + "C6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 34.13, + "z": 6.7 + }, + "B6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 51.38, + "z": 6.7 + }, + "A6": { + "shape": "circular", + "depth": 42, + "diameter": 8.5, + "totalLiquidVolume": 2000, + "x": 107, + "y": 68.63, + "z": 6.7 + } + }, + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/hardware-modules/products/aluminum-block-set" + ] + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "A2", + "B2", + "C2", + "D2", + "A3", + "B3", + "C3", + "D3", + "A4", + "B4", + "C4", + "D4", + "A5", + "B5", + "C5", + "D5", + "A6", + "B6", + "C6", + "D6" + ], + "metadata": { + "displayName": "Generic 2 mL Screwcap", + "displayCategory": "tubeRack", + "wellBottomShape": "v" + }, + "brand": { "brand": "generic", "brandId": [], "links": [] } + } + ], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": [ + "fixedTrash", + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 0, + "x": 82.84, + "y": 80, + "z": 82 + } + }, + "brand": { "brand": "Opentrons" }, + "groups": [{ "wells": ["A1"], "metadata": {} }], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + } + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "0": { + "displayName": "Water", + "description": "", + "displayColor": "#b925ff" + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "key": "5c6ea1c3-3aa7-470d-b8d6-7c164ef9f5cf", + "commandType": "loadPipette", + "params": { + "pipetteName": "p300_single_gen2", + "mount": "left", + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5" + } + }, + { + "key": "3b9eb9d7-cbd9-4ed2-b53b-4ead223db8cd", + "commandType": "loadModule", + "params": { + "model": "magneticModuleV2", + "location": { "slotName": "1" }, + "moduleId": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType" + } + }, + { + "key": "0ac500bf-fb0b-4ac4-99fb-82cc168da65b", + "commandType": "loadModule", + "params": { + "model": "temperatureModuleV2", + "location": { "slotName": "3" }, + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType" + } + }, + { + "key": "27493f0c-fdcc-4f10-9134-6a36b91bde05", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons Fixed Trash", + "labwareId": "f59af982-7eeb-4c76-bb74-6b167b77964b:opentrons/opentrons_1_trash_1100ml_fixed/1", + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "12" } + } + }, + { + "key": "63c19761-39b0-45b8-b1cb-df1ea99bf8d4", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "loadName": "opentrons_96_tiprack_300ul", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "2" } + } + }, + { + "key": "446395ee-4830-41c1-bf6b-a8c0a5541839", + "commandType": "loadLabware", + "params": { + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType" + } + } + }, + { + "key": "65d4c36b-f444-4592-9de3-d9ee19e5ad6d", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons 24 Well Aluminum Block with Generic 2 mL Screwcap", + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "loadName": "opentrons_24_aluminumblock_generic_2ml_screwcap", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType" + } + } + }, + { + "commandType": "loadLiquid", + "key": "c0faf39a-503e-4873-9a04-9d2692a6dd1f", + "params": { + "liquidId": "0", + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "volumeByWell": { + "A1": 100, + "B1": 100, + "C1": 100, + "D1": 100, + "E1": 100, + "F1": 100, + "G1": 100, + "H1": 100, + "A2": 100, + "B2": 100, + "C2": 100, + "D2": 100, + "E2": 100, + "F2": 100, + "G2": 100, + "H2": 100 + } + } + }, + { + "commandType": "magneticModule/engage", + "key": "5c2495b3-a9c2-442b-9427-ecc7572045f0", + "params": { + "moduleId": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType", + "height": 6 + } + }, + { + "commandType": "temperatureModule/setTargetTemperature", + "key": "1af4a020-cdff-4c95-8dbd-3b336d5ae083", + "params": { + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "celsius": 25 + } + }, + { + "commandType": "waitForDuration", + "key": "7eb56717-d65d-4411-84e3-6cd91fd5a36d", + "params": { "seconds": 62, "message": "" } + }, + { + "commandType": "pickUpTip", + "key": "4537f511-0df4-4b9b-ad8b-d73a1574cc71", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "wellName": "A1" + } + }, + { + "commandType": "aspirate", + "key": "27de7cf3-6e93-453c-96c2-cb2307641f6b", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "dispense", + "key": "258f9f85-6bb8-43df-8e1f-25d87146ebd8", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "dropTip", + "key": "e014c1cd-a0f6-432e-9289-e3404a0250d1", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "f59af982-7eeb-4c76-bb74-6b167b77964b:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "c25caf0a-9d08-4048-8292-bdd49b2fa3f7", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "0b44c760-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_96_tiprack_300ul/1", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "key": "350bfb92-6802-4292-b3cd-74d5389ba706", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "1e610d40-75c7-11ea-b42f-4b64e50f43e5:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/1", + "wellName": "B1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "dispense", + "key": "a0ce98ea-9aae-45f6-8414-a01af9fd287d", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "volume": 30, + "labwareId": "21ed8f60-75c7-11ea-b42f-4b64e50f43e5:opentrons/opentrons_24_aluminumblock_generic_2ml_screwcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 46.43 + } + }, + { + "commandType": "dropTip", + "key": "1422b491-a35f-45fa-b735-0762ae4ef8f6", + "params": { + "pipetteId": "0b3f2210-75c7-11ea-b42f-4b64e50f43e5", + "labwareId": "f59af982-7eeb-4c76-bb74-6b167b77964b:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "temperatureModule/waitForTemperature", + "key": "f686fd82-cb95-4d3f-8c02-b565f03140f7", + "params": { + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType", + "celsius": 25 + } + }, + { + "commandType": "magneticModule/disengage", + "key": "b15307d2-33a0-4d4b-887f-ebc9a1569eb4", + "params": { + "moduleId": "0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType" + } + }, + { + "commandType": "waitForResume", + "key": "2a30ba22-fb2d-4e1c-931e-f73fbec4eefd", + "params": { "message": "Wait until user intervention" } + }, + { + "commandType": "temperatureModule/deactivate", + "key": "66919698-0193-4930-8fba-eebec3291b76", + "params": { + "moduleId": "0b4319b0-75c7-11ea-b42f-4b64e50f43e5:temperatureModuleType" + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [] +} diff --git a/protocol-designer/fixtures/protocol/8/doItAllV7MigratedToV8.json b/protocol-designer/fixtures/protocol/8/doItAllV7MigratedToV8.json new file mode 100644 index 00000000000..964aa02a231 --- /dev/null +++ b/protocol-designer/fixtures/protocol/8/doItAllV7MigratedToV8.json @@ -0,0 +1,4863 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Flex protocol do it all", + "author": "", + "description": "", + "created": 1689346890165, + "lastModified": 1698852165996, + "category": null, + "subcategory": null, + "tags": [] + }, + "designerApplication": { + "name": "opentrons/protocol-designer", + "version": "8.0.0", + "data": { + "_internalAppBuildDate": "Wed, 01 Nov 2023 15:21:53 GMT", + "defaultValues": { + "aspirate_mmFromBottom": 1, + "dispense_mmFromBottom": 0.5, + "touchTip_mmFromTop": -1, + "blowout_mmFromTop": 0 + }, + "pipetteTiprackAssignments": { + "2e7c6344-58ab-465c-b542-489883cb63fe": "opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "6d1e53c3-2db3-451b-ad60-3fe13781a193": "opentrons/opentrons_flex_96_filtertiprack_50ul/1" + }, + "dismissedWarnings": { "form": {}, "timeline": {} }, + "ingredients": { + "0": { + "name": "Water", + "displayColor": "#b925ff", + "description": null, + "serialize": false, + "liquidGroupId": "0" + }, + "1": { + "name": "Samples", + "displayColor": "#ffd600", + "description": null, + "serialize": false, + "liquidGroupId": "1" + } + }, + "ingredLocations": { + "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2": { + "A1": { "0": { "volume": 100 } }, + "B1": { "0": { "volume": 100 } }, + "C1": { "0": { "volume": 100 } }, + "D1": { "0": { "volume": 100 } }, + "E1": { "0": { "volume": 100 } }, + "F1": { "0": { "volume": 100 } }, + "G1": { "0": { "volume": 100 } }, + "H1": { "0": { "volume": 100 } } + }, + "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1": { + "A1": { "1": { "volume": 1000 } } + } + }, + "savedStepForms": { + "__INITIAL_DECK_SETUP_STEP__": { + "labwareLocationUpdate": { + "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1": "A3", + "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1": "C1", + "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType", + "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType", + "d95bb3be-b453-457c-a947-bd03dc8e56b9:opentrons/opentrons_96_flat_bottom_adapter/1": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "239ceac8-23ec-4900-810a-70aeef880273:opentrons/nest_96_wellplate_200ul_flat/2": "d95bb3be-b453-457c-a947-bd03dc8e56b9:opentrons/opentrons_96_flat_bottom_adapter/1" + }, + "pipetteLocationUpdate": { + "2e7c6344-58ab-465c-b542-489883cb63fe": "left", + "6d1e53c3-2db3-451b-ad60-3fe13781a193": "right" + }, + "moduleLocationUpdate": { + "1be16305-74e7-4bdb-9737-61ec726d2b44:magneticBlockType": "D2", + "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType": "D1", + "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType": "D3", + "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType": "B1" + }, + "stepType": "manualIntervention", + "id": "__INITIAL_DECK_SETUP_STEP__" + }, + "236ab92a-24e3-42d8-bad5-cd4cdae9a4c4": { + "thermocyclerFormType": "thermocyclerProfile", + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType", + "blockIsActive": false, + "blockTargetTemp": null, + "lidIsActive": false, + "lidTargetTemp": null, + "lidOpen": false, + "profileVolume": "10", + "profileTargetLidTemp": "40", + "orderedProfileItems": [ + "40e2cf7d-112f-4ded-9375-0e225cc776eb", + "f4aa883f-41e6-42fb-875f-d4adf02fd2c5" + ], + "profileItemsById": { + "40e2cf7d-112f-4ded-9375-0e225cc776eb": { + "type": "profileStep", + "id": "40e2cf7d-112f-4ded-9375-0e225cc776eb", + "title": "tagmentation", + "temperature": "4", + "durationMinutes": "1", + "durationSeconds": "" + }, + "f4aa883f-41e6-42fb-875f-d4adf02fd2c5": { + "type": "profileStep", + "id": "f4aa883f-41e6-42fb-875f-d4adf02fd2c5", + "title": "hold", + "temperature": "10", + "durationMinutes": "2", + "durationSeconds": "" + } + }, + "blockIsActiveHold": false, + "blockTargetTempHold": null, + "lidIsActiveHold": false, + "lidTargetTempHold": null, + "lidOpenHold": null, + "id": "236ab92a-24e3-42d8-bad5-cd4cdae9a4c4", + "stepType": "thermocycler", + "stepName": "thermocycler", + "stepDetails": "" + }, + "2d5ee9a5-c405-4dbc-a57e-2603d709c03d": { + "thermocyclerFormType": "thermocyclerState", + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType", + "blockIsActive": false, + "blockTargetTemp": null, + "lidIsActive": false, + "lidTargetTemp": null, + "lidOpen": true, + "profileVolume": null, + "profileTargetLidTemp": null, + "orderedProfileItems": [], + "profileItemsById": {}, + "blockIsActiveHold": false, + "blockTargetTempHold": null, + "lidIsActiveHold": false, + "lidTargetTempHold": null, + "lidOpenHold": null, + "id": "2d5ee9a5-c405-4dbc-a57e-2603d709c03d", + "stepType": "thermocycler", + "stepName": "thermocycler", + "stepDetails": "" + }, + "f9a294f1-f42b-4cae-893a-592405349d56": { + "pipette": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": "100", + "changeTip": "always", + "path": "single", + "aspirate_wells_grouped": false, + "aspirate_flowRate": null, + "aspirate_labware": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "aspirate_wells": ["A1"], + "aspirate_wellOrder_first": "t2b", + "aspirate_wellOrder_second": "l2r", + "aspirate_mix_checkbox": false, + "aspirate_mix_times": null, + "aspirate_mix_volume": null, + "aspirate_mmFromBottom": null, + "aspirate_touchTip_checkbox": false, + "aspirate_touchTip_mmFromBottom": null, + "dispense_flowRate": null, + "dispense_labware": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "dispense_wells": ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + "dispense_wellOrder_first": "t2b", + "dispense_wellOrder_second": "l2r", + "dispense_mix_checkbox": false, + "dispense_mix_times": null, + "dispense_mix_volume": null, + "dispense_mmFromBottom": null, + "dispense_touchTip_checkbox": false, + "dispense_touchTip_mmFromBottom": null, + "disposalVolume_checkbox": true, + "disposalVolume_volume": "100", + "blowout_checkbox": false, + "blowout_location": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "preWetTip": false, + "aspirate_airGap_checkbox": false, + "aspirate_airGap_volume": "0", + "aspirate_delay_checkbox": false, + "aspirate_delay_mmFromBottom": null, + "aspirate_delay_seconds": "1", + "dispense_airGap_checkbox": false, + "dispense_airGap_volume": "50", + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "dispense_delay_mmFromBottom": null, + "dropTip_location": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "id": "f9a294f1-f42b-4cae-893a-592405349d56", + "stepType": "moveLiquid", + "stepName": "transfer", + "stepDetails": "" + }, + "5fdb9a12-fab4-42fd-886f-40af107b15d6": { + "times": "2", + "changeTip": "always", + "labware": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "mix_wellOrder_first": "t2b", + "mix_wellOrder_second": "l2r", + "blowout_checkbox": false, + "blowout_location": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "mix_mmFromBottom": 0.5, + "pipette": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "volume": "10", + "wells": ["A1"], + "aspirate_flowRate": null, + "dispense_flowRate": null, + "aspirate_delay_checkbox": false, + "aspirate_delay_seconds": "1", + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "mix_touchTip_checkbox": false, + "mix_touchTip_mmFromBottom": null, + "dropTip_location": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "id": "5fdb9a12-fab4-42fd-886f-40af107b15d6", + "stepType": "mix", + "stepName": "mix", + "stepDetails": "" + }, + "3901f6f9-cecd-4d2a-8d85-40d85f9f8b4f": { + "labware": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "useGripper": true, + "newLocation": "B2", + "id": "3901f6f9-cecd-4d2a-8d85-40d85f9f8b4f", + "stepType": "moveLabware", + "stepName": "move labware", + "stepDetails": "" + }, + "e3989707-210d-457f-a9bb-a85b3ef9b59c": { + "pauseAction": "untilTime", + "pauseHour": null, + "pauseMinute": "1", + "pauseSecond": null, + "pauseMessage": "", + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "pauseTemperature": null, + "id": "e3989707-210d-457f-a9bb-a85b3ef9b59c", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "4196ef26-eb2a-4642-83f4-cb5c1f6bdea0": { + "labware": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "useGripper": true, + "newLocation": "C3", + "id": "4196ef26-eb2a-4642-83f4-cb5c1f6bdea0", + "stepType": "moveLabware", + "stepName": "move labware", + "stepDetails": "" + }, + "558d7d58-3280-4373-8e79-26c4242a0c91": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "setHeaterShakerTemperature": null, + "targetHeaterShakerTemperature": null, + "targetSpeed": "500", + "setShake": true, + "latchOpen": false, + "heaterShakerSetTimer": null, + "heaterShakerTimerMinutes": null, + "heaterShakerTimerSeconds": null, + "id": "558d7d58-3280-4373-8e79-26c4242a0c91", + "stepType": "heaterShaker", + "stepName": "heater-shaker", + "stepDetails": "" + }, + "c83b4aaa-1baf-448e-9d76-3c6325874b0f": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "setHeaterShakerTemperature": null, + "targetHeaterShakerTemperature": null, + "targetSpeed": null, + "setShake": null, + "latchOpen": true, + "heaterShakerSetTimer": null, + "heaterShakerTimerMinutes": null, + "heaterShakerTimerSeconds": null, + "id": "c83b4aaa-1baf-448e-9d76-3c6325874b0f", + "stepType": "heaterShaker", + "stepName": "heater-shaker", + "stepDetails": "" + }, + "7747287c-abea-4855-843e-d61b272124b2": { + "labware": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "useGripper": false, + "newLocation": "offDeck", + "id": "7747287c-abea-4855-843e-d61b272124b2", + "stepType": "moveLabware", + "stepName": "move labware", + "stepDetails": "" + }, + "d6db5e5d-98cf-4f19-abbb-2f1406a389c7": { + "moduleId": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType", + "setTemperature": "true", + "targetTemperature": "4", + "id": "d6db5e5d-98cf-4f19-abbb-2f1406a389c7", + "stepType": "temperature", + "stepName": "temperature", + "stepDetails": "" + }, + "8b105da1-63d9-49f1-b370-5c74e01aa188": { + "pauseAction": "untilTemperature", + "pauseHour": null, + "pauseMinute": null, + "pauseSecond": null, + "pauseMessage": "", + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "pauseTemperature": "4", + "id": "8b105da1-63d9-49f1-b370-5c74e01aa188", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "dcc6a6c7-2db8-417b-a1aa-3927abccfadd": { + "moduleId": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType", + "setTemperature": "false", + "targetTemperature": null, + "id": "dcc6a6c7-2db8-417b-a1aa-3927abccfadd", + "stepType": "temperature", + "stepName": "temperature", + "stepDetails": "" + }, + "2f862881-7ce3-4d20-b0ef-53c8244f6ef3": { + "labware": "239ceac8-23ec-4900-810a-70aeef880273:opentrons/nest_96_wellplate_200ul_flat/2", + "useGripper": false, + "newLocation": "C2", + "id": "2f862881-7ce3-4d20-b0ef-53c8244f6ef3", + "stepType": "moveLabware", + "stepName": "move labware", + "stepDetails": "" + } + }, + "orderedStepIds": [ + "d6db5e5d-98cf-4f19-abbb-2f1406a389c7", + "8b105da1-63d9-49f1-b370-5c74e01aa188", + "236ab92a-24e3-42d8-bad5-cd4cdae9a4c4", + "2d5ee9a5-c405-4dbc-a57e-2603d709c03d", + "f9a294f1-f42b-4cae-893a-592405349d56", + "5fdb9a12-fab4-42fd-886f-40af107b15d6", + "3901f6f9-cecd-4d2a-8d85-40d85f9f8b4f", + "e3989707-210d-457f-a9bb-a85b3ef9b59c", + "4196ef26-eb2a-4642-83f4-cb5c1f6bdea0", + "558d7d58-3280-4373-8e79-26c4242a0c91", + "c83b4aaa-1baf-448e-9d76-3c6325874b0f", + "7747287c-abea-4855-843e-d61b272124b2", + "dcc6a6c7-2db8-417b-a1aa-3927abccfadd", + "2f862881-7ce3-4d20-b0ef-53c8244f6ef3" + ] + } + }, + "robot": { "model": "OT-3 Standard", "deckId": "ot3_standard" }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_flex_96_filtertiprack_50ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { "brand": "Opentrons", "brandId": [] }, + "metadata": { + "displayName": "Opentrons Flex 96 Filter Tip Rack 50 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16, + "gripHeightFromLabwareBottom": 23.9, + "wells": { + "A1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 86.38, + "y": 11.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "shape": "circular", + "diameter": 5.58, + "totalLiquidVolume": 50, + "x": 113.38, + "y": 11.38, + "z": 1.5 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "quirks": [], + "isTiprack": true, + "tipLength": 57.9, + "tipOverlap": 10.5, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_flex_96_filtertiprack_50ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 }, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { "x": 0, "y": 0, "z": 121 } + } + }, + "opentrons/opentrons_96_flat_bottom_adapter/1": { + "ordering": [], + "brand": { "brand": "Opentrons", "brandId": [] }, + "metadata": { + "displayName": "Opentrons 96 Flat Bottom Heater-Shaker Adapter", + "displayCategory": "adapter", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { "xDimension": 111, "yDimension": 75, "zDimension": 7.9 }, + "wells": {}, + "groups": [{ "metadata": {}, "wells": [] }], + "parameters": { + "format": "96Standard", + "quirks": [], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_flat_bottom_adapter" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "allowedRoles": ["adapter"], + "cornerOffsetFromSlot": { "x": 8.5, "y": 5.5, "z": 0 } + }, + "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "NEST", + "brandId": ["402501"], + "links": ["https://www.nest-biotech.com/pcr-plates/58773587.html"] + }, + "metadata": { + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15, + "gripHeightFromLabwareBottom": 10.65, + "wells": { + "A1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "shape": "circular", + "diameter": 5.34, + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + } + }, + "groups": [ + { + "metadata": { "wellBottomShape": "v" }, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": false, + "isMagneticModuleCompatible": true, + "magneticModuleEngageHeight": 20, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt" + }, + "namespace": "opentrons", + "version": 2, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 }, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { "x": 0, "y": 0, "z": 10.2 }, + "opentrons_96_well_aluminum_block": { "x": 0, "y": 0, "z": 12.66 } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { "x": 0, "y": 0, "z": 10.8 } + } + }, + "opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1": { + "ordering": [ + ["A1", "B1", "C1", "D1"], + ["A2", "B2", "C2", "D2"], + ["A3", "B3", "C3", "D3"], + ["A4", "B4", "C4", "D4"], + ["A5", "B5", "C5", "D5"], + ["A6", "B6", "C6", "D6"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": ["https://shop.opentrons.com/aluminum-block-set/"] + }, + "metadata": { + "displayName": "Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap", + "displayCategory": "aluminumBlock", + "displayVolumeUnits": "mL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 43.7 + }, + "wells": { + "A1": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 20.75, + "y": 68.62, + "z": 5.8 + }, + "B1": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 20.75, + "y": 51.37, + "z": 5.8 + }, + "C1": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 20.75, + "y": 34.12, + "z": 5.8 + }, + "D1": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 20.75, + "y": 16.87, + "z": 5.8 + }, + "A2": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 38, + "y": 68.62, + "z": 5.8 + }, + "B2": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 38, + "y": 51.37, + "z": 5.8 + }, + "C2": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 38, + "y": 34.12, + "z": 5.8 + }, + "D2": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 38, + "y": 16.87, + "z": 5.8 + }, + "A3": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 55.25, + "y": 68.62, + "z": 5.8 + }, + "B3": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 55.25, + "y": 51.37, + "z": 5.8 + }, + "C3": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 55.25, + "y": 34.12, + "z": 5.8 + }, + "D3": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 55.25, + "y": 16.87, + "z": 5.8 + }, + "A4": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 72.5, + "y": 68.62, + "z": 5.8 + }, + "B4": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 72.5, + "y": 51.37, + "z": 5.8 + }, + "C4": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 72.5, + "y": 34.12, + "z": 5.8 + }, + "D4": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 72.5, + "y": 16.87, + "z": 5.8 + }, + "A5": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 89.75, + "y": 68.62, + "z": 5.8 + }, + "B5": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 89.75, + "y": 51.37, + "z": 5.8 + }, + "C5": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 89.75, + "y": 34.12, + "z": 5.8 + }, + "D5": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 89.75, + "y": 16.87, + "z": 5.8 + }, + "A6": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 107, + "y": 68.62, + "z": 5.8 + }, + "B6": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 107, + "y": 51.37, + "z": 5.8 + }, + "C6": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 107, + "y": 34.12, + "z": 5.8 + }, + "D6": { + "depth": 37.9, + "shape": "circular", + "diameter": 10.2, + "totalLiquidVolume": 1500, + "x": 107, + "y": 16.87, + "z": 5.8 + } + }, + "groups": [ + { + "metadata": { + "displayName": "NEST 24x1.5 mL Snapcap", + "displayCategory": "tubeRack", + "wellBottomShape": "v" + }, + "brand": { + "brand": "NEST", + "brandId": ["615601", "615001"], + "links": [ + "https://www.nest-biotech.com/micro-centrifuge-tube/59201044.html" + ] + }, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "A2", + "B2", + "C2", + "D2", + "A3", + "B3", + "C3", + "D3", + "A4", + "B4", + "C4", + "D4", + "A5", + "B5", + "C5", + "D5", + "A6", + "B6", + "C6", + "D6" + ] + } + ], + "parameters": { + "format": "irregular", + "quirks": ["gripperIncompatible"], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_24_aluminumblock_nest_1.5ml_snapcap" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/nest_96_wellplate_200ul_flat/2": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "NEST", + "brandId": ["701011"], + "links": [ + "https://www.nest-biotech.com/cell-culture-plates/59415537.html" + ] + }, + "metadata": { + "displayName": "NEST 96 Well Plate 200 µL Flat", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.56, + "yDimension": 85.36, + "zDimension": 14.3 + }, + "gripForce": 15, + "gripHeightFromLabwareBottom": 11.8, + "wells": { + "A1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 74.18, + "z": 3.5 + }, + "B1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 65.18, + "z": 3.5 + }, + "C1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 56.18, + "z": 3.5 + }, + "D1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 47.18, + "z": 3.5 + }, + "E1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 38.18, + "z": 3.5 + }, + "F1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 29.18, + "z": 3.5 + }, + "G1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 20.18, + "z": 3.5 + }, + "H1": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 14.28, + "y": 11.18, + "z": 3.5 + }, + "A2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 74.18, + "z": 3.5 + }, + "B2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 65.18, + "z": 3.5 + }, + "C2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 56.18, + "z": 3.5 + }, + "D2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 47.18, + "z": 3.5 + }, + "E2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 38.18, + "z": 3.5 + }, + "F2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 29.18, + "z": 3.5 + }, + "G2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 20.18, + "z": 3.5 + }, + "H2": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 23.28, + "y": 11.18, + "z": 3.5 + }, + "A3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 74.18, + "z": 3.5 + }, + "B3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 65.18, + "z": 3.5 + }, + "C3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 56.18, + "z": 3.5 + }, + "D3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 47.18, + "z": 3.5 + }, + "E3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 38.18, + "z": 3.5 + }, + "F3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 29.18, + "z": 3.5 + }, + "G3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 20.18, + "z": 3.5 + }, + "H3": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 32.28, + "y": 11.18, + "z": 3.5 + }, + "A4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 74.18, + "z": 3.5 + }, + "B4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 65.18, + "z": 3.5 + }, + "C4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 56.18, + "z": 3.5 + }, + "D4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 47.18, + "z": 3.5 + }, + "E4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 38.18, + "z": 3.5 + }, + "F4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 29.18, + "z": 3.5 + }, + "G4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 20.18, + "z": 3.5 + }, + "H4": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 41.28, + "y": 11.18, + "z": 3.5 + }, + "A5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 74.18, + "z": 3.5 + }, + "B5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 65.18, + "z": 3.5 + }, + "C5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 56.18, + "z": 3.5 + }, + "D5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 47.18, + "z": 3.5 + }, + "E5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 38.18, + "z": 3.5 + }, + "F5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 29.18, + "z": 3.5 + }, + "G5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 20.18, + "z": 3.5 + }, + "H5": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 50.28, + "y": 11.18, + "z": 3.5 + }, + "A6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 74.18, + "z": 3.5 + }, + "B6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 65.18, + "z": 3.5 + }, + "C6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 56.18, + "z": 3.5 + }, + "D6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 47.18, + "z": 3.5 + }, + "E6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 38.18, + "z": 3.5 + }, + "F6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 29.18, + "z": 3.5 + }, + "G6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 20.18, + "z": 3.5 + }, + "H6": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 59.28, + "y": 11.18, + "z": 3.5 + }, + "A7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 74.18, + "z": 3.5 + }, + "B7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 65.18, + "z": 3.5 + }, + "C7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 56.18, + "z": 3.5 + }, + "D7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 47.18, + "z": 3.5 + }, + "E7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 38.18, + "z": 3.5 + }, + "F7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 29.18, + "z": 3.5 + }, + "G7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 20.18, + "z": 3.5 + }, + "H7": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 68.28, + "y": 11.18, + "z": 3.5 + }, + "A8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 74.18, + "z": 3.5 + }, + "B8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 65.18, + "z": 3.5 + }, + "C8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 56.18, + "z": 3.5 + }, + "D8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 47.18, + "z": 3.5 + }, + "E8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 38.18, + "z": 3.5 + }, + "F8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 29.18, + "z": 3.5 + }, + "G8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 20.18, + "z": 3.5 + }, + "H8": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 77.28, + "y": 11.18, + "z": 3.5 + }, + "A9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 74.18, + "z": 3.5 + }, + "B9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 65.18, + "z": 3.5 + }, + "C9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 56.18, + "z": 3.5 + }, + "D9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 47.18, + "z": 3.5 + }, + "E9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 38.18, + "z": 3.5 + }, + "F9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 29.18, + "z": 3.5 + }, + "G9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 20.18, + "z": 3.5 + }, + "H9": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 86.28, + "y": 11.18, + "z": 3.5 + }, + "A10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 74.18, + "z": 3.5 + }, + "B10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 65.18, + "z": 3.5 + }, + "C10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 56.18, + "z": 3.5 + }, + "D10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 47.18, + "z": 3.5 + }, + "E10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 38.18, + "z": 3.5 + }, + "F10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 29.18, + "z": 3.5 + }, + "G10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 20.18, + "z": 3.5 + }, + "H10": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 95.28, + "y": 11.18, + "z": 3.5 + }, + "A11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 74.18, + "z": 3.5 + }, + "B11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 65.18, + "z": 3.5 + }, + "C11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 56.18, + "z": 3.5 + }, + "D11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 47.18, + "z": 3.5 + }, + "E11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 38.18, + "z": 3.5 + }, + "F11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 29.18, + "z": 3.5 + }, + "G11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 20.18, + "z": 3.5 + }, + "H11": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 104.28, + "y": 11.18, + "z": 3.5 + }, + "A12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 74.18, + "z": 3.5 + }, + "B12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 65.18, + "z": 3.5 + }, + "C12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 56.18, + "z": 3.5 + }, + "D12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 47.18, + "z": 3.5 + }, + "E12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 38.18, + "z": 3.5 + }, + "F12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 29.18, + "z": 3.5 + }, + "G12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 20.18, + "z": 3.5 + }, + "H12": { + "depth": 10.8, + "shape": "circular", + "diameter": 6.85, + "totalLiquidVolume": 200, + "x": 113.28, + "y": 11.18, + "z": 3.5 + } + }, + "groups": [ + { + "metadata": { "wellBottomShape": "flat" }, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "nest_96_wellplate_200ul_flat" + }, + "namespace": "opentrons", + "version": 2, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 }, + "stackingOffsetWithLabware": { + "opentrons_96_flat_bottom_adapter": { "x": 0, "y": 0, "z": 6.7 }, + "opentrons_aluminum_flat_bottom_plate": { "x": 0, "y": 0, "z": 5.55 } + } + }, + "opentrons/opentrons_1_trash_3200ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 246.5, + "yDimension": 91.5, + "zDimension": 40 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_3200ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": [ + "fixedTrash", + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 78, + "xDimension": 225, + "totalLiquidVolume": 3200000, + "depth": 40, + "x": 123.25, + "y": 45.75, + "z": 0 + } + }, + "brand": { "brand": "Opentrons" }, + "groups": [{ "wells": ["A1"], "metadata": {} }], + "cornerOffsetFromSlot": { "x": -17, "y": -2.75, "z": 0 } + } + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "0": { + "displayName": "Water", + "description": "", + "displayColor": "#b925ff" + }, + "1": { + "displayName": "Samples", + "description": "", + "displayColor": "#ffd600" + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "key": "b9cc8c24-7d6c-49a2-af98-1119d9e7bd3c", + "commandType": "loadPipette", + "params": { + "pipetteName": "p1000_single_flex", + "mount": "left", + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe" + } + }, + { + "key": "c1b8ae3f-4622-4ffe-ba29-e3e4a59e2f74", + "commandType": "loadPipette", + "params": { + "pipetteName": "p50_multi_flex", + "mount": "right", + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193" + } + }, + { + "key": "ef757909-cbfb-4235-be10-c167e8f8d2c4", + "commandType": "loadModule", + "params": { + "model": "magneticBlockV1", + "location": { "slotName": "D2" }, + "moduleId": "1be16305-74e7-4bdb-9737-61ec726d2b44:magneticBlockType" + } + }, + { + "key": "bd526e67-aaa7-410d-aaf0-e9229f97de72", + "commandType": "loadModule", + "params": { + "model": "heaterShakerModuleV1", + "location": { "slotName": "D1" }, + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + }, + { + "key": "f2edc3da-645a-4f3a-9ff6-078e6ae76d7f", + "commandType": "loadModule", + "params": { + "model": "temperatureModuleV2", + "location": { "slotName": "D3" }, + "moduleId": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType" + } + }, + { + "key": "39548c7d-fd77-4822-90f4-249fe89b6521", + "commandType": "loadModule", + "params": { + "model": "thermocyclerModuleV2", + "location": { "slotName": "B1" }, + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + }, + { + "key": "21f403f2-9882-499f-9075-bd8b426773ea", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons 96 Flat Bottom Heater-Shaker Adapter", + "labwareId": "d95bb3be-b453-457c-a947-bd03dc8e56b9:opentrons/opentrons_96_flat_bottom_adapter/1", + "loadName": "opentrons_96_flat_bottom_adapter", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + } + }, + { + "key": "86e96eb7-871b-4c86-a48d-5e46dec38dbb", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons Fixed Trash", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "loadName": "opentrons_1_trash_3200ml_fixed", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "A3" } + } + }, + { + "key": "a0f159b2-99d7-4fa4-8fa8-85224be73485", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons Flex 96 Filter Tip Rack 50 µL", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "loadName": "opentrons_flex_96_filtertiprack_50ul", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "C1" } + } + }, + { + "key": "7b79a157-c6f6-4c83-85b8-5768bed63496", + "commandType": "loadLabware", + "params": { + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 2, + "location": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + } + }, + { + "key": "993c8cc0-4830-406b-bccc-0e98831c0f05", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap", + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "loadName": "opentrons_24_aluminumblock_nest_1.5ml_snapcap", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType" + } + } + }, + { + "key": "355ad6c3-eb8b-402f-9b93-da595b10017c", + "commandType": "loadLabware", + "params": { + "displayName": "NEST 96 Well Plate 200 µL Flat", + "labwareId": "239ceac8-23ec-4900-810a-70aeef880273:opentrons/nest_96_wellplate_200ul_flat/2", + "loadName": "nest_96_wellplate_200ul_flat", + "namespace": "opentrons", + "version": 2, + "location": { + "labwareId": "d95bb3be-b453-457c-a947-bd03dc8e56b9:opentrons/opentrons_96_flat_bottom_adapter/1" + } + } + }, + { + "commandType": "loadLiquid", + "key": "0e8c514a-4e9d-48df-9bbf-f8112e601eb7", + "params": { + "liquidId": "1", + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "volumeByWell": { "A1": 1000 } + } + }, + { + "commandType": "loadLiquid", + "key": "526b5048-4b70-45d2-bf4e-ed7ffc9977d8", + "params": { + "liquidId": "0", + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "volumeByWell": { + "A1": 100, + "B1": 100, + "C1": 100, + "D1": 100, + "E1": 100, + "F1": 100, + "G1": 100, + "H1": 100 + } + } + }, + { + "commandType": "temperatureModule/setTargetTemperature", + "key": "92212e3e-7df6-4677-b136-ff9461f9a49d", + "params": { + "moduleId": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType", + "celsius": 4 + } + }, + { + "commandType": "heaterShaker/waitForTemperature", + "key": "5b443878-88bc-455b-9208-655efa6b8aa4", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "celsius": 4 + } + }, + { + "commandType": "thermocycler/closeLid", + "key": "2d447fcd-043c-4483-b9a6-dbebdfb21124", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + }, + { + "commandType": "thermocycler/setTargetLidTemperature", + "key": "61181be7-36a7-4054-a676-bb9b8999f352", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType", + "celsius": 40 + } + }, + { + "commandType": "thermocycler/waitForLidTemperature", + "key": "4785b542-8282-4c0f-8e92-0e405446d011", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + }, + { + "commandType": "thermocycler/runProfile", + "key": "e364ba5d-f8b7-44b9-beac-5787dbf7951d", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType", + "profile": [ + { "holdSeconds": 60, "celsius": 4 }, + { "holdSeconds": 120, "celsius": 10 } + ], + "blockMaxVolumeUl": 10 + } + }, + { + "commandType": "thermocycler/deactivateBlock", + "key": "dac9356a-398f-4958-8b2a-ed27357547ca", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + }, + { + "commandType": "thermocycler/deactivateLid", + "key": "7dd1c1d4-fc48-4a06-8f53-c72534948d30", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + }, + { + "commandType": "thermocycler/openLid", + "key": "e943f642-aede-452c-8a14-d2382c524b52", + "params": { + "moduleId": "627b7a27-5bb7-46de-a530-67af45652e3b:thermocyclerModuleType" + } + }, + { + "commandType": "pickUpTip", + "key": "d00f7bef-fc8d-4add-85a3-7c871cfdddd6", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "A1" + } + }, + { + "commandType": "aspirate", + "key": "828474eb-41a9-4f3a-869d-1a43a5f7bcec", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "5e085675-4618-47b2-9aec-f28bdfe79663", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "92c6718d-c39f-408f-a212-212d78e87f20", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "42c86bee-88cb-45c1-9766-e79e750b379c", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "key": "947a087b-6e42-49ea-a5ef-ed4936214f0a", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "d43ef33f-5555-41c2-ae09-3717711606c0", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "ad71bf42-c3d2-4877-9ab0-5f72e223a22d", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "f6134895-9683-412b-be88-9bb08580e0d5", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "C1" + } + }, + { + "commandType": "aspirate", + "key": "e806aece-664c-4440-a798-f574e0a15d2d", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "a5de2e6d-b0e9-4bc0-9d2f-c598f38cc446", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "B1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "447e41d4-e9d8-45dd-9ec6-deba306dacd3", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "40139870-8b73-42d5-b74a-498fc4a2a3dc", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "D1" + } + }, + { + "commandType": "aspirate", + "key": "c97ad218-c70d-4ceb-9034-7e038b65f4e1", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "bad80b76-e6cf-4fa4-90cf-14058ab0268e", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "B1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "1eeb8fe5-9981-4a86-a566-b34ed638ac9f", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "590b2fc4-8a00-4a15-a908-a6d694ee277a", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "E1" + } + }, + { + "commandType": "aspirate", + "key": "74963c67-1601-4879-8a18-e50b66f1a52e", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "522b0ccc-2637-4efa-8f21-6bd11ccdf9fa", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "C1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "ec66fe64-fa95-4c0f-8fca-4b8ea46f8634", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "4dc3e4b7-85de-43a7-b9a5-2bef2e76d89e", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "F1" + } + }, + { + "commandType": "aspirate", + "key": "1e8cfecd-f901-4ac6-807f-8839c9d3ffde", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "723ea4fd-dae4-4669-a315-0386d8046959", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "C1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "598b75ff-be77-42a6-98e5-d19cb51ba564", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "bf6f1e06-bc3a-4750-8955-9ba5ed94f6a4", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "G1" + } + }, + { + "commandType": "aspirate", + "key": "df3e0fe0-455f-4b2b-994e-84de594f48ed", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "f9b0f7c1-71ca-4de6-bab1-bad55b9d4b30", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "D1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "5603836d-96e4-4499-b356-8c8c270198ae", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "5d4bf579-ce71-46b8-8f62-32c59459e240", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "H1" + } + }, + { + "commandType": "aspirate", + "key": "aa64f179-4a76-4992-b474-dd5edbb5a957", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "376dccac-4992-43e7-b26e-d2c929c45aaa", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "D1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "0e814e22-a644-45cc-be5c-c832aad124a2", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "06f5d129-d0a7-4222-8427-34e84504892e", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "A2" + } + }, + { + "commandType": "aspirate", + "key": "106dfcfc-16c3-450d-9d4a-7d66e2e9a5b1", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "879fdf97-0dca-4044-8815-44c11d5515c1", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "E1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "b65d5fb9-71b8-40da-8e48-5ead2a8391a4", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "dabf5206-1e08-4bde-a3ca-c45abf9f5382", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "B2" + } + }, + { + "commandType": "aspirate", + "key": "ca39acdb-7bf6-4504-bd82-968431bf0a56", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "d14e3dfa-5ad0-40a5-9014-60cfd99d35b5", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "E1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "2b30bfc6-4f13-42e0-9268-5f1d48f7e033", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "fa7b71d1-40a7-4786-8a75-56b21c5b5c86", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "C2" + } + }, + { + "commandType": "aspirate", + "key": "17878b68-0d68-458e-ad61-c0449e0ccc35", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "2052caf2-819a-41c8-bac0-fa6ef86f38cc", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "f0102cff-1014-4dd2-ae5f-a30b1f601de5", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "d352eb3f-0643-4f3c-afa3-71918e562e4b", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "D2" + } + }, + { + "commandType": "aspirate", + "key": "ebe59d72-d573-4efa-9a47-4e81413ee97c", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "65ea9eae-954b-478d-ac03-35cae87f38dc", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "c47101a2-c07e-4499-98f9-f4376fd2d5f8", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "2ff454a8-db5d-40de-b0da-4a86b13f2e26", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "E2" + } + }, + { + "commandType": "aspirate", + "key": "3381fd66-37d6-4768-b116-cf6e2a69839e", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "8d090c0b-6285-4517-a34a-b60977dd79ea", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "G1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "e630aad0-5871-4f74-9a56-5188b986f5c1", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "a4886849-2fbb-49a4-833a-ec8b51ee0a8d", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "F2" + } + }, + { + "commandType": "aspirate", + "key": "e6d321fe-ca0b-4ef9-a92a-0a3fcc3f5df3", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "d12ec4f7-a4fc-45ec-aa41-dc09d60a5ea5", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "G1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "45ece9e3-5ad7-43df-9087-b86284ab1ed1", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "3d754dc5-2291-41eb-8356-29c39d087971", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "G2" + } + }, + { + "commandType": "aspirate", + "key": "51162cf8-3912-4997-bf94-816d119e60a3", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "7e85c0f2-efca-41dd-99c0-ed5d12e4f61c", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "H1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "af99c99d-ae79-4034-ab74-548a16a35e38", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "8b2005e8-b0d6-40e3-ac23-4ab6ccc64d1c", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "H2" + } + }, + { + "commandType": "aspirate", + "key": "933142ae-c5cb-4e88-91c6-9ced20a8068b", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dispense", + "key": "54e49fb8-c126-43d5-94f3-bccee4b54aa2", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "volume": 50, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "H1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 137.35 + } + }, + { + "commandType": "dropTip", + "key": "68bc1e8b-f8f5-4c6d-a2a5-23c4a3847724", + "params": { + "pipetteId": "2e7c6344-58ab-465c-b542-489883cb63fe", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "02d2d484-8e57-43e9-b5e9-390349edd1d3", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "labwareId": "23ed35de-5bfd-4bb0-8f54-da99a2804ed9:opentrons/opentrons_flex_96_filtertiprack_50ul/1", + "wellName": "A3" + } + }, + { + "commandType": "configureForVolume", + "key": "0586a78f-f735-4321-bd66-773e8ff19c04", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "volume": 10 + } + }, + { + "commandType": "aspirate", + "key": "26b6b498-b67c-44ad-8fb0-2a9456fd5cf9", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "volume": 10, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7.85 + } + }, + { + "commandType": "dispense", + "key": "aaec55f0-eeb5-4d3e-8bd9-ceda51509a53", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "volume": 10, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7.85 + } + }, + { + "commandType": "aspirate", + "key": "c03e2dcf-0d00-4b4f-99f9-9ffa57fe269d", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "volume": 10, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7.85 + } + }, + { + "commandType": "dispense", + "key": "d1b59dbd-211e-4abc-bf19-e5443654ea30", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "volume": 10, + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7.85 + } + }, + { + "commandType": "dropTip", + "key": "e03a8717-26e4-45da-8934-b575e44aa634", + "params": { + "pipetteId": "6d1e53c3-2db3-451b-ad60-3fe13781a193", + "labwareId": "eb672b1d-e7c1-4cfa-ad4c-2a2a8de27813:opentrons/opentrons_1_trash_3200ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "moveLabware", + "key": "f139c2e7-368b-48c4-8e4c-d24d13cd0e15", + "params": { + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "strategy": "usingGripper", + "newLocation": { "slotName": "B2" } + } + }, + { + "commandType": "waitForDuration", + "key": "9e0db352-bc06-4291-b982-b0e9dee498ee", + "params": { "seconds": 60, "message": "" } + }, + { + "commandType": "moveLabware", + "key": "3f636140-0e87-4c8a-a4c8-7d5918694682", + "params": { + "labwareId": "fcba73e7-b88e-438e-963e-f8b9a5de0983:opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "strategy": "usingGripper", + "newLocation": { "slotName": "C3" } + } + }, + { + "commandType": "heaterShaker/closeLabwareLatch", + "key": "172c24c0-a502-4dce-94ca-36d74736e715", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + }, + { + "commandType": "heaterShaker/deactivateHeater", + "key": "f2dc8bda-d9b7-410a-bb6a-e99bc83e1885", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + }, + { + "commandType": "heaterShaker/setAndWaitForShakeSpeed", + "key": "e069573a-9df0-485c-87a3-f7e4f0f87bdd", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType", + "rpm": 500 + } + }, + { + "commandType": "heaterShaker/deactivateHeater", + "key": "d41fc1b4-2bea-493c-ae44-3f7f41f386af", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + }, + { + "commandType": "heaterShaker/deactivateShaker", + "key": "bc3faf1c-0ca0-4bf2-bd16-8245cd52f922", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + }, + { + "commandType": "heaterShaker/openLabwareLatch", + "key": "f50c42c9-ea6c-45a0-b981-66414be43e20", + "params": { + "moduleId": "c19dffa3-cb34-4702-bcf6-dcea786257d1:heaterShakerModuleType" + } + }, + { + "commandType": "moveLabware", + "key": "9e00a1d0-e8fe-487e-9a15-34eda63a3d27", + "params": { + "labwareId": "a793a135-06aa-4ed6-a1d3-c176c7810afa:opentrons/opentrons_24_aluminumblock_nest_1.5ml_snapcap/1", + "strategy": "manualMoveWithPause", + "newLocation": "offDeck" + } + }, + { + "commandType": "temperatureModule/deactivate", + "key": "b5112a35-0b0a-4e34-9eb4-b5d4aa413d12", + "params": { + "moduleId": "ef44ad7f-0fd9-46d6-8bc0-c70785644cc8:temperatureModuleType" + } + }, + { + "commandType": "moveLabware", + "key": "3b732f93-4ef7-47ee-a99c-1d46da87210e", + "params": { + "labwareId": "239ceac8-23ec-4900-810a-70aeef880273:opentrons/nest_96_wellplate_200ul_flat/2", + "strategy": "manualMoveWithPause", + "newLocation": { "slotName": "C2" } + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [] +} diff --git a/protocol-designer/fixtures/protocol/8/example_1_1_0MigratedToV8.json b/protocol-designer/fixtures/protocol/8/example_1_1_0MigratedToV8.json new file mode 100644 index 00000000000..558d88a594b --- /dev/null +++ b/protocol-designer/fixtures/protocol/8/example_1_1_0MigratedToV8.json @@ -0,0 +1,5320 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Some name!", + "author": "Author name", + "description": "Description here", + "created": 1560957631666, + "lastModified": 1698855969857, + "category": null, + "subcategory": null, + "tags": [] + }, + "designerApplication": { + "name": "opentrons/protocol-designer", + "version": "8.0.0", + "data": { + "_internalAppBuildDate": "Wed, 01 Nov 2023 16:22:39 GMT", + "defaultValues": { + "aspirate_mmFromBottom": 1, + "dispense_mmFromBottom": 0.5, + "touchTip_mmFromTop": -1, + "blowout_mmFromTop": 0 + }, + "pipetteTiprackAssignments": { + "c6f45030-92a5-11e9-ac62-1b173f839d9e": "opentrons/opentrons_96_tiprack_10ul/1", + "c6f47740-92a5-11e9-ac62-1b173f839d9e": "opentrons/tipone_96_tiprack_200ul/1" + }, + "dismissedWarnings": { "form": {}, "timeline": {} }, + "ingredients": { + "0": { + "name": "samples", + "description": null, + "serialize": false, + "liquidGroupId": "0" + }, + "1": { + "name": "dna", + "description": null, + "serialize": false, + "liquidGroupId": "1" + } + }, + "ingredLocations": { + "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well": { + "A1": { "0": { "volume": 121 } }, + "B1": { "0": { "volume": 121 } }, + "C1": { "0": { "volume": 121 } }, + "D1": { "0": { "volume": 121 } }, + "E1": { "0": { "volume": 121 } }, + "F1": { "1": { "volume": 44 } }, + "G1": { "1": { "volume": 44 } }, + "H1": { "1": { "volume": 44 } } + } + }, + "savedStepForms": { + "__INITIAL_DECK_SETUP_STEP__": { + "labwareLocationUpdate": { + "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1": "12", + "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul": "1", + "c6f51380-92a5-11e9-ac62-1b173f839d9e:tiprack-200ul": "2", + "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well": "10" + }, + "pipetteLocationUpdate": { + "c6f45030-92a5-11e9-ac62-1b173f839d9e": "left", + "c6f47740-92a5-11e9-ac62-1b173f839d9e": "right" + }, + "moduleLocationUpdate": {}, + "stepType": "manualIntervention", + "id": "__INITIAL_DECK_SETUP_STEP__" + }, + "e7d36200-92a5-11e9-ac62-1b173f839d9e": { + "pipette": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": "6", + "changeTip": "always", + "path": "single", + "aspirate_wells_grouped": false, + "aspirate_flowRate": 0.6, + "aspirate_labware": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "aspirate_wells": ["A1"], + "aspirate_wellOrder_first": "t2b", + "aspirate_wellOrder_second": "l2r", + "aspirate_mix_checkbox": true, + "aspirate_mix_times": 3, + "aspirate_mix_volume": "2", + "aspirate_mmFromBottom": 1, + "aspirate_touchTip_checkbox": true, + "aspirate_touchTip_mmFromBottom": 28.5, + "dispense_flowRate": null, + "dispense_labware": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "dispense_wells": [ + "C6", + "D6", + "E6", + "C7", + "D7", + "E7", + "C8", + "D8", + "E8" + ], + "dispense_wellOrder_first": "b2t", + "dispense_wellOrder_second": "r2l", + "dispense_mix_checkbox": true, + "dispense_mix_times": 2, + "dispense_mix_volume": "3", + "dispense_mmFromBottom": 2.5, + "dispense_touchTip_checkbox": true, + "dispense_touchTip_mmFromBottom": null, + "disposalVolume_checkbox": true, + "disposalVolume_volume": "1", + "blowout_checkbox": true, + "blowout_location": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "preWetTip": false, + "aspirate_airGap_checkbox": false, + "aspirate_airGap_volume": null, + "aspirate_delay_checkbox": false, + "aspirate_delay_mmFromBottom": "1", + "aspirate_delay_seconds": "1", + "dispense_airGap_checkbox": false, + "dispense_airGap_volume": null, + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "dispense_delay_mmFromBottom": "0.5", + "dropTip_location": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "id": "e7d36200-92a5-11e9-ac62-1b173f839d9e", + "stepType": "moveLiquid", + "stepName": "transfer things", + "stepDetails": "yeah notes" + }, + "18113c80-92a6-11e9-ac62-1b173f839d9e": { + "times": 3, + "changeTip": "always", + "labware": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "mix_wellOrder_first": "t2b", + "mix_wellOrder_second": "l2r", + "blowout_checkbox": true, + "blowout_location": "dest_well", + "mix_mmFromBottom": 0.5, + "pipette": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": "5.5", + "wells": ["F1"], + "aspirate_flowRate": 8, + "dispense_flowRate": 7, + "aspirate_delay_checkbox": false, + "aspirate_delay_seconds": "1", + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "mix_touchTip_checkbox": true, + "mix_touchTip_mmFromBottom": 30.5, + "dropTip_location": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "id": "18113c80-92a6-11e9-ac62-1b173f839d9e", + "stepType": "mix", + "stepName": "mix", + "stepDetails": "" + }, + "2e622080-92a6-11e9-ac62-1b173f839d9e": { + "pauseAction": "untilTime", + "pauseHour": 1, + "pauseMinute": 2, + "pauseSecond": 3, + "pauseMessage": "Delay plz", + "moduleId": null, + "pauseTemperature": null, + "id": "2e622080-92a6-11e9-ac62-1b173f839d9e", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + } + }, + "orderedStepIds": [ + "e7d36200-92a5-11e9-ac62-1b173f839d9e", + "18113c80-92a6-11e9-ac62-1b173f839d9e", + "2e622080-92a6-11e9-ac62-1b173f839d9e" + ] + } + }, + "robot": { "model": "OT-2 Standard", "deckId": "ot2_standard" }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons OT-2 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/tipone_96_tiprack_200ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "TipOne", + "brandId": ["1111-0200"], + "links": ["https://www.usascientific.com/200ul-tipone-stacks.aspx"] + }, + "metadata": { + "displayName": "TipOne 96 Tip Rack 200 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 63.9 + }, + "namespace": "opentrons", + "schemaVersion": 2, + "version": 1, + "wells": { + "H1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 9.25, + "z": 53.36 + }, + "G1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 18.25, + "z": 53.36 + }, + "F1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 27.25, + "z": 53.36 + }, + "E1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 36.25, + "z": 53.36 + }, + "D1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 45.25, + "z": 53.36 + }, + "C1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 54.25, + "z": 53.36 + }, + "B1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 63.25, + "z": 53.36 + }, + "A1": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 13.69, + "y": 72.25, + "z": 53.36 + }, + "H2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 9.25, + "z": 53.36 + }, + "G2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 18.25, + "z": 53.36 + }, + "F2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 27.25, + "z": 53.36 + }, + "E2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 36.25, + "z": 53.36 + }, + "D2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 45.25, + "z": 53.36 + }, + "C2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 54.25, + "z": 53.36 + }, + "B2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 63.25, + "z": 53.36 + }, + "A2": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 22.69, + "y": 72.25, + "z": 53.36 + }, + "H3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 9.25, + "z": 53.36 + }, + "G3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 18.25, + "z": 53.36 + }, + "F3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 27.25, + "z": 53.36 + }, + "E3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 36.25, + "z": 53.36 + }, + "D3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 45.25, + "z": 53.36 + }, + "C3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 54.25, + "z": 53.36 + }, + "B3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 63.25, + "z": 53.36 + }, + "A3": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 31.69, + "y": 72.25, + "z": 53.36 + }, + "H4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 9.25, + "z": 53.36 + }, + "G4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 18.25, + "z": 53.36 + }, + "F4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 27.25, + "z": 53.36 + }, + "E4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 36.25, + "z": 53.36 + }, + "D4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 45.25, + "z": 53.36 + }, + "C4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 54.25, + "z": 53.36 + }, + "B4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 63.25, + "z": 53.36 + }, + "A4": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 40.69, + "y": 72.25, + "z": 53.36 + }, + "H5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 9.25, + "z": 53.36 + }, + "G5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 18.25, + "z": 53.36 + }, + "F5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 27.25, + "z": 53.36 + }, + "E5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 36.25, + "z": 53.36 + }, + "D5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 45.25, + "z": 53.36 + }, + "C5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 54.25, + "z": 53.36 + }, + "B5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 63.25, + "z": 53.36 + }, + "A5": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 49.69, + "y": 72.25, + "z": 53.36 + }, + "H6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 9.25, + "z": 53.36 + }, + "G6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 18.25, + "z": 53.36 + }, + "F6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 27.25, + "z": 53.36 + }, + "E6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 36.25, + "z": 53.36 + }, + "D6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 45.25, + "z": 53.36 + }, + "C6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 54.25, + "z": 53.36 + }, + "B6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 63.25, + "z": 53.36 + }, + "A6": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 58.69, + "y": 72.25, + "z": 53.36 + }, + "H7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 9.25, + "z": 53.36 + }, + "G7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 18.25, + "z": 53.36 + }, + "F7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 27.25, + "z": 53.36 + }, + "E7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 36.25, + "z": 53.36 + }, + "D7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 45.25, + "z": 53.36 + }, + "C7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 54.25, + "z": 53.36 + }, + "B7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 63.25, + "z": 53.36 + }, + "A7": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 67.69, + "y": 72.25, + "z": 53.36 + }, + "H8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 9.25, + "z": 53.36 + }, + "G8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 18.25, + "z": 53.36 + }, + "F8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 27.25, + "z": 53.36 + }, + "E8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 36.25, + "z": 53.36 + }, + "D8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 45.25, + "z": 53.36 + }, + "C8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 54.25, + "z": 53.36 + }, + "B8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 63.25, + "z": 53.36 + }, + "A8": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 76.69, + "y": 72.25, + "z": 53.36 + }, + "H9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 9.25, + "z": 53.36 + }, + "G9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 18.25, + "z": 53.36 + }, + "F9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 27.25, + "z": 53.36 + }, + "E9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 36.25, + "z": 53.36 + }, + "D9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 45.25, + "z": 53.36 + }, + "C9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 54.25, + "z": 53.36 + }, + "B9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 63.25, + "z": 53.36 + }, + "A9": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 85.69, + "y": 72.25, + "z": 53.36 + }, + "H10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 9.25, + "z": 53.36 + }, + "G10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 18.25, + "z": 53.36 + }, + "F10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 27.25, + "z": 53.36 + }, + "E10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 36.25, + "z": 53.36 + }, + "D10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 45.25, + "z": 53.36 + }, + "C10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 54.25, + "z": 53.36 + }, + "B10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 63.25, + "z": 53.36 + }, + "A10": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 94.69, + "y": 72.25, + "z": 53.36 + }, + "H11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 9.25, + "z": 53.36 + }, + "G11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 18.25, + "z": 53.36 + }, + "F11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 27.25, + "z": 53.36 + }, + "E11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 36.25, + "z": 53.36 + }, + "D11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 45.25, + "z": 53.36 + }, + "C11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 54.25, + "z": 53.36 + }, + "B11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 63.25, + "z": 53.36 + }, + "A11": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 103.69, + "y": 72.25, + "z": 53.36 + }, + "H12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 9.25, + "z": 53.36 + }, + "G12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 18.25, + "z": 53.36 + }, + "F12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 27.25, + "z": 53.36 + }, + "E12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 36.25, + "z": 53.36 + }, + "D12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 45.25, + "z": 53.36 + }, + "C12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 54.25, + "z": 53.36 + }, + "B12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 63.25, + "z": 53.36 + }, + "A12": { + "depth": 10.54, + "shape": "circular", + "diameter": 6.4, + "totalLiquidVolume": 200, + "x": 112.69, + "y": 72.25, + "z": 53.36 + } + }, + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipOverlap": 6.1, + "tipLength": 50.93, + "isMagneticModuleCompatible": false, + "loadName": "tipone_96_tiprack_200ul" + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + "metadata": {} + } + ], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": [ + "fixedTrash", + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 0, + "x": 82.84, + "y": 80, + "z": 82 + } + }, + "brand": { "brand": "Opentrons" }, + "groups": [{ "wells": ["A1"], "metadata": {} }], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/usascientific_96_wellplate_2.4ml_deep/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "USA Scientific", + "brandId": ["1896-2000"], + "links": [ + "https://www.usascientific.com/2ml-deep96-well-plateone-bulk.aspx" + ] + }, + "metadata": { + "displayName": "USA Scientific 96 Deep Well Plate 2.4 mL", + "displayCategory": "wellPlate", + "displayVolumeUnits": "mL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.8, + "yDimension": 85.5, + "zDimension": 44.1 + }, + "wells": { + "H1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 11.2, + "z": 2.8 + }, + "G1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 20.2, + "z": 2.8 + }, + "F1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 29.2, + "z": 2.8 + }, + "E1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 38.2, + "z": 2.8 + }, + "D1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 47.2, + "z": 2.8 + }, + "C1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 56.2, + "z": 2.8 + }, + "B1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 65.2, + "z": 2.8 + }, + "A1": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 14.4, + "y": 74.2, + "z": 2.8 + }, + "H2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 11.2, + "z": 2.8 + }, + "G2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 20.2, + "z": 2.8 + }, + "F2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 29.2, + "z": 2.8 + }, + "E2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 38.2, + "z": 2.8 + }, + "D2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 47.2, + "z": 2.8 + }, + "C2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 56.2, + "z": 2.8 + }, + "B2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 65.2, + "z": 2.8 + }, + "A2": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 23.4, + "y": 74.2, + "z": 2.8 + }, + "H3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 11.2, + "z": 2.8 + }, + "G3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 20.2, + "z": 2.8 + }, + "F3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 29.2, + "z": 2.8 + }, + "E3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 38.2, + "z": 2.8 + }, + "D3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 47.2, + "z": 2.8 + }, + "C3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 56.2, + "z": 2.8 + }, + "B3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 65.2, + "z": 2.8 + }, + "A3": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 32.4, + "y": 74.2, + "z": 2.8 + }, + "H4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 11.2, + "z": 2.8 + }, + "G4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 20.2, + "z": 2.8 + }, + "F4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 29.2, + "z": 2.8 + }, + "E4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 38.2, + "z": 2.8 + }, + "D4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 47.2, + "z": 2.8 + }, + "C4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 56.2, + "z": 2.8 + }, + "B4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 65.2, + "z": 2.8 + }, + "A4": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 41.4, + "y": 74.2, + "z": 2.8 + }, + "H5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 11.2, + "z": 2.8 + }, + "G5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 20.2, + "z": 2.8 + }, + "F5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 29.2, + "z": 2.8 + }, + "E5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 38.2, + "z": 2.8 + }, + "D5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 47.2, + "z": 2.8 + }, + "C5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 56.2, + "z": 2.8 + }, + "B5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 65.2, + "z": 2.8 + }, + "A5": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 50.4, + "y": 74.2, + "z": 2.8 + }, + "H6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 11.2, + "z": 2.8 + }, + "G6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 20.2, + "z": 2.8 + }, + "F6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 29.2, + "z": 2.8 + }, + "E6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 38.2, + "z": 2.8 + }, + "D6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 47.2, + "z": 2.8 + }, + "C6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 56.2, + "z": 2.8 + }, + "B6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 65.2, + "z": 2.8 + }, + "A6": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 59.4, + "y": 74.2, + "z": 2.8 + }, + "H7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 11.2, + "z": 2.8 + }, + "G7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 20.2, + "z": 2.8 + }, + "F7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 29.2, + "z": 2.8 + }, + "E7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 38.2, + "z": 2.8 + }, + "D7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 47.2, + "z": 2.8 + }, + "C7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 56.2, + "z": 2.8 + }, + "B7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 65.2, + "z": 2.8 + }, + "A7": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 68.4, + "y": 74.2, + "z": 2.8 + }, + "H8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 11.2, + "z": 2.8 + }, + "G8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 20.2, + "z": 2.8 + }, + "F8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 29.2, + "z": 2.8 + }, + "E8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 38.2, + "z": 2.8 + }, + "D8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 47.2, + "z": 2.8 + }, + "C8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 56.2, + "z": 2.8 + }, + "B8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 65.2, + "z": 2.8 + }, + "A8": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 77.4, + "y": 74.2, + "z": 2.8 + }, + "H9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 11.2, + "z": 2.8 + }, + "G9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 20.2, + "z": 2.8 + }, + "F9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 29.2, + "z": 2.8 + }, + "E9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 38.2, + "z": 2.8 + }, + "D9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 47.2, + "z": 2.8 + }, + "C9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 56.2, + "z": 2.8 + }, + "B9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 65.2, + "z": 2.8 + }, + "A9": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 86.4, + "y": 74.2, + "z": 2.8 + }, + "H10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 11.2, + "z": 2.8 + }, + "G10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 20.2, + "z": 2.8 + }, + "F10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 29.2, + "z": 2.8 + }, + "E10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 38.2, + "z": 2.8 + }, + "D10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 47.2, + "z": 2.8 + }, + "C10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 56.2, + "z": 2.8 + }, + "B10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 65.2, + "z": 2.8 + }, + "A10": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 95.4, + "y": 74.2, + "z": 2.8 + }, + "H11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 11.2, + "z": 2.8 + }, + "G11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 20.2, + "z": 2.8 + }, + "F11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 29.2, + "z": 2.8 + }, + "E11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 38.2, + "z": 2.8 + }, + "D11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 47.2, + "z": 2.8 + }, + "C11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 56.2, + "z": 2.8 + }, + "B11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 65.2, + "z": 2.8 + }, + "A11": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 104.4, + "y": 74.2, + "z": 2.8 + }, + "H12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 11.2, + "z": 2.8 + }, + "G12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 20.2, + "z": 2.8 + }, + "F12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 29.2, + "z": 2.8 + }, + "E12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 38.2, + "z": 2.8 + }, + "D12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 47.2, + "z": 2.8 + }, + "C12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 56.2, + "z": 2.8 + }, + "B12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 65.2, + "z": 2.8 + }, + "A12": { + "depth": 41.3, + "shape": "rectangular", + "xDimension": 8.2, + "yDimension": 8.2, + "totalLiquidVolume": 2400, + "x": 113.4, + "y": 74.2, + "z": 2.8 + } + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + "metadata": { "wellBottomShape": "u" } + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": false, + "isMagneticModuleCompatible": true, + "magneticModuleEngageHeight": 14.94, + "loadName": "usascientific_96_wellplate_2.4ml_deep" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + } + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "0": { + "displayName": "samples", + "description": "", + "displayColor": "#b925ff" + }, + "1": { "displayName": "dna", "description": "", "displayColor": "#ffd600" } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "key": "10490d30-b30f-41e6-af23-63c890aff43a", + "commandType": "loadPipette", + "params": { + "pipetteName": "p10_single", + "mount": "left", + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e" + } + }, + { + "key": "d06bff13-49fd-4334-bad0-5be9530e773f", + "commandType": "loadPipette", + "params": { + "pipetteName": "p50_single", + "mount": "right", + "pipetteId": "c6f47740-92a5-11e9-ac62-1b173f839d9e" + } + }, + { + "key": "dbdd37ae-154e-474b-87aa-758f4a7d20da", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons Fixed Trash", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "12" } + } + }, + { + "key": "8be9048f-849c-4ee5-a87f-0e76e56096bb", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons OT-2 96 Tip Rack 10 µL", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "loadName": "opentrons_96_tiprack_10ul", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "1" } + } + }, + { + "key": "36f44450-4641-4150-8a60-5fd6c4963544", + "commandType": "loadLabware", + "params": { + "displayName": "(Retired) TipOne 96 Tip Rack 200 µL", + "labwareId": "c6f51380-92a5-11e9-ac62-1b173f839d9e:tiprack-200ul", + "loadName": "tipone_96_tiprack_200ul", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "2" } + } + }, + { + "key": "cbc12f40-2054-47a6-9c77-955aad1cb131", + "commandType": "loadLabware", + "params": { + "displayName": "USA Scientific 96 Deep Well Plate 2.4 mL", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "loadName": "usascientific_96_wellplate_2.4ml_deep", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "10" } + } + }, + { + "commandType": "loadLiquid", + "key": "bb6af72c-fe97-4646-a577-d9f8c663bde0", + "params": { + "liquidId": "1", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "volumeByWell": { "F1": 44, "G1": 44, "H1": 44 } + } + }, + { + "commandType": "loadLiquid", + "key": "e33b2f5e-213a-42cd-949e-f6709858c4b2", + "params": { + "liquidId": "0", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "volumeByWell": { + "A1": 121, + "B1": 121, + "C1": 121, + "D1": 121, + "E1": 121 + } + } + }, + { + "commandType": "pickUpTip", + "key": "ddcf8842-d757-4b1c-b442-968c2a3d6c90", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "A1" + } + }, + { + "commandType": "aspirate", + "key": "abc54166-dbeb-423e-9885-2dd2581f08b3", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "6866d78a-e080-4d1d-9dd6-c2de6638f594", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "8edb653f-4c38-4972-af4a-5f85c34bc42e", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "2f93d115-47db-41da-9776-c26653d58dba", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "447e8a2a-b355-4d37-bf44-e7228c147b5c", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "4b362abb-dfee-4e45-9bde-14b0911b5f0a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "f254821d-a7ec-466a-9bb6-aeef0b833020", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "93ff1c04-c163-4c6e-bc52-3e959da45fb2", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "a26a76a2-36df-425e-85ef-fa7a2173db81", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "f78dae7e-ce13-4f34-9dc8-f4a639bfc4d9", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "d37cfd16-1e0b-4a43-9ec4-72ef27669289", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "afa12da7-f947-4a80-bbb7-5d4016d606cd", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "05958719-9184-4648-8e5b-5b3789ccdd52", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "3849541a-4f6e-4c51-b415-1070aaa2de88", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E8", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "fd9eaa99-8b6d-458f-90d8-f1375db06212", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "fd938091-986e-40fd-8ace-97a0675b9033", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "4cc99062-a715-4d52-a0d3-eebce9f0ebe3", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "key": "4a0603eb-79b6-4e7c-a8f2-05362e4d82db", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "0c53fb8b-b2fc-4431-9326-2d884c82858b", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "14b4ffea-bd96-447d-ae08-96d84dd0564a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "7b67b875-927a-49b1-b6e0-24d7dd318379", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "4d8b0beb-e4dd-4808-a3a3-fa71ae3ca010", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "782eb84c-8dc8-4759-a7a0-50fc98820ec1", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "ec687525-373e-419f-8b25-4ef6e3b8edc0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "b4359cc2-a00d-41a0-ab4d-1101427d8523", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "33dd1e94-713d-4d96-8b93-b59971097f65", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "78ceed3b-af35-43e9-bc4f-c8c06a905332", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "d9f83ed3-8fee-4d3d-9df8-598c31e7532b", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "2006b089-bacc-4f55-b9eb-fd8be206fb8d", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "c1d223dc-48b4-43bc-aa75-0068bbe78495", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "f46d742d-5c69-41ab-963c-9245ab024a38", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D8", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "472381a3-7d18-414f-80e0-72ef890e2ba6", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "6926d70d-f8b2-47a3-9947-5def32d10650", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "9d4332a9-1a08-4636-87c3-ed35ec1b7a66", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "C1" + } + }, + { + "commandType": "aspirate", + "key": "f952266a-aa63-4f9e-9148-e4cb9c7f791b", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "e8f08e07-8af8-498e-b702-a5bacc71bd83", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "ebd71897-bda3-4e94-8dde-84a116ec6954", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "7cc1ad60-a61b-41df-b125-c4d05b1abf8a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "80f92251-3a98-43ba-aec1-650c12850d4c", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "553e5234-d25e-4f03-88d5-f16df8ca618f", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "3c5d2144-3ee0-4cf3-ba3a-0b6eba607b96", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "dd1b0732-ad01-405a-a2f2-3cc54c1262dd", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "dcae2b0e-a03d-4bfa-95f9-034b60552698", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "ea2ddcfd-41ec-4de4-8e74-443a4fbf7f9f", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "f1448790-3505-4dc9-b141-ff8b9c7b3f94", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "883b3351-0f40-4ffb-8859-129f4070e979", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "8922ec66-a4d0-4d30-b732-582fef687654", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C8", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "452bd6ca-f099-4a8c-aace-9400cb3e67d1", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C8", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "bb4426a9-c41b-4d32-80c4-eea39895a05e", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "239401b1-999b-4607-99c6-f9b2d78e85f8", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "7aa9910a-8f0f-493a-a15c-aeb7036b1223", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "D1" + } + }, + { + "commandType": "aspirate", + "key": "c1528676-5271-45b2-9736-acf95a216b43", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "83d8183f-0817-4c05-9d8d-1461b1c0cc51", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "91a4065f-e85a-4d46-8ece-ad85de3c51c2", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "1ac856c1-65fb-414f-b571-92a2bf59881a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "4b94a27f-3d30-4318-ac34-15fe23ccdab4", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "a11e69c6-21b2-41db-95a0-708b3f91fe0a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "b0976d49-629f-4652-a228-248e4ada6fd9", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "ae8865b0-d186-429e-8af3-965635484e52", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "9807cc62-54ef-4453-b7cb-a2ba58bf3276", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "4e0f91a8-94ad-4b8f-a681-0a7ae4061489", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "e38a70c0-0e37-4302-8034-6d86bd30adef", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "4d477202-6665-498c-a033-35a684860299", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "327198e1-4572-4636-bd8e-87826bb26518", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "8d882fc2-8b31-45c9-b413-bd0af852b264", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E7", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "06ec3733-9197-4a9e-a841-cc5be2fcb8b5", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "2fc19bc6-87e3-439f-807c-8f604317771c", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "792e5e38-f2c8-4bf4-a3c2-00d59cd6edd0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "E1" + } + }, + { + "commandType": "aspirate", + "key": "c582bd36-3f43-43c2-b96a-7abf81a36f14", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "6f05b509-e93a-429f-a94d-274bab463246", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "e6b4b610-3433-47a2-99ca-1bea8ecfda34", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "c25fe82f-1781-4912-b624-bcb74cda9521", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "b9c0a710-8b44-47bc-8165-007ea6086d21", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "c9e0f9d9-1380-4066-8a23-da4183a5ad37", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "9d91e374-98fb-45ab-836f-501c9dcdbf6f", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "26b25c10-1619-40bb-83b7-4f9e4c4d8a31", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "efc2f04b-9ec5-4ca4-9303-0e4783479bb3", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "babe915b-503b-4d27-89e0-c96bcfd3ea9c", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "037898d8-7827-488f-b319-96e3bc472f18", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "6f1962ac-fb92-4cff-9519-f63eaf2f2052", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "016aa372-b1e8-4ab8-9b66-7265099af1b0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "d2f9c4af-0430-46b6-8262-d405e2d57fb0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D7", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "d14cb460-44ef-4f42-817f-ad5c58348f87", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "887f144f-90a7-4883-8029-6d069fa96706", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "48983d29-6c4b-48bf-81e2-fcbc0e86281a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "F1" + } + }, + { + "commandType": "aspirate", + "key": "d3a62da8-670a-4d92-9deb-293235df4db4", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "b4709140-520a-49dc-98bb-f5d8826cbb2f", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "bce13939-d717-4e33-bbc8-c4cf68dd17bf", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "bb0e80f5-6bd9-46d7-95b6-af38234ca295", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "2afb3d0e-07f8-4d5a-b05b-5a532e01e991", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "88fe3503-e3a2-4c7a-b0a8-db580baf414d", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "c8ef1483-95a8-4661-bf44-6babb755b288", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "264f5cdc-8650-45e8-91a9-806795f30bc8", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "438a574c-8730-4083-af36-ef7d09bd364c", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "691080be-0bbd-4be0-be94-ac8d34202dae", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "5d6a01d1-9189-4c23-ae1b-9482c37d6751", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "2e05b531-e624-4c07-9071-7149f7221335", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "8b423fd0-2618-4b87-970d-60a4faf61a57", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C7", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "a105d18f-8b9d-4b8e-9363-faa62d1f99b9", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C7", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "d8dab320-a9ef-4449-8f43-e57e60e41427", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "d1bc3bfe-77b2-4ad2-9cec-dad5b6b72d89", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "e868f3cd-41bf-44c4-8278-02c276564e36", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "G1" + } + }, + { + "commandType": "aspirate", + "key": "6e21c33d-f3a8-4629-8b99-91e49ae6487a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "fc152c99-7992-4dfd-9c5c-62bf3dd4bb52", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "aedb3bf0-7e71-454a-8a33-167a5b50ccac", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "112d537a-63ff-4cbf-afcc-10bcfd2bc560", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "9a1f4f79-ef7b-48ab-8bdb-c2ed60e3e245", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "506b9ed6-9c0f-4f4b-9597-37e4170ce351", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "a8112429-8d68-4cd8-8acd-8d56f44651a6", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "13fd50ae-c7ac-4d82-b924-e576ee9f1011", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "10059ec2-5f30-470a-964b-d9e8e7d6a287", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "7e10811a-3ebb-497c-895d-1141cd5cbffc", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "8ae8efa4-303e-456c-8ffc-734be857a19a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "9e6fa5d6-f443-4c7b-8309-c00f8fbe63e8", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "d59c772e-22b2-4f20-9c5f-e6e6f7edef91", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "b3a7dcec-9a39-4e9e-9b5c-413dd795b61b", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "E6", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "e15cb327-53f1-49c5-8e1d-b2d33ac679af", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "e5b04e34-cf2b-4181-a5cc-3737774bb66d", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "cece71c9-da4c-429f-8a5a-1d4e0b7f7b3a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "H1" + } + }, + { + "commandType": "aspirate", + "key": "b2e3a107-36e0-4e4f-b1b7-d0bb9592d8f0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "128f331b-bd74-41f3-83d1-35591a9d6cb5", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "0102846f-ca86-487a-9760-ae6da952b73a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "9167ff8c-af10-48bb-8537-b07b62ac9dfd", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "1abcaafd-7d19-4d68-ab4a-3128f05b176b", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "f6d6984c-2492-4e39-ba91-fdbac332f60a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "82ed4f39-63e1-4200-94e2-3ee37a37c13a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "0ceb99e9-dd68-4cb3-a75a-d18808953bea", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "a21407fa-e542-4036-8cd7-f47628ec37f0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "000cd287-04be-41ab-a55f-78c57e1ec344", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "3db19200-9dc5-4b70-8519-a2d3f263e096", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "5d25da83-b1dc-4c87-84b5-9fde3f1a0788", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "51b4455e-9bcb-4cc1-a874-c006e774d94a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "a311a28f-2909-4fb4-b178-9b432a926021", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "D6", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "40f9cc2c-7052-46a8-9ea1-7614dad45ca0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "1499c089-210e-4074-a80f-17f4d544e2d3", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "c67723e3-3f5b-47e5-99a8-58c688c1f889", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "A2" + } + }, + { + "commandType": "aspirate", + "key": "ac5f3058-c256-4aca-ae23-b104ede6e5ed", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "45699ee9-5546-4677-ab4b-3945be9d7bfe", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "ce0965a9-6b5b-4520-9164-4353166a5507", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "7fa4064e-e14e-47e1-9fce-85a9a8476e46", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "6f959c9c-c0fc-46f5-bc7e-84a1b2d23020", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "6616fc0c-cbe5-4f75-80cb-51183c0826b0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 2, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "ac728c08-f5d6-46c2-9c4d-3bdeae690733", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 1 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "touchTip", + "key": "8f1d01ff-7646-42bb-99d8-1caddf458c75", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 28.5 } } + } + }, + { + "commandType": "dispense", + "key": "0726f420-1fd4-4e67-af7d-28d2100f4b3d", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 6, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "1bb939b4-6bd1-46e9-ae18-8bf6ac6a9f84", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "a8bcd111-03b0-4c7d-af3b-ac77058dfd29", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "aspirate", + "key": "33e6bc40-9f85-44f3-8514-9cec9505d384", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 0.6 + } + }, + { + "commandType": "dispense", + "key": "fdcddf51-9f17-481b-a590-0c790981918a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 3, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C6", + "wellLocation": { "origin": "bottom", "offset": { "z": 2.5 } }, + "flowRate": 10 + } + }, + { + "commandType": "touchTip", + "key": "28031375-1a51-446d-afaf-add397279dbb", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "C6", + "wellLocation": { "origin": "bottom", "offset": { "z": 40.3 } } + } + }, + { + "commandType": "blowout", + "key": "4cfcc5c1-7f5f-46aa-9d76-35e93696bb8d", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "flowRate": 10, + "wellLocation": { "origin": "bottom", "offset": { "z": 0 } } + } + }, + { + "commandType": "dropTip", + "key": "1264fdbe-e29e-4519-9edd-8b0c32e23a4b", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "pickUpTip", + "key": "e0cd280c-0ab0-4b4d-a4e0-d858ac3597fc", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "c6f4ec70-92a5-11e9-ac62-1b173f839d9e:tiprack-10ul", + "wellName": "B2" + } + }, + { + "commandType": "aspirate", + "key": "8f1cb41f-792c-45b1-b3b7-165d0e5bd22a", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 5.5, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 8 + } + }, + { + "commandType": "dispense", + "key": "3eb444e3-ca80-4e1e-a5cd-57123d5870b0", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 5.5, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7 + } + }, + { + "commandType": "aspirate", + "key": "7ba054a3-04a0-4b80-b6ba-6f74a691f667", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 5.5, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 8 + } + }, + { + "commandType": "dispense", + "key": "841d80e8-9027-4f9f-8261-87417ec7b818", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 5.5, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7 + } + }, + { + "commandType": "aspirate", + "key": "f391b154-1154-4219-8928-bcd8c0edc04e", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 5.5, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 8 + } + }, + { + "commandType": "dispense", + "key": "45b3fca4-e821-4d84-944e-a3015c039cda", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "volume": 5.5, + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 7 + } + }, + { + "commandType": "blowout", + "key": "9ea61ca4-ddf7-4ea4-910d-0477bb6adc7f", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "flowRate": 7, + "wellLocation": { "origin": "bottom", "offset": { "z": 41.3 } } + } + }, + { + "commandType": "touchTip", + "key": "0a45babc-a25e-450a-8a38-30d7e88d6a15", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "dafd4000-92a5-11e9-ac62-1b173f839d9e:96-deep-well", + "wellName": "F1", + "wellLocation": { "origin": "bottom", "offset": { "z": 30.5 } } + } + }, + { + "commandType": "dropTip", + "key": "6e459ebb-5081-40c4-8a7e-9f9e1a1e78e9", + "params": { + "pipetteId": "c6f45030-92a5-11e9-ac62-1b173f839d9e", + "labwareId": "1db05991-1a06-4210-b448-7427d029da57:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + }, + { + "commandType": "waitForDuration", + "key": "e0b12bd3-db8b-4ca6-9aba-0c5a4f349a95", + "params": { "seconds": 3723, "message": "Delay plz" } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [] +} diff --git a/protocol-designer/fixtures/protocol/8/mix_8_0_0.json b/protocol-designer/fixtures/protocol/8/mix_8_0_0.json new file mode 100644 index 00000000000..e7071894f70 --- /dev/null +++ b/protocol-designer/fixtures/protocol/8/mix_8_0_0.json @@ -0,0 +1,2288 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Pause and mix v5.0.0", + "author": "", + "description": "A test for 5.0.0 -> 5.1.0 migration", + "created": 1600714068238, + "lastModified": 1698856063990, + "category": null, + "subcategory": null, + "tags": [] + }, + "designerApplication": { + "name": "opentrons/protocol-designer", + "version": "8.0.0", + "data": { + "_internalAppBuildDate": "Wed, 01 Nov 2023 16:22:39 GMT", + "defaultValues": { + "aspirate_mmFromBottom": 1, + "dispense_mmFromBottom": 0.5, + "touchTip_mmFromTop": -1, + "blowout_mmFromTop": 0 + }, + "pipetteTiprackAssignments": { + "pipetteId": "opentrons/opentrons_96_tiprack_10ul/1" + }, + "dismissedWarnings": { "form": {}, "timeline": {} }, + "ingredients": {}, + "ingredLocations": {}, + "savedStepForms": { + "__INITIAL_DECK_SETUP_STEP__": { + "labwareLocationUpdate": { + "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1": "12", + "f1c677c0-fc3a-11ea-8809-e959e7d61d96:opentrons/opentrons_96_tiprack_10ul/1": "1", + "fe572c50-fc3a-11ea-8809-e959e7d61d96:opentrons/biorad_96_wellplate_200ul_pcr/1": "7" + }, + "pipetteLocationUpdate": { "pipetteId": "left" }, + "moduleLocationUpdate": {}, + "stepType": "manualIntervention", + "id": "__INITIAL_DECK_SETUP_STEP__" + }, + "f59ea8e0-fc3a-11ea-8809-e959e7d61d96": { + "pauseAction": "untilTime", + "pauseHour": "1", + "pauseMinute": "2", + "pauseSecond": "3", + "pauseMessage": "", + "moduleId": null, + "pauseTemperature": null, + "id": "f59ea8e0-fc3a-11ea-8809-e959e7d61d96", + "stepType": "pause", + "stepName": "pause", + "stepDetails": "" + }, + "fc4dc7c0-fc3a-11ea-8809-e959e7d61d96": { + "times": "2", + "changeTip": "always", + "labware": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "mix_wellOrder_first": "t2b", + "mix_wellOrder_second": "l2r", + "blowout_checkbox": false, + "blowout_location": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "mix_mmFromBottom": 0.5, + "pipette": "pipetteId", + "volume": "5", + "wells": ["A1"], + "aspirate_flowRate": null, + "dispense_flowRate": null, + "aspirate_delay_checkbox": false, + "aspirate_delay_seconds": "1", + "dispense_delay_checkbox": false, + "dispense_delay_seconds": "1", + "mix_touchTip_checkbox": false, + "mix_touchTip_mmFromBottom": null, + "dropTip_location": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "id": "fc4dc7c0-fc3a-11ea-8809-e959e7d61d96", + "stepType": "mix", + "stepName": "mix", + "stepDetails": "" + } + }, + "orderedStepIds": [ + "f59ea8e0-fc3a-11ea-8809-e959e7d61d96", + "fc4dc7c0-fc3a-11ea-8809-e959e7d61d96" + ] + } + }, + "robot": { "model": "OT-2 Standard", "deckId": "ot2_standard" }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons OT-2 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/biorad_96_wellplate_200ul_pcr/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "metadata": { + "displayName": "Bio-Rad 96 Well Plate 200 µL PCR", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "yDimension": 85.48, + "xDimension": 127.76, + "zDimension": 16.06 + }, + "parameters": { + "format": "96Standard", + "isTiprack": false, + "loadName": "biorad_96_wellplate_200ul_pcr", + "isMagneticModuleCompatible": true, + "magneticModuleEngageHeight": 18 + }, + "wells": { + "H1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 11.24, + "z": 1.25 + }, + "G1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 20.24, + "z": 1.25 + }, + "F1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 29.24, + "z": 1.25 + }, + "E1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 38.24, + "z": 1.25 + }, + "D1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 47.24, + "z": 1.25 + }, + "C1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 56.24, + "z": 1.25 + }, + "B1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 65.24, + "z": 1.25 + }, + "A1": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 14.38, + "y": 74.24, + "z": 1.25 + }, + "H2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 11.24, + "z": 1.25 + }, + "G2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 20.24, + "z": 1.25 + }, + "F2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 29.24, + "z": 1.25 + }, + "E2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 38.24, + "z": 1.25 + }, + "D2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 47.24, + "z": 1.25 + }, + "C2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 56.24, + "z": 1.25 + }, + "B2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 65.24, + "z": 1.25 + }, + "A2": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 23.38, + "y": 74.24, + "z": 1.25 + }, + "H3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 11.24, + "z": 1.25 + }, + "G3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 20.24, + "z": 1.25 + }, + "F3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 29.24, + "z": 1.25 + }, + "E3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 38.24, + "z": 1.25 + }, + "D3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 47.24, + "z": 1.25 + }, + "C3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 56.24, + "z": 1.25 + }, + "B3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 65.24, + "z": 1.25 + }, + "A3": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 32.38, + "y": 74.24, + "z": 1.25 + }, + "H4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 11.24, + "z": 1.25 + }, + "G4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 20.24, + "z": 1.25 + }, + "F4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 29.24, + "z": 1.25 + }, + "E4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 38.24, + "z": 1.25 + }, + "D4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 47.24, + "z": 1.25 + }, + "C4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 56.24, + "z": 1.25 + }, + "B4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 65.24, + "z": 1.25 + }, + "A4": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 41.38, + "y": 74.24, + "z": 1.25 + }, + "H5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 11.24, + "z": 1.25 + }, + "G5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 20.24, + "z": 1.25 + }, + "F5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 29.24, + "z": 1.25 + }, + "E5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 38.24, + "z": 1.25 + }, + "D5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 47.24, + "z": 1.25 + }, + "C5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 56.24, + "z": 1.25 + }, + "B5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 65.24, + "z": 1.25 + }, + "A5": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 50.38, + "y": 74.24, + "z": 1.25 + }, + "H6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 11.24, + "z": 1.25 + }, + "G6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 20.24, + "z": 1.25 + }, + "F6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 29.24, + "z": 1.25 + }, + "E6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 38.24, + "z": 1.25 + }, + "D6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 47.24, + "z": 1.25 + }, + "C6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 56.24, + "z": 1.25 + }, + "B6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 65.24, + "z": 1.25 + }, + "A6": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 59.38, + "y": 74.24, + "z": 1.25 + }, + "H7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 11.24, + "z": 1.25 + }, + "G7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 20.24, + "z": 1.25 + }, + "F7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 29.24, + "z": 1.25 + }, + "E7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 38.24, + "z": 1.25 + }, + "D7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 47.24, + "z": 1.25 + }, + "C7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 56.24, + "z": 1.25 + }, + "B7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 65.24, + "z": 1.25 + }, + "A7": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 68.38, + "y": 74.24, + "z": 1.25 + }, + "H8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 11.24, + "z": 1.25 + }, + "G8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 20.24, + "z": 1.25 + }, + "F8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 29.24, + "z": 1.25 + }, + "E8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 38.24, + "z": 1.25 + }, + "D8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 47.24, + "z": 1.25 + }, + "C8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 56.24, + "z": 1.25 + }, + "B8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 65.24, + "z": 1.25 + }, + "A8": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 77.38, + "y": 74.24, + "z": 1.25 + }, + "H9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 11.24, + "z": 1.25 + }, + "G9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 20.24, + "z": 1.25 + }, + "F9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 29.24, + "z": 1.25 + }, + "E9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 38.24, + "z": 1.25 + }, + "D9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 47.24, + "z": 1.25 + }, + "C9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 56.24, + "z": 1.25 + }, + "B9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 65.24, + "z": 1.25 + }, + "A9": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 86.38, + "y": 74.24, + "z": 1.25 + }, + "H10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 11.24, + "z": 1.25 + }, + "G10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 20.24, + "z": 1.25 + }, + "F10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 29.24, + "z": 1.25 + }, + "E10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 38.24, + "z": 1.25 + }, + "D10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 47.24, + "z": 1.25 + }, + "C10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 56.24, + "z": 1.25 + }, + "B10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 65.24, + "z": 1.25 + }, + "A10": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 95.38, + "y": 74.24, + "z": 1.25 + }, + "H11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 11.24, + "z": 1.25 + }, + "G11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 20.24, + "z": 1.25 + }, + "F11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 29.24, + "z": 1.25 + }, + "E11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 38.24, + "z": 1.25 + }, + "D11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 47.24, + "z": 1.25 + }, + "C11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 56.24, + "z": 1.25 + }, + "B11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 65.24, + "z": 1.25 + }, + "A11": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 104.38, + "y": 74.24, + "z": 1.25 + }, + "H12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 11.24, + "z": 1.25 + }, + "G12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 20.24, + "z": 1.25 + }, + "F12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 29.24, + "z": 1.25 + }, + "E12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 38.24, + "z": 1.25 + }, + "D12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 47.24, + "z": 1.25 + }, + "C12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 56.24, + "z": 1.25 + }, + "B12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 65.24, + "z": 1.25 + }, + "A12": { + "depth": 14.81, + "shape": "circular", + "diameter": 5.46, + "totalLiquidVolume": 200, + "x": 113.38, + "y": 74.24, + "z": 1.25 + } + }, + "brand": { + "brand": "Bio-Rad", + "brandId": ["hsp9601"], + "links": [ + "http://www.bio-rad.com/en-us/sku/hsp9601-hard-shell-96-well-pcr-plates-low-profile-thin-wall-skirted-white-clear?ID=hsp9601" + ] + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + "metadata": { "wellBottomShape": "v" } + } + ], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + }, + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": [ + "fixedTrash", + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 0, + "x": 82.84, + "y": 80, + "z": 82 + } + }, + "brand": { "brand": "Opentrons" }, + "groups": [{ "wells": ["A1"], "metadata": {} }], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 } + } + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": {}, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "key": "b9692f92-0f90-43b4-9011-cb6f1dce93f3", + "commandType": "loadPipette", + "params": { + "pipetteName": "p20_single_gen2", + "mount": "left", + "pipetteId": "pipetteId" + } + }, + { + "key": "a699da37-b119-4513-bc89-8d12bfb04d11", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons Fixed Trash", + "labwareId": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "12" } + } + }, + { + "key": "8a5654da-305b-40db-b709-00aaba4e432a", + "commandType": "loadLabware", + "params": { + "displayName": "Opentrons OT-2 96 Tip Rack 10 µL", + "labwareId": "f1c677c0-fc3a-11ea-8809-e959e7d61d96:opentrons/opentrons_96_tiprack_10ul/1", + "loadName": "opentrons_96_tiprack_10ul", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "1" } + } + }, + { + "key": "dd5ec77d-196d-4a1d-885b-1515a42b50c2", + "commandType": "loadLabware", + "params": { + "displayName": "Bio-Rad 96 Well Plate 200 µL PCR", + "labwareId": "fe572c50-fc3a-11ea-8809-e959e7d61d96:opentrons/biorad_96_wellplate_200ul_pcr/1", + "loadName": "biorad_96_wellplate_200ul_pcr", + "namespace": "opentrons", + "version": 1, + "location": { "slotName": "7" } + } + }, + { + "commandType": "waitForDuration", + "key": "40f6c875-d477-47a5-a35a-8653242875e3", + "params": { "seconds": 3723, "message": "" } + }, + { + "commandType": "pickUpTip", + "key": "81de545d-5f36-4a94-a779-15cd80055f69", + "params": { + "pipetteId": "pipetteId", + "labwareId": "f1c677c0-fc3a-11ea-8809-e959e7d61d96:opentrons/opentrons_96_tiprack_10ul/1", + "wellName": "A1" + } + }, + { + "commandType": "aspirate", + "key": "238e649e-e4ab-42d7-b673-039e3d1eaf85", + "params": { + "pipetteId": "pipetteId", + "volume": 5, + "labwareId": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 3.78 + } + }, + { + "commandType": "dispense", + "key": "e7334c9f-7c7d-4ffb-9f23-79b166c5e4ed", + "params": { + "pipetteId": "pipetteId", + "volume": 5, + "labwareId": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 3.78 + } + }, + { + "commandType": "aspirate", + "key": "367fd252-0da1-4c13-8b84-1f7a7d787d30", + "params": { + "pipetteId": "pipetteId", + "volume": 5, + "labwareId": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 3.78 + } + }, + { + "commandType": "dispense", + "key": "1bd4c905-bc16-4af8-8ea9-f41bb6437892", + "params": { + "pipetteId": "pipetteId", + "volume": 5, + "labwareId": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1", + "wellLocation": { "origin": "bottom", "offset": { "z": 0.5 } }, + "flowRate": 3.78 + } + }, + { + "commandType": "dropTip", + "key": "09a770c7-3078-4c91-9209-20c1745850f3", + "params": { + "pipetteId": "pipetteId", + "labwareId": "ffc02dfb-f8b2-4dd8-89f4-13ca4c606944:opentrons/opentrons_1_trash_1100ml_fixed/1", + "wellName": "A1" + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [] +} diff --git a/protocol-designer/src/__tests__/validateProtocolFixtures.test.ts b/protocol-designer/src/__tests__/validateProtocolFixtures.test.ts index a44ffd5be24..466a6841a39 100644 --- a/protocol-designer/src/__tests__/validateProtocolFixtures.test.ts +++ b/protocol-designer/src/__tests__/validateProtocolFixtures.test.ts @@ -8,6 +8,7 @@ import protocolV4Schema from '@opentrons/shared-data/protocol/schemas/4.json' import protocolV5Schema from '@opentrons/shared-data/protocol/schemas/5.json' import protocolV6Schema from '@opentrons/shared-data/protocol/schemas/6.json' import protocolV7Schema from '@opentrons/shared-data/protocol/schemas/7.json' +import protocolV8Schema from '@opentrons/shared-data/protocol/schemas/8.json' import labwareV2Schema from '@opentrons/shared-data/labware/schemas/2.json' import commandV7Schema from '@opentrons/shared-data/command/schemas/7.json' @@ -44,8 +45,8 @@ const expectResultToMatchSchema = ( const getSchemaDefForProtocol = (protocol: any): any => { // For reference, possibilities are, from newest to oldest: - // "$otSharedSchema": "#/protocol/schemas/7" - // "schemaVersion": 7 + // "$otSharedSchema": "#/protocol/schemas/8" + // "schemaVersion": 8 // "protocol-schema": "1.0.0" let n if (typeof protocol.$otSharedSchema === 'string') { @@ -69,6 +70,8 @@ const getSchemaDefForProtocol = (protocol: any): any => { return protocolV6Schema case '7': return protocolV7Schema + case '8': + return protocolV8Schema } const errorMessage = `bad schema for protocol!: ${ diff --git a/protocol-designer/src/components/DeckSetup/index.tsx b/protocol-designer/src/components/DeckSetup/index.tsx index 7e80df35cff..8197275779a 100644 --- a/protocol-designer/src/components/DeckSetup/index.tsx +++ b/protocol-designer/src/components/DeckSetup/index.tsx @@ -535,6 +535,7 @@ export const DeckSetup = (): JSX.Element => { cutoutLocation={fixture.fixtureLocation as Cutout} deckDefinition={deckDef} slotClipColor={darkFill} + showExpansion={fixture.fixtureLocation === 'A1'} fixtureBaseColor={lightFill} /> ))} diff --git a/protocol-designer/src/components/FileSidebar/FileSidebar.tsx b/protocol-designer/src/components/FileSidebar/FileSidebar.tsx index 69d7189e377..7d247725a80 100644 --- a/protocol-designer/src/components/FileSidebar/FileSidebar.tsx +++ b/protocol-designer/src/components/FileSidebar/FileSidebar.tsx @@ -11,11 +11,15 @@ import { resetScrollElements } from '../../ui/steps/utils' import { Portal } from '../portals/MainPageModalPortal' import { useBlockingHint } from '../Hints/useBlockingHint' import { KnowledgeBaseLink } from '../KnowledgeBaseLink' -import { getUnusedEntities } from './utils' +import { + getUnusedEntities, + getUnusedTrash, + getUnusedStagingAreas, +} from './utils' import modalStyles from '../modals/modal.css' import styles from './FileSidebar.css' -import { HintKey } from '../../tutorial' +import type { HintKey } from '../../tutorial' import type { InitialDeckSetup, SavedStepFormState, @@ -28,6 +32,14 @@ import type { RobotType, } from '@opentrons/shared-data' +export interface AdditionalEquipment { + [additionalEquipmentId: string]: { + name: 'gripper' | 'wasteChute' | 'stagingArea' + id: string + location?: string + } +} + export interface Props { loadFile: (event: React.ChangeEvent) => unknown createNewFile?: () => unknown @@ -36,29 +48,28 @@ export interface Props { fileData?: ProtocolFile | null pipettesOnDeck: InitialDeckSetup['pipettes'] modulesOnDeck: InitialDeckSetup['modules'] + labwareOnDeck: InitialDeckSetup['labware'] savedStepForms: SavedStepFormState robotType: RobotType additionalEquipment: AdditionalEquipment } -export interface AdditionalEquipment { - [additionalEquipmentId: string]: { - name: 'gripper' | 'wasteChute' | 'stagingArea' - id: string - location?: string - } -} - interface WarningContent { content: React.ReactNode heading: string } +interface Fixture { + trashBin: boolean + wasteChute: boolean + stagingAreaSlots: string[] +} interface MissingContent { noCommands: boolean pipettesWithoutStep: PipetteOnDeck[] modulesWithoutStep: ModuleOnDeck[] gripperWithoutStep: boolean + fixtureWithoutStep: Fixture } const LOAD_COMMANDS: Array = [ @@ -73,6 +84,7 @@ function getWarningContent({ pipettesWithoutStep, modulesWithoutStep, gripperWithoutStep, + fixtureWithoutStep, }: MissingContent): WarningContent | null { if (noCommands) { return { @@ -164,6 +176,50 @@ function getWarningContent({ heading: i18n.t(`alert.export_warnings.${moduleCase}.heading`), } } + + if (fixtureWithoutStep.trashBin || fixtureWithoutStep.wasteChute) { + return { + content: + (fixtureWithoutStep.trashBin && !fixtureWithoutStep.wasteChute) || + (!fixtureWithoutStep.trashBin && fixtureWithoutStep.wasteChute) ? ( +

+ {i18n.t('alert.export_warnings.unused_trash.body', { + name: fixtureWithoutStep.trashBin ? 'trash bin' : 'waste chute', + })} +

+ ) : ( +

+ {i18n.t('alert.export_warnings.unused_trash.body_both', { + trashName: 'trash bin', + wasteName: 'waste chute', + })} +

+ ), + heading: i18n.t('alert.export_warnings.unused_trash.heading'), + } + } + + if (fixtureWithoutStep.stagingAreaSlots.length > 0) { + return { + content: ( + <> +

+ {i18n.t('alert.export_warnings.unused_staging_area.body1', { + count: fixtureWithoutStep.stagingAreaSlots.length, + slot: fixtureWithoutStep.stagingAreaSlots, + })} +

+

+ {i18n.t('alert.export_warnings.unused_staging_area.body2', { + count: fixtureWithoutStep.stagingAreaSlots.length, + })} +

+ + ), + heading: i18n.t('alert.export_warnings.unused_staging_area.heading'), + } + } + return null } @@ -189,6 +245,7 @@ export function FileSidebar(props: Props): JSX.Element { savedStepForms, robotType, additionalEquipment, + labwareOnDeck, } = props const [ showExportWarningModal, @@ -197,7 +254,21 @@ export function FileSidebar(props: Props): JSX.Element { const isGripperAttached = Object.values(additionalEquipment).some( equipment => equipment?.name === 'gripper' ) + const { trashBinUnused, wasteChuteUnused } = getUnusedTrash( + labwareOnDeck, + // additionalEquipment, + fileData?.commands + ) + const fixtureWithoutStep: Fixture = { + trashBin: trashBinUnused, + // TODO(jr, 10/30/23): wire this up later when we know waste chute commands + wasteChute: wasteChuteUnused, + stagingAreaSlots: getUnusedStagingAreas( + additionalEquipment, + fileData?.commands + ), + } const [showBlockingHint, setShowBlockingHint] = React.useState(false) const cancelModal = (): void => setShowExportWarningModal(false) @@ -232,9 +303,12 @@ export function FileSidebar(props: Props): JSX.Element { const hasWarning = noCommands || - modulesWithoutStep.length || - pipettesWithoutStep.length || - gripperWithoutStep + modulesWithoutStep.length > 0 || + pipettesWithoutStep.length > 0 || + gripperWithoutStep || + fixtureWithoutStep.trashBin || + fixtureWithoutStep.wasteChute || + fixtureWithoutStep.stagingAreaSlots.length > 0 const warning = hasWarning && @@ -243,6 +317,7 @@ export function FileSidebar(props: Props): JSX.Element { pipettesWithoutStep, modulesWithoutStep, gripperWithoutStep, + fixtureWithoutStep, }) const getExportHintContent = (): { diff --git a/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx b/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx index 5728738f02c..8b15ec934f1 100644 --- a/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx +++ b/protocol-designer/src/components/FileSidebar/__tests__/FileSidebar.test.tsx @@ -17,6 +17,7 @@ import { import fixture_tiprack_10_ul from '@opentrons/shared-data/labware/fixtures/2/fixture_tiprack_10_ul.json' import { useBlockingHint } from '../../Hints/useBlockingHint' import { FileSidebar, v7WarningContent } from '../FileSidebar' +import { FLEX_TRASH_DEF_URI } from '@opentrons/step-generation' jest.mock('../../Hints/useBlockingHint') jest.mock('../../../file-data/selectors') @@ -54,6 +55,7 @@ describe('FileSidebar', () => { savedStepForms: {}, robotType: 'OT-2 Standard', additionalEquipment: {}, + labwareOnDeck: {}, } commands = [ @@ -157,6 +159,44 @@ describe('FileSidebar', () => { ) }) + it('warning modal is shown when export is clicked with unused staging area slot', () => { + const stagingArea = 'stagingAreaId' + props.savedStepForms = savedStepForms + // @ts-expect-error(sa, 2021-6-22): props.fileData might be null + props.fileData.commands = commands + props.additionalEquipment = { + [stagingArea]: { name: 'stagingArea', id: stagingArea, location: 'A3' }, + } + + const wrapper = shallow() + const downloadButton = wrapper.find(DeprecatedPrimaryButton).at(0) + downloadButton.simulate('click') + const alertModal = wrapper.find(AlertModal) + + expect(alertModal).toHaveLength(1) + expect(alertModal.prop('heading')).toEqual( + 'One or more staging area slots are unused' + ) + }) + + it('warning modal is shown when export is clicked with unused trash', () => { + props.savedStepForms = savedStepForms + const labwareId = 'mockLabwareId' + // @ts-expect-error(sa, 2021-6-22): props.fileData might be null + props.fileData.commands = commands + props.labwareOnDeck = { + [labwareId]: { labwareDefURI: FLEX_TRASH_DEF_URI, id: labwareId } as any, + } + + const wrapper = shallow() + const downloadButton = wrapper.find(DeprecatedPrimaryButton).at(0) + downloadButton.simulate('click') + const alertModal = wrapper.find(AlertModal) + + expect(alertModal).toHaveLength(1) + expect(alertModal.prop('heading')).toEqual('Unused trash') + }) + it('warning modal is shown when export is clicked with unused gripper', () => { const gripperId = 'gripperId' props.modulesOnDeck = modulesOnDeck diff --git a/protocol-designer/src/components/FileSidebar/index.ts b/protocol-designer/src/components/FileSidebar/index.ts index 85390d1cf49..b25b3f99ae8 100644 --- a/protocol-designer/src/components/FileSidebar/index.ts +++ b/protocol-designer/src/components/FileSidebar/index.ts @@ -28,6 +28,7 @@ interface SP { savedStepForms: SavedStepFormState robotType: RobotType additionalEquipment: AdditionalEquipment + labwareOnDeck: InitialDeckSetup['labware'] } export const FileSidebar = connect( mapStateToProps, @@ -54,6 +55,7 @@ function mapStateToProps(state: BaseState): SP { // Ignore clicking 'CREATE NEW' button in these cases _canCreateNew: !selectors.getNewProtocolModal(state), _hasUnsavedChanges: loadFileSelectors.getHasUnsavedChanges(state), + labwareOnDeck: initialDeckSetup.labware, } } @@ -73,6 +75,7 @@ function mergeProps( savedStepForms, robotType, additionalEquipment, + labwareOnDeck, } = stateProps const { dispatch } = dispatchProps return { @@ -95,5 +98,6 @@ function mergeProps( savedStepForms, robotType, additionalEquipment, + labwareOnDeck, } } diff --git a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedStagingAreas.ts b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedStagingAreas.ts new file mode 100644 index 00000000000..333a6aee456 --- /dev/null +++ b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedStagingAreas.ts @@ -0,0 +1,45 @@ +import { getUnusedStagingAreas } from '../getUnusedStagingAreas' +import type { CreateCommand } from '@opentrons/shared-data' +import type { AdditionalEquipment } from '../../FileSidebar' + +describe('getUnusedStagingAreas', () => { + it('returns true for unused staging area', () => { + const stagingArea = 'stagingAreaId' + const mockAdditionalEquipment = { + [stagingArea]: { name: 'stagingArea', id: stagingArea, location: 'A3' }, + } as AdditionalEquipment + + expect(getUnusedStagingAreas(mockAdditionalEquipment, [])).toEqual(['A4']) + }) + it('returns true for multi unused staging areas', () => { + const stagingArea = 'stagingAreaId' + const stagingArea2 = 'stagingAreaId2' + const mockAdditionalEquipment = { + [stagingArea]: { name: 'stagingArea', id: stagingArea, location: 'A3' }, + [stagingArea2]: { name: 'stagingArea', id: stagingArea2, location: 'B3' }, + } as AdditionalEquipment + + expect(getUnusedStagingAreas(mockAdditionalEquipment, [])).toEqual([ + 'A4', + 'B4', + ]) + }) + it('returns false for unused staging area', () => { + const stagingArea = 'stagingAreaId' + const mockAdditionalEquipment = { + [stagingArea]: { name: 'stagingArea', id: stagingArea, location: 'A3' }, + } as AdditionalEquipment + const mockCommand = ([ + { + stagingArea: { + commandType: 'loadLabware', + params: { location: { slotName: 'A4' } }, + }, + }, + ] as unknown) as CreateCommand[] + + expect( + getUnusedStagingAreas(mockAdditionalEquipment, mockCommand) + ).toEqual(['A4']) + }) +}) diff --git a/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedTrash.test.ts b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedTrash.test.ts new file mode 100644 index 00000000000..474a2f9d733 --- /dev/null +++ b/protocol-designer/src/components/FileSidebar/utils/__tests__/getUnusedTrash.test.ts @@ -0,0 +1,38 @@ +import { FLEX_TRASH_DEF_URI } from '../../../../constants' +import { getUnusedTrash } from '../getUnusedTrash' +import type { CreateCommand } from '@opentrons/shared-data' +import type { InitialDeckSetup } from '../../../../step-forms' + +describe('getUnusedTrash', () => { + it('returns true for unused trash bin', () => { + const labwareId = 'mockLabwareId' + const mockTrash = ({ + [labwareId]: { labwareDefURI: FLEX_TRASH_DEF_URI, id: labwareId }, + } as unknown) as InitialDeckSetup['labware'] + + expect(getUnusedTrash(mockTrash, [])).toEqual({ + trashBinUnused: true, + wasteChuteUnused: false, + }) + }) + it('returns false for unused trash bin', () => { + const labwareId = 'mockLabwareId' + const mockTrash = ({ + [labwareId]: { labwareDefURI: FLEX_TRASH_DEF_URI, id: labwareId }, + } as unknown) as InitialDeckSetup['labware'] + const mockCommand = ([ + { + labwareId: { + commandType: 'dropTip', + params: { labwareId: labwareId }, + }, + }, + ] as unknown) as CreateCommand[] + + expect(getUnusedTrash(mockTrash, mockCommand)).toEqual({ + trashBinUnused: true, + wasteChuteUnused: false, + }) + }) + // TODO(jr, 10/30/23): add test coverage for waste chute +}) diff --git a/protocol-designer/src/components/FileSidebar/utils/getUnusedStagingAreas.ts b/protocol-designer/src/components/FileSidebar/utils/getUnusedStagingAreas.ts new file mode 100644 index 00000000000..a701858a9eb --- /dev/null +++ b/protocol-designer/src/components/FileSidebar/utils/getUnusedStagingAreas.ts @@ -0,0 +1,49 @@ +import type { CreateCommand } from '@opentrons/shared-data' +import type { AdditionalEquipment } from '../FileSidebar' + +export const getUnusedStagingAreas = ( + additionalEquipment: AdditionalEquipment, + commands?: CreateCommand[] +): string[] => { + const stagingAreaSlots = Object.values(additionalEquipment) + .filter(equipment => equipment?.name === 'stagingArea') + .map(equipment => { + if (equipment.location == null) { + console.error( + `expected to find staging area slot location with id ${equipment.id} but could not.` + ) + } + return equipment.location ?? '' + }) + + const corresponding4thColumnSlots = stagingAreaSlots.map(slot => { + const letter = slot.charAt(0) + const correspondingLocation = stagingAreaSlots.find(slot => + slot.startsWith(letter) + ) + if (correspondingLocation) { + return letter + '4' + } + + return slot + }) + + const stagingAreaCommandSlots: string[] = corresponding4thColumnSlots.filter( + location => + commands?.filter( + command => + (command.commandType === 'loadLabware' && + command.params.location !== 'offDeck' && + 'slotName' in command.params.location && + command.params.location.slotName === location) || + (command.commandType === 'moveLabware' && + command.params.newLocation !== 'offDeck' && + 'slotName' in command.params.newLocation && + command.params.newLocation.slotName === location) + ) + ? location + : null + ) + + return stagingAreaCommandSlots +} diff --git a/protocol-designer/src/components/FileSidebar/utils/getUnusedTrash.ts b/protocol-designer/src/components/FileSidebar/utils/getUnusedTrash.ts new file mode 100644 index 00000000000..2585b2c75eb --- /dev/null +++ b/protocol-designer/src/components/FileSidebar/utils/getUnusedTrash.ts @@ -0,0 +1,39 @@ +import { + FLEX_TRASH_DEF_URI, + OT_2_TRASH_DEF_URI, +} from '@opentrons/step-generation' + +import type { CreateCommand } from '@opentrons/shared-data' +import type { InitialDeckSetup } from '../../../step-forms' + +interface UnusedTrash { + trashBinUnused: boolean + wasteChuteUnused: boolean +} + +// TODO(jr, 10/30/23): plug in waste chute logic when we know the commands! +export const getUnusedTrash = ( + labwareOnDeck: InitialDeckSetup['labware'], + commands?: CreateCommand[] +): UnusedTrash => { + const trashBin = Object.values(labwareOnDeck).find( + labware => + labware.labwareDefURI === FLEX_TRASH_DEF_URI || + labware.labwareDefURI === OT_2_TRASH_DEF_URI + ) + + const hasTrashBinCommands = + trashBin != null + ? commands?.some( + command => + (command.commandType === 'dropTip' && + command.params.labwareId === trashBin.id) || + (command.commandType === 'dispense' && + command.params.labwareId === trashBin.id) + ) + : null + return { + trashBinUnused: trashBin != null && !hasTrashBinCommands, + wasteChuteUnused: false, + } +} diff --git a/protocol-designer/src/components/FileSidebar/utils/index.ts b/protocol-designer/src/components/FileSidebar/utils/index.ts index 1f9e4ed1b44..69cca896b00 100644 --- a/protocol-designer/src/components/FileSidebar/utils/index.ts +++ b/protocol-designer/src/components/FileSidebar/utils/index.ts @@ -1 +1,3 @@ export * from './getUnusedEntities' +export * from './getUnusedStagingAreas' +export * from './getUnusedTrash' diff --git a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx index abf692ecda6..69a22b67424 100644 --- a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx +++ b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx @@ -19,6 +19,9 @@ import { MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM, LabwareDefinition2, ModuleType, + ModuleModel, + getModuleType, + THERMOCYCLER_MODULE_V2, } from '@opentrons/shared-data' import { i18n } from '../../localization' import { SPAN7_8_10_11_SLOT } from '../../constants' @@ -35,8 +38,9 @@ import { KnowledgeBaseLink } from '../KnowledgeBaseLink' import { LabwareItem } from './LabwareItem' import { LabwarePreview } from './LabwarePreview' import styles from './styles.css' -import { DeckSlot } from '../../types' -import { LabwareDefByDefURI } from '../../labware-defs' + +import type { DeckSlot } from '../../types' +import type { LabwareDefByDefURI } from '../../labware-defs' export interface Props { onClose: (e?: any) => unknown @@ -47,8 +51,8 @@ export interface Props { slot?: DeckSlot | null /** if adding to a module, the slot of the parent (for display) */ parentSlot?: DeckSlot | null - /** if adding to a module, the module's type */ - moduleType?: ModuleType | null + /** if adding to a module, the module's model */ + moduleModel?: ModuleModel | null /** tipracks that may be added to deck (depends on pipette<>tiprack assignment) */ permittedTipracks: string[] isNextToHeaterShaker: boolean @@ -87,7 +91,10 @@ const RECOMMENDED_LABWARE_BY_MODULE: { [K in ModuleType]: string[] } = { 'nest_96_wellplate_2ml_deep', 'opentrons_96_wellplate_200ul_pcr_full_skirt', ], - [THERMOCYCLER_MODULE_TYPE]: ['nest_96_wellplate_100ul_pcr_full_skirt'], + [THERMOCYCLER_MODULE_TYPE]: [ + 'nest_96_wellplate_100ul_pcr_full_skirt', + 'opentrons_96_wellplate_200ul_pcr_full_skirt', + ], [HEATERSHAKER_MODULE_TYPE]: [ 'opentrons_96_deep_well_adapter', 'opentrons_96_flat_bottom_adapter', @@ -103,14 +110,23 @@ const RECOMMENDED_LABWARE_BY_MODULE: { [K in ModuleType]: string[] } = { export const getLabwareIsRecommended = ( def: LabwareDefinition2, - moduleType?: ModuleType | null -): boolean => - moduleType - ? RECOMMENDED_LABWARE_BY_MODULE[moduleType].includes( - def.parameters.loadName - ) - : false - + moduleModel?: ModuleModel | null +): boolean => { + // special-casing the thermocycler module V2 recommended labware + // since its different from V1 + const moduleType = moduleModel != null ? getModuleType(moduleModel) : null + if (moduleModel === THERMOCYCLER_MODULE_V2) { + return ( + def.parameters.loadName === 'opentrons_96_wellplate_200ul_pcr_full_skirt' + ) + } else { + return moduleType != null + ? RECOMMENDED_LABWARE_BY_MODULE[moduleType].includes( + def.parameters.loadName + ) + : false + } +} export const LabwareSelectionModal = (props: Props): JSX.Element | null => { const { customLabwareDefs, @@ -119,13 +135,14 @@ export const LabwareSelectionModal = (props: Props): JSX.Element | null => { onUploadLabware, slot, parentSlot, - moduleType, + moduleModel, selectLabware, isNextToHeaterShaker, adapterLoadName, has96Channel, } = props const defs = getOnlyLatestDefs() + const moduleType = moduleModel != null ? getModuleType(moduleModel) : null const URIs = Object.keys(defs) const [selectedCategory, setSelectedCategory] = React.useState( null @@ -198,7 +215,7 @@ export const LabwareSelectionModal = (props: Props): JSX.Element | null => { labwareDef.parameters.loadName === ADAPTER_96_CHANNEL return ( (filterRecommended && - !getLabwareIsRecommended(labwareDef, moduleType)) || + !getLabwareIsRecommended(labwareDef, moduleModel)) || (filterHeight && getIsLabwareAboveHeight( labwareDef, @@ -358,7 +375,7 @@ export const LabwareSelectionModal = (props: Props): JSX.Element | null => { typeof LabwarePreview >['moduleCompatibility'] = null if (previewedLabware && moduleType) { - if (getLabwareIsRecommended(previewedLabware, moduleType)) { + if (getLabwareIsRecommended(previewedLabware, moduleModel)) { moduleCompatibility = 'recommended' } else if (getLabwareCompatible(previewedLabware)) { moduleCompatibility = 'potentiallyCompatible' @@ -425,7 +442,7 @@ export const LabwareSelectionModal = (props: Props): JSX.Element | null => { moduleOnDeck.id === slot)) || null const parentSlot = parentModule != null ? parentModule.slot : null - const moduleType = parentModule != null ? parentModule.type : null + const moduleModel = parentModule != null ? parentModule.model : null const isNextToHeaterShaker = initialModules.some( hardwareModule => hardwareModule.type === HEATERSHAKER_MODULE_TYPE && @@ -63,7 +63,7 @@ function mapStateToProps(state: BaseState): SP { customLabwareDefs: labwareDefSelectors.getCustomLabwareDefsByURI(state), slot, parentSlot, - moduleType, + moduleModel, isNextToHeaterShaker, has96Channel, adapterDefUri: has96Channel ? adapter96ChannelDefUri : undefined, diff --git a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx index 62e6587177f..8d10f948467 100644 --- a/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx +++ b/protocol-designer/src/components/StepEditForm/fields/WellOrderField/index.tsx @@ -89,16 +89,6 @@ export const WellOrderField = (props: WellOrderFieldProps): JSX.Element => {
- {firstValue != null && secondValue != null ? ( { )}
+ ) } diff --git a/protocol-designer/src/file-data/__tests__/createFile.test.ts b/protocol-designer/src/file-data/__tests__/createFile.test.ts index d7b1d0bebe3..d3677c91f42 100644 --- a/protocol-designer/src/file-data/__tests__/createFile.test.ts +++ b/protocol-designer/src/file-data/__tests__/createFile.test.ts @@ -1,6 +1,6 @@ import Ajv from 'ajv' -import protocolV7Schema from '@opentrons/shared-data/protocol/schemas/7.json' -import commandV7Schema from '@opentrons/shared-data/command/schemas/7.json' +import protocolV8Schema from '@opentrons/shared-data/protocol/schemas/8.json' +import commandV8Schema from '@opentrons/shared-data/command/schemas/8.json' import labwareV2Schema from '@opentrons/shared-data/labware/schemas/2.json' import fixture_12_trough from '@opentrons/shared-data/labware/fixtures/2/fixture_12_trough.json' import fixture_96_plate from '@opentrons/shared-data/labware/fixtures/2/fixture_96_plate.json' @@ -42,11 +42,11 @@ const ajv = new Ajv({ jsonPointers: true, }) // v3 and v4 protocol schema contain reference to v2 labware schema, so give AJV access to it -// and add v7 command schema +// and add v8 command schema ajv.addSchema(labwareV2Schema) -ajv.addSchema(commandV7Schema) +ajv.addSchema(commandV8Schema) -const validateProtocol = ajv.compile(protocolV7Schema) +const validateProtocol = ajv.compile(protocolV8Schema) const expectResultToMatchSchema = (result: any): void => { const valid = validateProtocol(result) @@ -67,7 +67,7 @@ describe('createFile selector', () => { afterEach(() => { jest.restoreAllMocks() }) - it('should return a schema-valid JSON V7 protocol', () => { + it('should return a schema-valid JSON V8 protocol', () => { // @ts-expect-error(sa, 2021-6-15): resultFunc not part of Selector type const result = createFile.resultFunc( fileMetadata, @@ -84,8 +84,8 @@ describe('createFile selector', () => { pipetteEntities, labwareNicknamesById, labwareDefsByURI, - // TODO(jr, 10/3/23): add additionalEquipmentEntities when the schemaV7 supports - // loadFixture + // TODO(jr, 10/3/23): add additionalEquipmentEntities when the schemaV8 supports + // loadAddressableArea {} ) expectResultToMatchSchema(result) diff --git a/protocol-designer/src/file-data/selectors/fileCreator.ts b/protocol-designer/src/file-data/selectors/fileCreator.ts index e530cabb52f..ed5aaa56638 100644 --- a/protocol-designer/src/file-data/selectors/fileCreator.ts +++ b/protocol-designer/src/file-data/selectors/fileCreator.ts @@ -10,9 +10,7 @@ import { OT2_STANDARD_DECKID, OT2_STANDARD_MODEL, FLEX_STANDARD_DECKID, - PipetteName, SPAN7_8_10_11_SLOT, - Cutout, } from '@opentrons/shared-data' import { selectors as dismissSelectors } from '../../dismiss' import { @@ -23,7 +21,10 @@ import { uuid } from '../../utils' import { selectors as ingredSelectors } from '../../labware-ingred/selectors' import { selectors as stepFormSelectors } from '../../step-forms' import { selectors as uiLabwareSelectors } from '../../ui/labware' -import { getLoadLiquidCommands } from '../../load-file/migration/utils/getLoadLiquidCommands' +import { + DesignerApplicationData, + getLoadLiquidCommands, +} from '../../load-file/migration/utils/getLoadLiquidCommands' import { swatchColors } from '../../components/swatchColors' import { getAdditionalEquipmentEntities } from '../../step-forms/selectors' import { @@ -43,15 +44,22 @@ import type { AdditionalEquipmentEntity, } from '@opentrons/step-generation' import type { + CommandAnnotationV1Mixin, + CommandV8Mixin, CreateCommand, - ProtocolFile, -} from '@opentrons/shared-data/protocol/types/schemaV7' -import type { + Cutout, + LabwareV2Mixin, + LiquidV1Mixin, + LoadFixtureCreateCommand, LoadLabwareCreateCommand, LoadModuleCreateCommand, LoadPipetteCreateCommand, - LoadFixtureCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/setup' + OT2RobotMixin, + OT3RobotMixin, + PipetteName, + ProtocolBase, + ProtocolFile, +} from '@opentrons/shared-data' import type { Selector } from '../../types' // TODO: BC: 2018-02-21 uncomment this assert, causes test failures @@ -292,7 +300,7 @@ export const createFile: Selector = createSelector( [] ) - // TODO(jr, 10/3/23): add standard slot, and trash bin loadFixtures + // TODO(jr, 10/31/23): update to loadAddressableArea const loadFixtureCommands = reduce< AdditionalEquipmentEntity, LoadFixtureCreateCommand[] @@ -363,7 +371,44 @@ export const createFile: Selector = createSelector( const commands = [...loadCommands, ...nonLoadCommands] - const protocolFile = { + const flexDeckSpec: OT3RobotMixin = { + robot: { + model: FLEX_ROBOT_TYPE, + deckId: FLEX_STANDARD_DECKID, + }, + } + const ot2DeckSpec: OT2RobotMixin = { + robot: { + model: OT2_STANDARD_MODEL, + deckId: OT2_STANDARD_DECKID, + }, + } + const deckStructure = + robotType === FLEX_ROBOT_TYPE ? flexDeckSpec : ot2DeckSpec + + const labwareV2Mixin: LabwareV2Mixin = { + labwareDefinitionSchemaId: 'opentronsLabwareSchemaV2', + labwareDefinitions, + } + + const liquidV1Mixin: LiquidV1Mixin = { + liquidSchemaId: 'opentronsLiquidSchemaV1', + liquids, + } + + const commandv8Mixin: CommandV8Mixin = { + commandSchemaId: 'opentronsCommandSchemaV8', + commands, + } + + const commandAnnotionaV1Mixin: CommandAnnotationV1Mixin = { + commandAnnotationSchemaId: 'opentronsCommandAnnotationSchemaV1', + commandAnnotations: [], + } + + const protocolBase: ProtocolBase = { + $otSharedSchema: '#/protocol/schemas/8', + schemaVersion: 8, metadata: { protocolName: name, author, @@ -376,18 +421,15 @@ export const createFile: Selector = createSelector( tags: [], }, designerApplication, - robot: - robotType === FLEX_ROBOT_TYPE - ? { model: FLEX_ROBOT_TYPE, deckId: FLEX_STANDARD_DECKID } - : { model: OT2_STANDARD_MODEL, deckId: OT2_STANDARD_DECKID }, - liquids, - labwareDefinitions, } + return { - ...protocolFile, - $otSharedSchema: '#/protocol/schemas/7', - schemaVersion: 7, - commands, + ...protocolBase, + ...deckStructure, + ...labwareV2Mixin, + ...liquidV1Mixin, + ...commandv8Mixin, + ...commandAnnotionaV1Mixin, } } ) diff --git a/protocol-designer/src/load-file/migration/7_1_0.ts b/protocol-designer/src/load-file/migration/7_1_0.ts deleted file mode 100644 index d3351c9f4bf..00000000000 --- a/protocol-designer/src/load-file/migration/7_1_0.ts +++ /dev/null @@ -1,130 +0,0 @@ -import mapValues from 'lodash/mapValues' -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { getOnlyLatestDefs } from '../../labware-defs' -import { uuid } from '../../utils' -import { - FLEX_TRASH_DEF_URI, - INITIAL_DECK_SETUP_STEP_ID, - OT_2_TRASH_DEF_URI, -} from '../../constants' -import type { - LoadLabwareCreateCommand, - ProtocolFile, -} from '@opentrons/shared-data/protocol/types/schemaV7' -import type { DesignerApplicationData } from './utils/getLoadLiquidCommands' - -// NOTE: this migration updates fixed trash by treating it as an entity -// additionally, drop tip location is now selectable -const PD_VERSION = '7.1.0' - -interface LabwareLocationUpdate { - [id: string]: string -} - -export const migrateFile = ( - appData: ProtocolFile -): ProtocolFile => { - const { designerApplication, robot, commands } = appData - const labwareLocationUpdate: LabwareLocationUpdate = - designerApplication?.data?.savedStepForms[INITIAL_DECK_SETUP_STEP_ID] - .labwareLocationUpdate - const allLatestDefs = getOnlyLatestDefs() - - const robotType = robot.model - const trashSlot = robotType === FLEX_ROBOT_TYPE ? 'A3' : '12' - const trashDefUri = - robotType === FLEX_ROBOT_TYPE ? FLEX_TRASH_DEF_URI : OT_2_TRASH_DEF_URI - - const trashDefinition = allLatestDefs[trashDefUri] - const trashId = `${uuid()}:${trashDefUri}` - - const trashLoadCommand = [ - { - key: uuid(), - commandType: 'loadLabware', - params: { - location: { slotName: trashSlot }, - version: 1, - namespace: 'opentrons', - loadName: trashDefinition.parameters.loadName, - displayName: trashDefinition.metadata.displayName, - labwareId: trashId, - }, - }, - ] as LoadLabwareCreateCommand[] - - const newLabwareLocationUpdate: LabwareLocationUpdate = Object.keys( - labwareLocationUpdate - ).reduce((acc: LabwareLocationUpdate, labwareId: string) => { - if (labwareId === 'fixedTrash') { - acc[trashId] = trashSlot - } else { - acc[labwareId] = labwareLocationUpdate[labwareId] - } - return acc - }, {}) - - const migrateSavedStepForms = ( - savedStepForms: Record - ): Record => { - return mapValues(savedStepForms, stepForm => { - if (stepForm.stepType === 'moveLiquid') { - return { - ...stepForm, - blowout_location: - stepForm.blowout_location === 'fixedTrash' - ? trashId - : stepForm.blowout_location, - dropTip_location: trashId, - } - } else if (stepForm.stepType === 'mix') { - return { - ...stepForm, - blowout_location: - stepForm.blowout_location === 'fixedTrash' - ? trashId - : stepForm.blowout_location, - dropTip_location: trashId, - } - } - - return stepForm - }) - } - - const filteredSavedStepForms = Object.fromEntries( - Object.entries( - appData.designerApplication?.data?.savedStepForms ?? {} - ).filter(([key, value]) => key !== INITIAL_DECK_SETUP_STEP_ID) - ) - const newFilteredSavedStepForms = migrateSavedStepForms( - filteredSavedStepForms - ) - - return { - ...appData, - designerApplication: { - ...appData.designerApplication, - version: PD_VERSION, - data: { - ...appData.designerApplication?.data, - savedStepForms: { - [INITIAL_DECK_SETUP_STEP_ID]: { - ...appData.designerApplication?.data?.savedStepForms[ - INITIAL_DECK_SETUP_STEP_ID - ], - labwareLocationUpdate: { - ...newLabwareLocationUpdate, - }, - }, - ...newFilteredSavedStepForms, - }, - }, - }, - labwareDefinitions: { - ...{ [trashId]: trashDefinition }, - ...appData.labwareDefinitions, - }, - commands: [...commands, ...trashLoadCommand], - } -} diff --git a/protocol-designer/src/load-file/migration/8_0_0.ts b/protocol-designer/src/load-file/migration/8_0_0.ts new file mode 100644 index 00000000000..c397cea13e9 --- /dev/null +++ b/protocol-designer/src/load-file/migration/8_0_0.ts @@ -0,0 +1,239 @@ +import mapValues from 'lodash/mapValues' +import { + FLEX_ROBOT_TYPE, + FLEX_STANDARD_DECKID, + OT2_STANDARD_DECKID, + OT2_STANDARD_MODEL, +} from '@opentrons/shared-data' +import { getOnlyLatestDefs } from '../../labware-defs' +import { uuid } from '../../utils' +import { + FLEX_TRASH_DEF_URI, + INITIAL_DECK_SETUP_STEP_ID, + OT_2_TRASH_DEF_URI, +} from '../../constants' +import type { ProtocolFileV7 } from '@opentrons/shared-data' +import type { + CommandAnnotationV1Mixin, + CommandV8Mixin, + LabwareV2Mixin, + LiquidV1Mixin, + LoadLabwareCreateCommand, + OT2RobotMixin, + OT3RobotMixin, + ProtocolBase, + ProtocolFile, +} from '@opentrons/shared-data/protocol/types/schemaV8' +import type { DesignerApplicationData } from './utils/getLoadLiquidCommands' + +// NOTE: this migration is to schema v8 and updates fixed trash by +// treating it as an entity. Additionally, drop tip location is now selectable +const PD_VERSION = '8.0.0' +const SCHEMA_VERSION = 8 +interface LabwareLocationUpdate { + [id: string]: string +} + +export const migrateFile = ( + appData: ProtocolFileV7 +): ProtocolFile => { + const { + designerApplication, + commands, + robot, + labwareDefinitions, + liquids, + } = appData + + if (designerApplication == null || designerApplication.data == null) { + throw Error('The designerApplication key in your file is corrupt.') + } + + const labwareLocationUpdate: LabwareLocationUpdate = + designerApplication.data.savedStepForms[INITIAL_DECK_SETUP_STEP_ID] + .labwareLocationUpdate + const allLatestDefs = getOnlyLatestDefs() + + const robotType = robot.model + const trashSlot = robotType === FLEX_ROBOT_TYPE ? 'A3' : '12' + const trashDefUri = + robotType === FLEX_ROBOT_TYPE ? FLEX_TRASH_DEF_URI : OT_2_TRASH_DEF_URI + + const trashDefinition = allLatestDefs[trashDefUri] + const trashId = `${uuid()}:${trashDefUri}` + + const trashLoadCommand = [ + { + key: uuid(), + commandType: 'loadLabware', + params: { + location: { slotName: trashSlot }, + version: 1, + namespace: 'opentrons', + loadName: trashDefinition.parameters.loadName, + displayName: trashDefinition.metadata.displayName, + labwareId: trashId, + }, + }, + ] as LoadLabwareCreateCommand[] + + const newLabwareLocationUpdate: LabwareLocationUpdate = Object.keys( + labwareLocationUpdate + ).reduce((acc: LabwareLocationUpdate, labwareId: string) => { + if (labwareId === 'fixedTrash') { + acc[trashId] = trashSlot + } else { + acc[labwareId] = labwareLocationUpdate[labwareId] + } + return acc + }, {}) + + const migrateSavedStepForms = ( + savedStepForms: Record + ): Record => { + return mapValues(savedStepForms, stepForm => { + const sharedParams = { + blowout_location: + stepForm.blowout_location === 'fixedTrash' + ? trashId + : stepForm.blowout_location, + dropTip_location: trashId, + } + + if (stepForm.stepType === 'moveLiquid') { + return { + ...stepForm, + aspirate_labware: + stepForm.aspirate_labware === 'fixedTrash' + ? trashId + : stepForm.aspirate_labware, + dispense_labware: + stepForm.dispense_labware === 'fixedTrash' + ? trashId + : stepForm.dispense_labware, + ...sharedParams, + } + } else if (stepForm.stepType === 'mix') { + return { + ...stepForm, + labware: + stepForm.labware === 'fixedTrash' ? trashId : stepForm.labware, + ...sharedParams, + } + } + + return stepForm + }) + } + + const filteredSavedStepForms = Object.fromEntries( + Object.entries( + appData.designerApplication?.data?.savedStepForms ?? {} + ).filter(([key, value]) => key !== INITIAL_DECK_SETUP_STEP_ID) + ) + const newFilteredSavedStepForms = migrateSavedStepForms( + filteredSavedStepForms + ) + + const loadLabwareCommands: LoadLabwareCreateCommand[] = commands + .filter( + (command): command is LoadLabwareCreateCommand => + command.commandType === 'loadLabware' + ) + .map(command => { + // protocols that do multiple migrations through 7.0.0 have a loadName === definitionURI + // this ternary below fixes that + const loadName = + labwareDefinitions[command.params.loadName] != null + ? labwareDefinitions[command.params.loadName].parameters.loadName + : command.params.loadName + return { + ...command, + params: { + ...command.params, + loadName, + }, + } + }) + + const migratedCommandsV8 = commands.filter( + command => command.commandType !== 'loadLabware' + ) + + const flexDeckSpec: OT3RobotMixin = { + robot: { + model: FLEX_ROBOT_TYPE, + deckId: FLEX_STANDARD_DECKID, + }, + } + const ot2DeckSpec: OT2RobotMixin = { + robot: { + model: OT2_STANDARD_MODEL, + deckId: OT2_STANDARD_DECKID, + }, + } + const deckStructure = + robotType === FLEX_ROBOT_TYPE ? flexDeckSpec : ot2DeckSpec + + const protocolBase: ProtocolBase = { + $otSharedSchema: '#/protocol/schemas/8', + schemaVersion: SCHEMA_VERSION, + metadata: { + ...appData.metadata, + }, + designerApplication: { + ...appData.designerApplication, + version: PD_VERSION, + data: { + ...designerApplication.data, + savedStepForms: { + [INITIAL_DECK_SETUP_STEP_ID]: { + ...designerApplication.data.savedStepForms[ + INITIAL_DECK_SETUP_STEP_ID + ], + labwareLocationUpdate: { + ...newLabwareLocationUpdate, + }, + }, + ...newFilteredSavedStepForms, + }, + }, + }, + } + + const labwareV2Mixin: LabwareV2Mixin = { + labwareDefinitionSchemaId: 'opentronsLabwareSchemaV2', + labwareDefinitions: { + ...{ [trashDefUri]: trashDefinition }, + ...appData.labwareDefinitions, + }, + } + + const liquidV1Mixin: LiquidV1Mixin = { + liquidSchemaId: 'opentronsLiquidSchemaV1', + liquids, + } + + const commandv8Mixin: CommandV8Mixin = { + commandSchemaId: 'opentronsCommandSchemaV8', + commands: [ + ...migratedCommandsV8, + ...loadLabwareCommands, + ...trashLoadCommand, + ], + } + + const commandAnnotionaV1Mixin: CommandAnnotationV1Mixin = { + commandAnnotationSchemaId: 'opentronsCommandAnnotationSchemaV1', + commandAnnotations: [], + } + + return { + ...protocolBase, + ...deckStructure, + ...labwareV2Mixin, + ...liquidV1Mixin, + ...commandv8Mixin, + ...commandAnnotionaV1Mixin, + } +} diff --git a/protocol-designer/src/load-file/migration/__tests__/7_1_0.test.ts b/protocol-designer/src/load-file/migration/__tests__/8_0_0.test.ts similarity index 87% rename from protocol-designer/src/load-file/migration/__tests__/7_1_0.test.ts rename to protocol-designer/src/load-file/migration/__tests__/8_0_0.test.ts index 581805bc6b5..c87755ae728 100644 --- a/protocol-designer/src/load-file/migration/__tests__/7_1_0.test.ts +++ b/protocol-designer/src/load-file/migration/__tests__/8_0_0.test.ts @@ -1,19 +1,19 @@ -import { migrateFile } from '../7_1_0' +import { migrateFile } from '../8_0_0' import fixture_trash from '@opentrons/shared-data/labware/fixtures/2/fixture_trash.json' import _oldDoItAllProtocol from '../../../../fixtures/protocol/7/doItAllV7.json' import { getOnlyLatestDefs, LabwareDefByDefURI } from '../../../labware-defs' -import type { ProtocolFile } from '@opentrons/shared-data' +import type { ProtocolFileV7 } from '@opentrons/shared-data' jest.mock('../../../labware-defs') -const oldDoItAllProtocol = (_oldDoItAllProtocol as unknown) as ProtocolFile +const oldDoItAllProtocol = (_oldDoItAllProtocol as unknown) as ProtocolFileV7 const mockGetOnlyLatestDefs = getOnlyLatestDefs as jest.MockedFunction< typeof getOnlyLatestDefs > const trashUri = 'opentrons/opentrons_1_trash_3200ml_fixed/1' -describe('v7.1 migration', () => { +describe('v8.0 migration', () => { beforeEach(() => { mockGetOnlyLatestDefs.mockReturnValue({ [trashUri]: fixture_trash, @@ -38,21 +38,6 @@ describe('v7.1 migration', () => { version: 1, }, }, - { - commandType: 'loadLabware', - key: expect.any(String), - params: { - displayName: 'Opentrons Fixed Trash', - labwareId: - '89d0e1b6-4d51-447b-b01b-3726a1f54137:opentrons/opentrons_1_trash_3200ml_fixed/1', - loadName: 'opentrons_1_trash_3200ml_fixed', - location: { - slotName: 'A3', - }, - namespace: 'opentrons', - version: 1, - }, - }, { commandType: 'loadLabware', key: expect.any(String), diff --git a/protocol-designer/src/load-file/migration/index.ts b/protocol-designer/src/load-file/migration/index.ts index 29490da4d4e..60d5614a62a 100644 --- a/protocol-designer/src/load-file/migration/index.ts +++ b/protocol-designer/src/load-file/migration/index.ts @@ -10,7 +10,7 @@ import { migrateFile as migrateFileFiveOne } from './5_1_0' import { migrateFile as migrateFileFiveTwo } from './5_2_0' import { migrateFile as migrateFileSix } from './6_0_0' import { migrateFile as migrateFileSeven } from './7_0_0' -import { migrateFile as migrateFileSevenOne } from './7_1_0' +import { migrateFile as migrateFileEight } from './8_0_0' export const OLDEST_MIGRATEABLE_VERSION = '1.0.0' type Version = string @@ -43,7 +43,7 @@ const allMigrationsByVersion: MigrationsByVersion = { // @ts-expect-error '7.0.0': migrateFileSeven, // @ts-expect-error - '7.1.0': migrateFileSevenOne, + '8.0.0': migrateFileEight, } export const migration = ( file: any diff --git a/protocol-designer/src/load-file/migration/utils/getLoadLiquidCommands.ts b/protocol-designer/src/load-file/migration/utils/getLoadLiquidCommands.ts index fe11d129b8f..e5d9da09567 100644 --- a/protocol-designer/src/load-file/migration/utils/getLoadLiquidCommands.ts +++ b/protocol-designer/src/load-file/migration/utils/getLoadLiquidCommands.ts @@ -6,8 +6,8 @@ export interface DesignerApplicationData { ingredients: Record< string, { - name: string | null | undefined - description: string | null | undefined + name?: string | null + description?: string | null serialize: boolean } > diff --git a/protocol-designer/src/localization/en/alert.json b/protocol-designer/src/localization/en/alert.json index 8c514cd8dc6..c7ffb8499df 100644 --- a/protocol-designer/src/localization/en/alert.json +++ b/protocol-designer/src/localization/en/alert.json @@ -241,6 +241,18 @@ "heading": "Unused gripper", "body1": "The Flex Gripper specified in your protocol is not currently used in any step. In order to run this protocol you will need to connect it to your robot.", "body2": "If you don't intend to use the Flex Gripper, please consider removing it from your protocol." + }, + "unused_trash": { + "heading": "Unused trash", + "body": "The {{name}} specified in your protocol is not currently used in any step. It will not appear as a needed item on the deck when uploaded to the app.", + "body_both": "The {{trashName}} and {{wasteName}} specified in your protocol are not currently used in any step. They will not appear as a needed item on the deck when uploaded to the app." + }, + "unused_staging_area": { + "heading": "One or more staging area slots are unused", + "body1_plural": "The {{count}} staging area slots in [{{slot}}] in your protocol are not currently in use. They will not appear as a needed item on the deck when uploaded to the app.", + "body1": "The staging area slot in {{slot}} in your protocol is not currently in use. It will not appear as a needed item on the deck when uploaded to the app.", + "body2_plural": "If you don't intend to use these staging area slots, please consider removing it from your protocol.", + "body2": "If you don't intend to use this staging area slot, please consider removing it from your protocol." } }, "crash": { diff --git a/protocol-designer/src/step-forms/reducers/index.ts b/protocol-designer/src/step-forms/reducers/index.ts index e5d1fecc018..82e0021ea29 100644 --- a/protocol-designer/src/step-forms/reducers/index.ts +++ b/protocol-designer/src/step-forms/reducers/index.ts @@ -1153,13 +1153,22 @@ export const labwareInvariantProperties: Reducer< const labware = { ...loadLabwareCommands.reduce( (acc: NormalizedLabwareById, command: LoadLabwareCreateCommand) => { - const { labwareId, loadName } = command.params - const defUri = labwareId?.split(':')[1] + const { labwareId, loadName, namespace, version } = command.params + const labwareDefinitionMatch = Object.entries( + file.labwareDefinitions + ).find( + ([definitionUri, labwareDef]) => + labwareDef.parameters.loadName === loadName && + labwareDef.namespace === namespace && + labwareDef.version === version + ) + const labwareDefURI = + labwareDefinitionMatch != null ? labwareDefinitionMatch[0] : '' const id = labwareId ?? '' return { ...acc, [id]: { - labwareDefURI: loadName.includes('/') ? loadName : defUri ?? '', + labwareDefURI, }, } }, diff --git a/protocol-designer/src/steplist/substepTimeline.ts b/protocol-designer/src/steplist/substepTimeline.ts index 2ef909db37d..3c6daed9ceb 100644 --- a/protocol-designer/src/steplist/substepTimeline.ts +++ b/protocol-designer/src/steplist/substepTimeline.ts @@ -4,7 +4,6 @@ import { getWellsForTips, getNextRobotStateAndWarningsSingleCommand, } from '@opentrons/step-generation' -import { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' import { Channels } from '@opentrons/components' import type { CommandCreatorError, @@ -13,7 +12,8 @@ import type { InvariantContext, RobotState, } from '@opentrons/step-generation' -import { SubstepTimelineFrame, SourceDestData, TipLocation } from './types' +import type { CreateCommand } from '@opentrons/shared-data' +import type { SubstepTimelineFrame, SourceDestData, TipLocation } from './types' /** Return last picked up tip in the specified commands, if any */ export function _getNewActiveTips( diff --git a/protocol-designer/src/top-selectors/substep-highlight.ts b/protocol-designer/src/top-selectors/substep-highlight.ts index 339c3a23326..b959b902e76 100644 --- a/protocol-designer/src/top-selectors/substep-highlight.ts +++ b/protocol-designer/src/top-selectors/substep-highlight.ts @@ -1,16 +1,16 @@ import { createSelector } from 'reselect' -import { getWellNamePerMultiTip } from '@opentrons/shared-data' -import { getWellSetForMultichannel } from '../utils' -import { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' import mapValues from 'lodash/mapValues' +import { getWellNamePerMultiTip } from '@opentrons/shared-data' +import { WellGroup } from '@opentrons/components' import * as StepGeneration from '@opentrons/step-generation' import { selectors as stepFormSelectors } from '../step-forms' import { selectors as fileDataSelectors } from '../file-data' import { getHoveredStepId, getHoveredSubstep } from '../ui/steps' -import { WellGroup } from '@opentrons/components' -import { PipetteEntity, LabwareEntity } from '@opentrons/step-generation' -import { Selector } from '../types' -import { SubstepItemData } from '../steplist/types' +import { getWellSetForMultichannel } from '../utils' +import type { CreateCommand } from '@opentrons/shared-data' +import type { PipetteEntity, LabwareEntity } from '@opentrons/step-generation' +import type { Selector } from '../types' +import type { SubstepItemData } from '../steplist/types' function _wellsForPipette( pipetteEntity: PipetteEntity, diff --git a/protocol-designer/src/utils/labwareModuleCompatibility.ts b/protocol-designer/src/utils/labwareModuleCompatibility.ts index 159b285ba87..ea02c5d9685 100644 --- a/protocol-designer/src/utils/labwareModuleCompatibility.ts +++ b/protocol-designer/src/utils/labwareModuleCompatibility.ts @@ -52,6 +52,7 @@ export const COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE: Record< [THERMOCYCLER_MODULE_TYPE]: [ 'biorad_96_wellplate_200ul_pcr', 'nest_96_wellplate_100ul_pcr_full_skirt', + 'opentrons_96_wellplate_200ul_pcr_full_skirt', ], [HEATERSHAKER_MODULE_TYPE]: [ 'opentrons_96_deep_well_adapter', @@ -63,6 +64,8 @@ export const COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE: Record< 'nest_96_wellplate_100ul_pcr_full_skirt', 'nest_96_wellplate_2ml_deep', 'opentrons_96_wellplate_200ul_pcr_full_skirt', + 'armadillo_96_wellplate_200ul_pcr_full_skirt', + 'biorad_96_wellplate_200ul_pcr', ], } export const getLabwareIsCompatible = ( @@ -94,6 +97,7 @@ export const COMPATIBLE_LABWARE_ALLOWLIST_FOR_ADAPTER: Record< [FLAT_BOTTOM_ADAPTER_LOADNAME]: ['opentrons/nest_96_wellplate_200ul_flat/2'], [PCR_ADAPTER_LOADNAME]: [ 'opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2', + 'opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2', ], [UNIVERSAL_FLAT_ADAPTER_LOADNAME]: [ 'opentrons/corning_384_wellplate_112ul_flat/2', @@ -178,7 +182,8 @@ export const getAdapterLabwareIsAMatch = ( draggedLabwareLoadname === 'nest_96_wellplate_200ul_flat' const pcrPair = loadName === PCR_ADAPTER_LOADNAME && - draggedLabwareLoadname === 'nest_96_wellplate_100ul_pcr_full_skirt' + (draggedLabwareLoadname === 'nest_96_wellplate_100ul_pcr_full_skirt' || + draggedLabwareLoadname === 'opentrons_96_wellplate_200ul_pcr_full_skirt') const universalPair = loadName === UNIVERSAL_FLAT_ADAPTER_LOADNAME && (draggedLabwareLoadname === 'corning_384_wellplate_112ul_flat' || diff --git a/react-api-client/src/maintenance_runs/__fixtures__/maintenanceCommands.ts b/react-api-client/src/maintenance_runs/__fixtures__/maintenanceCommands.ts index 83e1c30a877..488e8d539d6 100644 --- a/react-api-client/src/maintenance_runs/__fixtures__/maintenanceCommands.ts +++ b/react-api-client/src/maintenance_runs/__fixtures__/maintenanceCommands.ts @@ -1,5 +1,5 @@ import { CommandsData, RunCommandSummary } from '@opentrons/api-client' -import type { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' +import type { CreateCommand } from '@opentrons/shared-data' export const mockAnonLoadCommand: CreateCommand = { commandType: 'loadLabware', diff --git a/react-api-client/src/runs/__fixtures__/runCommands.ts b/react-api-client/src/runs/__fixtures__/runCommands.ts index e0e65408c06..7a78725e2a9 100644 --- a/react-api-client/src/runs/__fixtures__/runCommands.ts +++ b/react-api-client/src/runs/__fixtures__/runCommands.ts @@ -1,5 +1,5 @@ import { CommandsData, RunCommandSummary } from '@opentrons/api-client' -import type { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' +import type { CreateCommand } from '@opentrons/shared-data' export const mockAnonLoadCommand: CreateCommand = { commandType: 'loadLabware', diff --git a/react-api-client/src/runs/useCreateLiveCommandMutation.ts b/react-api-client/src/runs/useCreateLiveCommandMutation.ts index a8f63da346b..5b546fbb530 100644 --- a/react-api-client/src/runs/useCreateLiveCommandMutation.ts +++ b/react-api-client/src/runs/useCreateLiveCommandMutation.ts @@ -11,7 +11,7 @@ import type { HostConfig, CreateCommandParams, } from '@opentrons/api-client' -import type { CreateCommand } from '@opentrons/shared-data/protocol/types/schemaV7' +import type { CreateCommand } from '@opentrons/shared-data' export interface CreateLiveCommandMutateParams extends CreateCommandParams { command: CreateCommand diff --git a/robot-server/Makefile b/robot-server/Makefile index dacf7f1638c..11562fa389e 100755 --- a/robot-server/Makefile +++ b/robot-server/Makefile @@ -69,7 +69,9 @@ clean_all_cmd = $(clean_cmd) dist # OT_ROBOT_SERVER_DOT_ENV_PATH doesn't work because that's read too late. # * Doing `pipenv run env PYTHONDEVMODE=1 uvicorn ...` works, except it's # probably POSIX-only. -run_dev ?= uvicorn "robot_server:app" --host localhost --port 31950 --ws wsproto --lifespan on --reload +dev_port ?= "31950" +dev_host ?= "localhost" +run_dev ?= uvicorn "robot_server:app" --host $(dev_host) --port $(dev_port) --ws wsproto --lifespan on --reload .PHONY: all all: clean sdist wheel diff --git a/robot-server/robot_server/hardware.py b/robot-server/robot_server/hardware.py index 5c0deb148be..6af230bd9ff 100644 --- a/robot-server/robot_server/hardware.py +++ b/robot-server/robot_server/hardware.py @@ -16,7 +16,7 @@ from uuid import uuid4 # direct to avoid import cycles in service.dependencies from traceback import format_exception_only, TracebackException from contextlib import contextmanager -from opentrons_shared_data.robot.dev_types import RobotType +from opentrons_shared_data.robot.dev_types import RobotType, RobotTypeEnum from opentrons import initialize as initialize_api, should_use_ot3 from opentrons.config import ( @@ -274,6 +274,11 @@ async def get_robot_type() -> RobotType: return "OT-3 Standard" if should_use_ot3() else "OT-2 Standard" +async def get_robot_type_enum() -> RobotTypeEnum: + """Return what kind of robot this server is running on.""" + return RobotTypeEnum.FLEX if should_use_ot3() else RobotTypeEnum.OT2 + + async def get_deck_type() -> DeckType: """Return what kind of deck the robot that this server is running on has.""" return DeckType(guess_deck_type_from_global_config()) diff --git a/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py b/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py index 9349b65f6d1..166faf7767e 100644 --- a/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py +++ b/robot-server/robot_server/maintenance_runs/maintenance_engine_store.py @@ -33,6 +33,10 @@ class EngineConflictError(RuntimeError): """ +class NoRunnerEnginePairError(RuntimeError): + """Raised if you try to get the current engine or runner while there is none.""" + + class RunnerEnginePair(NamedTuple): """A stored ProtocolRunner/ProtocolEngine pair.""" @@ -90,13 +94,15 @@ def _estop_listener(self, event: HardwareEvent) -> None: @property def engine(self) -> ProtocolEngine: """Get the "current" ProtocolEngine.""" - assert self._runner_engine_pair is not None, "Engine not yet created." + if self._runner_engine_pair is None: + raise NoRunnerEnginePairError() return self._runner_engine_pair.engine @property def runner(self) -> LiveRunner: """Get the "current" ProtocolRunner.""" - assert self._runner_engine_pair is not None, "Runner not yet created." + if self._runner_engine_pair is None: + raise NoRunnerEnginePairError() return self._runner_engine_pair.runner @property diff --git a/robot-server/robot_server/robot/calibration/check/user_flow.py b/robot-server/robot_server/robot/calibration/check/user_flow.py index 34fd5e8e116..361b17dbe8a 100644 --- a/robot-server/robot_server/robot/calibration/check/user_flow.py +++ b/robot-server/robot_server/robot/calibration/check/user_flow.py @@ -8,13 +8,17 @@ get_robot_deck_attitude, save_robot_deck_attitude, get_custom_tiprack_definition_for_tlc, - load_tip_length_calibration, - save_tip_length_calibration, - create_tip_length_data, + mark_bad_calibration, +) + +from opentrons.calibration_storage.ot2 import ( get_pipette_offset, save_pipette_calibration, - mark_bad_calibration, + load_tip_length_calibration, + create_tip_length_data, + save_tip_length_calibration, ) + from opentrons.calibration_storage.ot2 import models from opentrons.types import Mount, Point, Location from opentrons.hardware_control import ( @@ -340,6 +344,7 @@ def _get_current_calibrations(self): pipette_offsets = { m: get_pipette_offset(p.pipette_id, m) for m, p in self._filtered_hw_pips.items() + if p.pipette_id is not None } tip_lengths = { m: self._get_tip_length_from_pipette(m, p) @@ -417,9 +422,11 @@ def _get_stored_pipette_offset_cal( mount: Optional[Mount] = None, ) -> models.v1.InstrumentOffsetModel: if not pipette or not mount: - pip_offset = get_pipette_offset(self.hw_pipette.pipette_id, self.mount) + pip_offset = get_pipette_offset( + self.hw_pipette.pipette_id or "", self.mount + ) else: - pip_offset = get_pipette_offset(pipette.pipette_id, mount) + pip_offset = get_pipette_offset(pipette.pipette_id or "", mount) assert pip_offset, "No Pipette Offset Found" return pip_offset diff --git a/robot-server/robot_server/robot/calibration/deck/user_flow.py b/robot-server/robot_server/robot/calibration/deck/user_flow.py index b40f2164999..c64af632018 100644 --- a/robot-server/robot_server/robot/calibration/deck/user_flow.py +++ b/robot-server/robot_server/robot/calibration/deck/user_flow.py @@ -16,9 +16,15 @@ from opentrons.calibration_storage import ( helpers, types as cal_types, - clear_pipette_offset_calibrations, +) + +from opentrons.calibration_storage.ot2.tip_length import ( load_tip_length_calibration, ) +from opentrons.calibration_storage.ot2.pipette_offset import ( + clear_pipette_offset_calibrations, +) + from opentrons.hardware_control import robot_calibration as robot_cal from opentrons.hardware_control import ( HardwareControlAPI, diff --git a/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py b/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py index 1df6c3c907f..111f2e13766 100644 --- a/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py +++ b/robot-server/robot_server/robot/calibration/pipette_offset/user_flow.py @@ -12,11 +12,15 @@ ) from opentrons.calibration_storage import ( helpers, +) + +from opentrons.calibration_storage.ot2 import ( + load_tip_length_calibration, get_pipette_offset, save_pipette_calibration, delete_pipette_offset_file, - load_tip_length_calibration, ) + from opentrons.calibration_storage.ot2 import models from opentrons.calibration_storage.types import ( TipLengthCalNotFound, @@ -308,7 +312,7 @@ def _determine_state_machine(perform_tip_length: bool) -> PipetteOffsetStateMach def _get_stored_tip_length_cal(self) -> Optional[float]: try: return load_tip_length_calibration( - self._hw_pipette.pipette_id, + self._hw_pipette.pipette_id or "", self._tip_rack._core.get_definition(), ).tipLength except TipLengthCalNotFound: @@ -317,6 +321,8 @@ def _get_stored_tip_length_cal(self) -> Optional[float]: def _get_stored_pipette_offset_cal( self, ) -> Optional[models.v1.InstrumentOffsetModel]: + if self._hw_pipette.pipette_id is None: + return None return get_pipette_offset(self._hw_pipette.pipette_id, self._mount) def _get_tip_length(self) -> float: @@ -449,7 +455,7 @@ async def save_offset(self): save_pipette_calibration( offset=offset, mount=self._mount, - pip_id=self._hw_pipette.pipette_id, + pip_id=self._hw_pipette.pipette_id or "", tiprack_hash=tiprack_hash, tiprack_uri=self._tip_rack.uri, ) @@ -472,7 +478,7 @@ async def save_offset(self): tip_length_offset=noz_pt.z - self._nozzle_height_at_reference, tip_rack=self._tip_rack, ) - delete_pipette_offset_file(self._hw_pipette.pipette_id, self.mount) + delete_pipette_offset_file(self._hw_pipette.pipette_id or "", self.mount) new_tip_length = self._get_stored_tip_length_cal() self._has_calibrated_tip_length = new_tip_length is not None # load the new tip length for the rest of the session diff --git a/robot-server/robot_server/robot/calibration/util.py b/robot-server/robot_server/robot/calibration/util.py index ec8c7aeb536..d7a1d2a7845 100644 --- a/robot-server/robot_server/robot/calibration/util.py +++ b/robot-server/robot_server/robot/calibration/util.py @@ -8,11 +8,10 @@ from opentrons.protocol_api import labware from opentrons.protocols.geometry import planning from opentrons.protocol_api.core.legacy.deck import Deck -from opentrons.calibration_storage import ( - helpers, - create_tip_length_data, - save_tip_length_calibration as cal_storage_save_tip_length, -) +from opentrons.calibration_storage import helpers + +from opentrons.calibration_storage import ot2 as ot2_cal_storage + from opentrons.types import Point, Location from robot_server.service.errors import RobotServerError @@ -203,10 +202,10 @@ def save_tip_length_calibration( # TODO: 07-22-2020 parent slot is not important when tracking # tip length data, hence the empty string, we should remove it # from create_tip_length_data in a refactor - tip_length_data = create_tip_length_data( + tip_length_data = ot2_cal_storage.create_tip_length_data( tip_rack._core.get_definition(), tip_length_offset ) - cal_storage_save_tip_length(pipette_id, tip_length_data) + ot2_cal_storage.save_tip_length_calibration(pipette_id, tip_length_data) def get_default_tipracks(default_uris: List["LabwareUri"]) -> List["LabwareDefinition"]: diff --git a/robot-server/robot_server/runs/dependencies.py b/robot-server/robot_server/runs/dependencies.py index c64cad8843b..662f4c7cdd8 100644 --- a/robot-server/robot_server/runs/dependencies.py +++ b/robot-server/robot_server/runs/dependencies.py @@ -23,7 +23,7 @@ from robot_server.deletion_planner import RunDeletionPlanner from .run_auto_deleter import RunAutoDeleter -from .engine_store import EngineStore +from .engine_store import EngineStore, NoRunnerEnginePairError from .run_store import RunStore from .run_data_manager import RunDataManager from robot_server.errors.robot_errors import ( @@ -127,8 +127,8 @@ async def get_is_okay_to_create_maintenance_run( """Whether a maintenance run can be created if a protocol run already exists.""" try: protocol_run_state = engine_store.engine.state_view - except AssertionError: - return False + except NoRunnerEnginePairError: + return True return ( not protocol_run_state.commands.has_been_played() or protocol_run_state.commands.get_is_terminal() diff --git a/robot-server/robot_server/runs/engine_store.py b/robot-server/robot_server/runs/engine_store.py index 6faff1e6e1f..558a903332d 100644 --- a/robot-server/robot_server/runs/engine_store.py +++ b/robot-server/robot_server/runs/engine_store.py @@ -14,6 +14,7 @@ HardwareEventHandler, ) from opentrons.protocols.parse import PythonParseMode +from opentrons.protocols.api_support.deck_type import should_load_fixed_trash from opentrons.protocol_runner import ( AnyRunner, JsonRunner, @@ -41,6 +42,10 @@ class EngineConflictError(RuntimeError): """ +class NoRunnerEnginePairError(RuntimeError): + """Raised if you try to get the current engine or runner while there is none.""" + + class RunnerEnginePair(NamedTuple): """A stored Runner/ProtocolEngine pair.""" @@ -90,13 +95,15 @@ def __init__( @property def engine(self) -> ProtocolEngine: """Get the "current" persisted ProtocolEngine.""" - assert self._runner_engine_pair is not None, "Engine not yet created." + if self._runner_engine_pair is None: + raise NoRunnerEnginePairError() return self._runner_engine_pair.engine @property def runner(self) -> AnyRunner: """Get the "current" persisted ProtocolRunner.""" - assert self._runner_engine_pair is not None, "Runner not yet created." + if self._runner_engine_pair is None: + raise NoRunnerEnginePairError() return self._runner_engine_pair.runner @property @@ -159,6 +166,11 @@ async def create( EngineConflictError: The current runner/engine pair is not idle, so a new set may not be created. """ + if protocol is not None: + load_fixed_trash = should_load_fixed_trash(protocol.source.config) + else: + load_fixed_trash = False + engine = await create_protocol_engine( hardware_api=self._hardware_api, config=ProtocolEngineConfig( @@ -168,6 +180,7 @@ async def create( RobotTypeEnum.robot_literal_to_enum(self._robot_type) ), ), + load_fixed_trash=load_fixed_trash, ) runner = create_protocol_runner( protocol_engine=engine, diff --git a/robot-server/robot_server/service/legacy/routers/settings.py b/robot-server/robot_server/service/legacy/routers/settings.py index a9105e6c7db..5c98c2a43ca 100644 --- a/robot-server/robot_server/service/legacy/routers/settings.py +++ b/robot-server/robot_server/service/legacy/routers/settings.py @@ -25,7 +25,7 @@ ) from robot_server.errors import LegacyErrorResponse -from robot_server.hardware import get_hardware, get_robot_type +from robot_server.hardware import get_hardware, get_robot_type, get_robot_type_enum from robot_server.service.legacy import reset_odd from robot_server.service.legacy.models import V1BasicResponse from robot_server.service.legacy.models.settings import ( @@ -210,7 +210,7 @@ async def post_log_level_upstream(log_level: LogLevel) -> V1BasicResponse: response_model=FactoryResetOptions, ) async def get_settings_reset_options( - robot_type: str = Depends(get_robot_type), + robot_type: RobotTypeEnum = Depends(get_robot_type_enum), ) -> FactoryResetOptions: reset_options = reset_util.reset_options(robot_type).items() return FactoryResetOptions( @@ -231,7 +231,7 @@ async def get_settings_reset_options( async def post_settings_reset_options( factory_reset_commands: Dict[reset_util.ResetOptionId, bool], persistence_resetter: PersistenceResetter = Depends(get_persistence_resetter), - robot_type: str = Depends(get_robot_type), + robot_type: RobotTypeEnum = Depends(get_robot_type_enum), ) -> V1BasicResponse: reset_options = reset_util.reset_options(robot_type) not_allowed_options = [ @@ -247,7 +247,7 @@ async def post_settings_reset_options( ).as_error(status.HTTP_403_FORBIDDEN) options = set(k for k, v in factory_reset_commands.items() if v) - reset_util.reset(options) + reset_util.reset(options, robot_type) if factory_reset_commands.get(reset_util.ResetOptionId.runs_history, False): await persistence_resetter.mark_directory_reset() diff --git a/robot-server/tests/conftest.py b/robot-server/tests/conftest.py index a414541f45f..405585c72e8 100644 --- a/robot-server/tests/conftest.py +++ b/robot-server/tests/conftest.py @@ -21,10 +21,17 @@ from opentrons.hardware_control import API, HardwareControlAPI, ThreadedAsyncLock from opentrons.calibration_storage import ( helpers, + save_robot_deck_attitude, +) + +# NOTE(FS 10-24-2023), the fixtures using these functions currently ONLY +# get pulled in by OT-2 server tests. If this ever changes, we need to +# conditionally set up ot2/ot3 calibration structures instead of invariably +# calling the OT2 functions. +from opentrons.calibration_storage.ot2 import ( save_pipette_calibration, create_tip_length_data, save_tip_length_calibration, - save_robot_deck_attitude, ) from opentrons.protocol_api import labware from opentrons.types import Point, Mount diff --git a/robot-server/tests/integration/conftest.py b/robot-server/tests/integration/conftest.py index 1ee57c462e2..669e2cb7655 100644 --- a/robot-server/tests/integration/conftest.py +++ b/robot-server/tests/integration/conftest.py @@ -4,6 +4,7 @@ import time from pathlib import Path from typing import Any, Dict, Generator +from datetime import datetime import pytest import requests @@ -18,6 +19,7 @@ _SESSION_SERVER_HOST = "localhost" _OT2_SESSION_SERVER_PORT = "31950" _OT3_SESSION_SERVER_PORT = "31960" +_INTEGRATION_SERVER_STARTUP_TIMEOUT_S = 30 def pytest_tavern_beta_before_every_test_run( @@ -110,7 +112,11 @@ def _requests_session() -> Generator[requests.Session, None, None]: def _wait_until_ready(base_url: str) -> None: with _requests_session() as requests_session: + started = datetime.now() while True: + now = datetime.now() + if (now - started).total_seconds() > _INTEGRATION_SERVER_STARTUP_TIMEOUT_S: + raise RuntimeError("Could not start dev server") try: health_response = requests_session.get(f"{base_url}/health") except requests.ConnectionError: diff --git a/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml new file mode 100644 index 00000000000..7165f65d478 --- /dev/null +++ b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_flex.tavern.yaml @@ -0,0 +1,655 @@ +test_name: Upload and analyze a JSONv8 protocol + +marks: + - usefixtures: + - ot3_server_base_url + - ot3_only + +stages: + - name: Upload simple v8 protocol for flex + request: + url: '{ot3_server_base_url}/protocols' + method: POST + files: + files: '../shared-data/protocol/fixtures/8/simpleFlexV8.json' + response: + strict: + - json:off + save: + json: + protocol_id: data.id + analysis_id: data.analysisSummaries[0].id + status_code: 201 + json: + data: + id: !anystr + protocolType: json + analysisSummaries: + - id: !anystr + status: pending + - name: Retry until analyses status is completed. + max_retries: 10 + delay_after: 0.1 + request: + url: '{ot3_server_base_url}/protocols' + method: GET + response: + strict: + - json:off + status_code: 200 + json: + data: + - id: '{protocol_id}' + analysisSummaries: + - id: '{analysis_id}' + status: completed + - name: Get protocol by ID + request: + url: '{ot3_server_base_url}/protocols/{protocol_id}' + method: GET + response: + status_code: 200 + json: + data: + id: '{protocol_id}' + createdAt: !anystr + files: + - name: simpleFlexV8.json + role: main + protocolType: json + robotType: OT-3 Standard + metadata: + tags: + - unitTest + created: !anyint + description: A short test protocol + author: engineering + protocolName: Simple test protocol + analyses: [] + analysisSummaries: + - id: '{analysis_id}' + status: completed + links: + referencingRuns: [] + - name: Get protocol analysis by ID + request: + url: '{ot3_server_base_url}/protocols/{protocol_id}/analyses' + method: GET + response: + status_code: 200 + json: + meta: + cursor: 0 + totalLength: 1 + data: + - id: '{analysis_id}' + status: completed + result: ok + pipettes: + - id: pipetteId + pipetteName: p1000_96 + mount: left + labware: + - id: sourcePlateId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + definitionUri: opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/1 + displayName: Source Plate + location: + moduleId: temperatureModuleId + - id: destPlateId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + definitionUri: opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/1 + displayName: Sample Collection Plate + location: + moduleId: magneticModuleId + - id: tipRackId + loadName: opentrons_96_tiprack_1000ul + definitionUri: opentrons/opentrons_96_tiprack_1000ul/1 + displayName: Opentrons 96 Tip Rack 1000 µL + location: + slotName: 'B2' + - id: fixedTrash + loadName: opentrons_1_trash_1100ml_fixed + definitionUri: opentrons/opentrons_1_trash_1100ml_fixed/1 + displayName: Trash + location: + slotName: 'A3' + modules: + - id: magneticModuleId + serialNumber: !anystr + model: magneticModuleV2 + location: + slotName: 'D3' + - id: temperatureModuleId + serialNumber: !anystr + model: temperatureModuleV2 + location: + slotName: 'D1' + commands: + # Initial home + - id: !anystr + createdAt: !anystr + commandType: home + key: !anystr + status: succeeded + params: { } + result: { } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadPipette + key: !anystr + status: succeeded + params: + pipetteName: p1000_96 + mount: left + pipetteId: pipetteId + result: + pipetteId: pipetteId + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadModule + key: !anystr + status: succeeded + params: + model: magneticModuleV2 + location: + slotName: 'D3' + moduleId: magneticModuleId + result: + moduleId: magneticModuleId + definition: !anydict + model: magneticModuleV2 + serialNumber: !anystr + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadModule + key: !anystr + status: succeeded + params: + model: temperatureModuleV2 + location: + slotName: 'D1' + moduleId: temperatureModuleId + result: + moduleId: temperatureModuleId + definition: !anydict + model: temperatureModuleV2 + serialNumber: !anystr + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + moduleId: temperatureModuleId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + namespace: opentrons + version: 1 + labwareId: sourcePlateId + displayName: Source Plate + result: + labwareId: sourcePlateId + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + moduleId: magneticModuleId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + namespace: opentrons + version: 1 + labwareId: destPlateId + displayName: Sample Collection Plate + result: + labwareId: destPlateId + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + slotName: 'B2' + loadName: opentrons_96_tiprack_1000ul + namespace: opentrons + version: 1 + labwareId: tipRackId + displayName: Opentrons 96 Tip Rack 1000 µL + result: + labwareId: tipRackId + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + slotName: 'A3' + loadName: opentrons_1_trash_1100ml_fixed + namespace: opentrons + version: 1 + labwareId: fixedTrash + displayName: Trash + result: + labwareId: fixedTrash + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLiquid + key: !anystr + status: succeeded + params: + liquidId: 'waterId' + labwareId: 'sourcePlateId' + volumeByWell: + A1: 100.0 + B1: 100.0 + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: home + key: !anystr + status: succeeded + params: {} + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: pickUpTip + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: tipRackId + wellName: B1 + wellLocation: + origin: top + offset: + x: 0 + y: 0 + z: 0 + result: + position: { 'x': 178.38, 'y': 279.24, 'z': 97.47 } + tipVolume: 1000.0 + tipLength: 77.5 + tipDiameter: 7.23 + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: aspirate + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: sourcePlateId + wellName: A1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 2.0 + volume: 5.0 + flowRate: 3.0 + result: + position: { 'x': 14.38, 'y': 74.24, 'z': 12.05 } + volume: 5.0 + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: waitForDuration + key: !anystr + status: succeeded + params: + seconds: 42.0 + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: dispense + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 1.0 + volume: 4.5 + flowRate: 2.5 + result: + position: { 'x': 341.205, 'y': 65.115, 'z': 84.3 } + volume: 4.5 + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: touchTip + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 11.0 + radius: 1.0 + speed: 42.0 + result: + position: { 'x': 341.205, 'y': 65.115, 'z': 94.3 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: blowout + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 12.0 + flowRate: 2.0 + result: + position: { 'x': 341.205, 'y': 65.115, 'z': 95.3 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToCoordinates + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + coordinates: + x: 100.0 + y: 100.0 + z: 100.0 + forceDirect: false + result: + position: { 'x': 100.0, 'y': 100.0, 'z': 100.0 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToWell + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B2 + wellLocation: + origin: top + offset: + x: 0 + y: 0 + z: 0 + forceDirect: false + speed: 12.3 + result: + position: + { 'x': 350.205, 'y': 65.115, 'z': 98.25 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToWell + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B2 + wellLocation: + origin: bottom + offset: + x: 2.0 + y: 3.0 + z: 10.0 + minimumZHeight: 35.0 + forceDirect: true + result: + position: + { 'x': 352.205, 'y': 68.115, 'z': 93.3 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: dropTip + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: fixedTrash + wellName: A1 + wellLocation: + origin: default + offset: + x: 0 + y: 0 + z: 0 + alternateDropLocation: false + result: + position: { 'x': 410.84000000000003, 'y': 374.56, 'z': 82.0 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: waitForResume + key: !anystr + status: succeeded + params: + message: pause command + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToCoordinates + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + coordinates: + x: 0.0 + y: 0.0 + z: 0.0 + minimumZHeight: 35.0 + forceDirect: true + result: + position: { 'x': 0.0, 'y': 0.0, 'z': 0.0 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveRelative + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + axis: x + distance: 1.0 + result: + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveRelative + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + axis: y + distance: 0.1 + result: + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: savePosition + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + result: + positionId: !anystr + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveRelative + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + axis: z + distance: 10.0 + result: + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: savePosition + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + positionId: positionId + result: + positionId: positionId + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + errors: [] + liquids: + - id: waterId + displayName: Water + description: Liquid H2O + displayColor: '#7332a8' + +--- +test_name: Upload and analyze a JSONv8 protocol, with liquids + +marks: + - usefixtures: + - ot3_server_base_url + - ot3_only + +stages: + - name: Upload simple v8 protocol + request: + url: '{ot3_server_base_url}/protocols' + method: POST + files: + files: '../shared-data/protocol/fixtures/8/simpleFlexV8.json' + response: + strict: + - json:off + save: + json: + protocol_id: data.id + analysis_id: data.analysisSummaries[0].id + status_code: 201 + json: + data: + id: !anystr + protocolType: json + analysisSummaries: + - id: !anystr + status: pending + - name: Retry until analyses status is completed. + max_retries: 10 + delay_after: 0.1 + request: + url: '{ot3_server_base_url}/protocols' + method: GET + response: + strict: + - json:off + status_code: 200 + json: + data: + - id: '{protocol_id}' + analysisSummaries: + - id: '{analysis_id}' + status: completed + - name: Get protocol analysis by ID + request: + url: '{ot3_server_base_url}/protocols/{protocol_id}/analyses' + method: GET + response: + strict: + - json:off + status_code: 200 + json: + data: + - id: '{analysis_id}' + status: completed + result: ok + errors: [] + liquids: + - id: waterId + displayName: Water + description: Liquid H2O + displayColor: '#7332a8' diff --git a/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml new file mode 100644 index 00000000000..e33c1d71026 --- /dev/null +++ b/robot-server/tests/integration/http_api/protocols/test_v8_json_upload_ot2.tavern.yaml @@ -0,0 +1,651 @@ +test_name: Upload and analyze a JSONv8 protocol + +marks: + - usefixtures: + - ot2_server_base_url + +stages: + - name: Upload simple v8 protocol + request: + url: '{ot2_server_base_url}/protocols' + method: POST + files: + files: '../shared-data/protocol/fixtures/8/simpleV8.json' + response: + strict: + - json:off + save: + json: + protocol_id: data.id + analysis_id: data.analysisSummaries[0].id + status_code: 201 + json: + data: + id: !anystr + protocolType: json + analysisSummaries: + - id: !anystr + status: pending + - name: Retry until analyses status is completed. + max_retries: 10 + delay_after: 0.1 + request: + url: '{ot2_server_base_url}/protocols' + method: GET + response: + strict: + - json:off + status_code: 200 + json: + data: + - id: '{protocol_id}' + analysisSummaries: + - id: '{analysis_id}' + status: completed + - name: Get protocol by ID + request: + url: '{ot2_server_base_url}/protocols/{protocol_id}' + method: GET + response: + status_code: 200 + json: + data: + id: '{protocol_id}' + createdAt: !anystr + files: + - name: simpleV8.json + role: main + protocolType: json + robotType: OT-2 Standard + metadata: + tags: + - unitTest + created: !anyint + description: A short test protocol + author: engineering + protocolName: Simple test protocol + analyses: [] + analysisSummaries: + - id: '{analysis_id}' + status: completed + links: + referencingRuns: [] + - name: Get protocol analysis by ID + request: + url: '{ot2_server_base_url}/protocols/{protocol_id}/analyses' + method: GET + response: + status_code: 200 + json: + meta: + cursor: 0 + totalLength: 1 + data: + - id: '{analysis_id}' + status: completed + result: ok + pipettes: + - id: pipetteId + pipetteName: p10_single + mount: left + labware: + - id: sourcePlateId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + definitionUri: opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/1 + displayName: Source Plate + location: + moduleId: temperatureModuleId + - id: destPlateId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + definitionUri: opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/1 + displayName: Sample Collection Plate + location: + moduleId: magneticModuleId + - id: tipRackId + loadName: opentrons_96_tiprack_10ul + definitionUri: opentrons/opentrons_96_tiprack_10ul/1 + displayName: Opentrons 96 Tip Rack 10 µL + location: + slotName: '8' + - id: fixedTrash + loadName: opentrons_1_trash_1100ml_fixed + definitionUri: opentrons/opentrons_1_trash_1100ml_fixed/1 + displayName: Trash + location: + slotName: '12' + modules: + - id: magneticModuleId + serialNumber: !anystr + model: magneticModuleV2 + location: + slotName: '3' + - id: temperatureModuleId + serialNumber: !anystr + model: temperatureModuleV2 + location: + slotName: '1' + commands: + # Initial home + - id: !anystr + createdAt: !anystr + commandType: home + key: !anystr + status: succeeded + params: { } + result: { } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadPipette + key: !anystr + status: succeeded + params: + pipetteName: p10_single + mount: left + pipetteId: pipetteId + result: + pipetteId: pipetteId + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadModule + key: !anystr + status: succeeded + params: + model: magneticModuleV2 + location: + slotName: '3' + moduleId: magneticModuleId + result: + moduleId: magneticModuleId + definition: !anydict + model: magneticModuleV2 + serialNumber: !anystr + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadModule + key: !anystr + status: succeeded + params: + model: temperatureModuleV2 + location: + slotName: '1' + moduleId: temperatureModuleId + result: + moduleId: temperatureModuleId + definition: !anydict + model: temperatureModuleV2 + serialNumber: !anystr + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + moduleId: temperatureModuleId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + namespace: opentrons + version: 1 + labwareId: sourcePlateId + displayName: Source Plate + result: + labwareId: sourcePlateId + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + moduleId: magneticModuleId + loadName: armadillo_96_wellplate_200ul_pcr_full_skirt + namespace: opentrons + version: 1 + labwareId: destPlateId + displayName: Sample Collection Plate + result: + labwareId: destPlateId + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + slotName: '8' + loadName: opentrons_96_tiprack_10ul + namespace: opentrons + version: 1 + labwareId: tipRackId + displayName: Opentrons 96 Tip Rack 10 µL + result: + labwareId: tipRackId + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLabware + key: !anystr + status: succeeded + params: + location: + slotName: '12' + loadName: opentrons_1_trash_1100ml_fixed + namespace: opentrons + version: 1 + labwareId: fixedTrash + displayName: Trash + result: + labwareId: fixedTrash + definition: !anydict + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: loadLiquid + key: !anystr + status: succeeded + params: + liquidId: 'waterId' + labwareId: 'sourcePlateId' + volumeByWell: + A1: 100.0 + B1: 100.0 + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: home + key: !anystr + status: succeeded + params: {} + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: pickUpTip + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: tipRackId + wellName: B1 + wellLocation: + origin: top + offset: + x: 0 + y: 0 + z: 0 + result: + position: { 'x': 146.88, 'y': 246.24, 'z': 64.69 } + tipVolume: 10.0 + tipLength: 35.910000000000004 + tipDiameter: 3.27 + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: aspirate + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: sourcePlateId + wellName: A1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 2.0 + volume: 5.0 + flowRate: 3.0 + result: + position: { 'x': 12.930000000000001, 'y': 74.08999999999999, 'z': 83.14 } + volume: 5.0 + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: waitForDuration + key: !anystr + status: succeeded + params: + seconds: 42.0 + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: dispense + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 1.0 + volume: 4.5 + flowRate: 2.5 + result: + position: { 'x': 280.805, 'y': 65.115, 'z': 84.3 } + volume: 4.5 + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: touchTip + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 11.0 + radius: 1.0 + result: + position: { 'x': 280.805, 'y': 65.115, 'z': 94.3 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: blowout + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B1 + wellLocation: + origin: bottom + offset: + x: 0.0 + y: 0.0 + z: 12.0 + flowRate: 2.0 + result: + position: { 'x': 280.805, 'y': 65.115, 'z': 95.3 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToCoordinates + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + coordinates: + x: 100.0 + y: 100.0 + z: 100.0 + forceDirect: false + result: + position: {'x': 100.0, 'y': 100.0, 'z': 100.0} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToWell + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B2 + wellLocation: + origin: top + offset: + x: 0 + y: 0 + z: 0 + forceDirect: false + result: + position: + { 'x': 289.805, 'y': 65.115, 'z': 98.25 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToWell + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: destPlateId + wellName: B2 + wellLocation: + origin: bottom + offset: + x: 2.0 + y: 3.0 + z: 10.0 + minimumZHeight: 35.0 + forceDirect: true + result: + position: + { 'x': 291.805, 'y': 68.115, 'z': 93.3 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: dropTip + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + labwareId: fixedTrash + wellName: A1 + wellLocation: + origin: default + offset: + x: 0 + y: 0 + z: 0 + alternateDropLocation: false + result: + position: { 'x': 347.84000000000003, 'y': 325.06, 'z': 82.0 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: waitForResume + key: !anystr + status: succeeded + params: + message: pause command + result: {} + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveToCoordinates + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + coordinates: + x: 0.0 + y: 0.0 + z: 0.0 + minimumZHeight: 35.0 + forceDirect: true + result: + position: { 'x': 0.0, 'y': 0.0, 'z': 0.0 } + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveRelative + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + axis: x + distance: 1.0 + result: + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveRelative + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + axis: y + distance: 0.1 + result: + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: savePosition + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + result: + positionId: !anystr + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: moveRelative + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + axis: z + distance: 10.0 + result: + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + - id: !anystr + createdAt: !anystr + commandType: savePosition + key: !anystr + status: succeeded + params: + pipetteId: pipetteId + positionId: positionId + result: + positionId: positionId + position: + x: !anyfloat + y: !anyfloat + z: !anyfloat + startedAt: !anystr + completedAt: !anystr + errors: [] + liquids: + - id: waterId + displayName: Water + description: Liquid H2O + displayColor: '#7332a8' + +--- +test_name: Upload and analyze a JSONv8 protocol, with liquids + +marks: + - usefixtures: + - ot2_server_base_url + +stages: + - name: Upload simple v8 protocol + request: + url: '{ot2_server_base_url}/protocols' + method: POST + files: + files: '../shared-data/protocol/fixtures/8/simpleV8.json' + response: + strict: + - json:off + save: + json: + protocol_id: data.id + analysis_id: data.analysisSummaries[0].id + status_code: 201 + json: + data: + id: !anystr + protocolType: json + analysisSummaries: + - id: !anystr + status: pending + - name: Retry until analyses status is completed. + max_retries: 10 + delay_after: 0.1 + request: + url: '{ot2_server_base_url}/protocols' + method: GET + response: + strict: + - json:off + status_code: 200 + json: + data: + - id: '{protocol_id}' + analysisSummaries: + - id: '{analysis_id}' + status: completed + - name: Get protocol analysis by ID + request: + url: '{ot2_server_base_url}/protocols/{protocol_id}/analyses' + method: GET + response: + strict: + - json:off + status_code: 200 + json: + data: + - id: '{analysis_id}' + status: completed + result: ok + errors: [] + liquids: + - id: waterId + displayName: Water + description: Liquid H2O + displayColor: '#7332a8' diff --git a/robot-server/tests/integration/http_api/runs/test_deck_slot_standardization.py b/robot-server/tests/integration/http_api/runs/test_deck_slot_standardization.py index 04ad1461d24..0627ce43c0b 100644 --- a/robot-server/tests/integration/http_api/runs/test_deck_slot_standardization.py +++ b/robot-server/tests/integration/http_api/runs/test_deck_slot_standardization.py @@ -174,7 +174,6 @@ async def test_deck_slot_standardization( run_summary = (await robot_client.get_run(run_id)).json()["data"] [run_summary_module] = run_summary["modules"] [ - run_summary_fixed_trash_labware, run_summary_labware_1, run_summary_labware_2, ] = run_summary["labware"] diff --git a/robot-server/tests/integration/http_api/runs/test_maintenance_run_creation.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_maintenance_run_creation.tavern.yaml new file mode 100644 index 00000000000..7b7d7930f22 --- /dev/null +++ b/robot-server/tests/integration/http_api/runs/test_maintenance_run_creation.tavern.yaml @@ -0,0 +1,115 @@ +test_name: Make sure you can create a maintenance run while there are no normal runs + +marks: + - usefixtures: + - ot2_server_base_url + +stages: + - &assert_no_runs + name: 'Setup check: Make sure there are no runs' + request: + url: '{ot2_server_base_url}/runs' + response: + json: + data: [] + links: {} + meta: + cursor: 0 + totalLength: 0 + + - &assert_no_maintenance_runs + name: 'Setup check: Make sure there are no maintenance runs' + request: + url: '{ot2_server_base_url}/maintenance_runs/current' + response: + status_code: 404 + + - &assert_can_create_maintenance_run + name: Make sure we can create a maintenance run + request: + url: '{ot2_server_base_url}/maintenance_runs' + method: POST + response: + status_code: 201 + +--- + +test_name: Make sure you can create a maintenance run while there is an unstarted protocol run + +marks: + - usefixtures: + - ot2_server_base_url + +stages: + - *assert_no_runs + - *assert_no_maintenance_runs + + - &upload_protocol_and_save_id + name: Upload a protocol + request: + url: '{ot2_server_base_url}/protocols' + method: POST + files: + files: tests/integration/protocols/simple_v6.json + response: + status_code: 201 + save: + json: + protocol_id: data.id + + - &create_protocol_run_and_save_id + name: Create a protocol run + request: + url: '{ot2_server_base_url}/runs' + method: POST + json: + data: + protocolId: '{protocol_id}' + response: + status_code: 201 + save: + json: + protocol_run_id: data.id + + - *assert_can_create_maintenance_run + +--- + +test_name: Make sure you can create a maintenance run while there is a completed protocol run + +marks: + - usefixtures: + - ot2_server_base_url + +stages: + - *assert_no_runs + - *assert_no_maintenance_runs + + - *upload_protocol_and_save_id + - *create_protocol_run_and_save_id + + - name: Issue a play action + request: + url: '{ot2_server_base_url}/runs/{protocol_run_id}/actions' + json: + data: + actionType: play + method: POST + response: + status_code: 201 + + - name: Wait for the protocol run to complete + max_retries: 10 + delay_after: 0.1 + request: + url: '{ot2_server_base_url}/runs/{protocol_run_id}' + method: GET + response: + status_code: 200 + strict: + - json:off + json: + data: + status: succeeded + + - *assert_can_create_maintenance_run diff --git a/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml index 34490ec2b6b..3434f210bd0 100644 --- a/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_run_queued_protocol_commands.tavern.yaml @@ -89,12 +89,7 @@ stages: current: True errors: [] id: '{run_id}' - labware: - - id: 'fixedTrash' - loadName: 'opentrons_1_trash_1100ml_fixed' - definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1' - location: - slotName: '12' + labware: [] labwareOffsets: [] liquids: [] modules: [] diff --git a/robot-server/tests/integration/http_api/runs/test_runs_pagination.tavern.yaml b/robot-server/tests/integration/http_api/runs/test_runs_pagination.tavern.yaml index a79df412c40..00111d778cc 100644 --- a/robot-server/tests/integration/http_api/runs/test_runs_pagination.tavern.yaml +++ b/robot-server/tests/integration/http_api/runs/test_runs_pagination.tavern.yaml @@ -92,11 +92,7 @@ stages: errors: [ ] pipettes: [ ] modules: [ ] - labware: - - id: !anystr - loadName: !anystr - definitionUri: opentrons/opentrons_1_trash_1100ml_fixed/1 - location: !anydict + labware: [ ] labwareOffsets: [ ] liquids: [ ] - id: '{second_run_id}' @@ -107,11 +103,7 @@ stages: errors: [ ] pipettes: [ ] modules: [ ] - labware: - - id: !anystr - loadName: !anystr - definitionUri: opentrons/opentrons_1_trash_1100ml_fixed/1 - location: !anydict + labware: [ ] labwareOffsets: [ ] liquids: [ ] - id: '{third_run_id}' @@ -122,11 +114,7 @@ stages: errors: [ ] pipettes: [ ] modules: [ ] - labware: - - id: !anystr - loadName: !anystr - definitionUri: opentrons/opentrons_1_trash_1100ml_fixed/1 - location: !anydict + labware: [ ] labwareOffsets: [ ] liquids: [ ] links: diff --git a/robot-server/tests/integration/protocols/simpleV8.json b/robot-server/tests/integration/protocols/simpleV8.json new file mode 100644 index 00000000000..a50a4e2cb5b --- /dev/null +++ b/robot-server/tests/integration/protocols/simpleV8.json @@ -0,0 +1,1386 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Simple test protocol", + "author": "engineering ", + "description": "A short test protocol", + "created": 1223131231, + "tags": ["unitTest"] + }, + "robot": { + "model": "OT-2 Standard", + "deckId": "ot2_standard" + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "waterId": { + "displayName": "Water", + "description": "Liquid H2O", + "displayColor": "#7332a8" + } + }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": ["fixedTrash", "centerMultichannelOnWells"] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 77, + "x": 82.84, + "y": 53.56, + "z": 5 + } + }, + "brand": { + "brand": "Opentrons" + }, + "groups": [ + { + "wells": ["A1"], + "metadata": {} + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "example/plate/1": { + "ordering": [ + ["A1", "B1", "C1", "D1"], + ["A2", "B2", "C2", "D2"] + ], + "brand": { + "brand": "foo", + "brandId": [] + }, + "metadata": { + "displayName": "Foo 8 Well Plate 33uL", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL" + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 100 + }, + "wells": { + "A1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 75.43, + "z": 75 + }, + "B1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 56.15, + "z": 75 + }, + "C1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 36.87, + "z": 75 + }, + "D1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 17.59, + "z": 75 + }, + "A2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 75.43, + "z": 75 + }, + "B2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 56.15, + "z": 75 + }, + "C2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 36.87, + "z": 75 + }, + "D2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 17.59, + "z": 75 + } + }, + "groups": [ + { + "metadata": {}, + "wells": ["A1", "B1", "C1", "A2", "B2", "C2"] + } + ], + "parameters": { + "format": "irregular", + "quirks": [], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "foo_8_plate_33ul" + }, + "namespace": "example", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "commandType": "loadPipette", + "id": "0abc123", + "params": { + "pipetteId": "pipetteId", + "pipetteName": "p10_single", + "mount": "left" + } + }, + { + "commandType": "loadModule", + "id": "1abc123", + "params": { + "moduleId": "magneticModuleId", + "model": "magneticModuleV1", + "location": { "slotName": "3" } + } + }, + { + "commandType": "loadModule", + "id": "2abc123", + "params": { + "moduleId": "temperatureModuleId", + "model": "temperatureModuleV2", + "location": { "slotName": "1" } + } + }, + { + "commandType": "loadLabware", + "id": "3abc123", + "params": { + "labwareId": "sourcePlateId", + "loadName": "foo_8_plate_33ul", + "namespace": "example", + "version": 1, + "location": { + "moduleId": "temperatureModuleId" + }, + "displayName": "Source Plate" + } + }, + { + "commandType": "loadLabware", + "id": "4abc123", + "params": { + "labwareId": "destPlateId", + "loadName": "foo_8_plate_33ul", + "namespace": "example", + "version": 1, + "location": { + "moduleId": "magneticModuleId" + }, + "displayName": "Sample Collection Plate" + } + }, + { + "commandType": "loadLabware", + "id": "5abc123", + "params": { + "labwareId": "tipRackId", + "location": { "slotName": "8" }, + "loadName": "opentrons_96_tiprack_10ul", + "namespace": "opentrons", + "version": 1, + "displayName": "Opentrons 96 Tip Rack 10 µL" + } + }, + { + "commandType": "loadLabware", + "id": "6abc123", + "params": { + "labwareId": "fixedTrash", + "location": { + "slotName": "12" + }, + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "displayName": "Trash" + } + }, + { + "commandType": "loadLiquid", + "params": { + "liquidId": "waterId", + "labwareId": "sourcePlateId", + "volumeByWell": { + "A1": 100, + "B1": 100 + } + } + }, + { + "commandType": "pickUpTip", + "id": "8abc123", + "params": { + "pipetteId": "pipetteId", + "labwareId": "tipRackId", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "id": "9abc123", + "params": { + "pipetteId": "pipetteId", + "labwareId": "sourcePlateId", + "wellName": "A1", + "volume": 5, + "flowRate": 3, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 2 } + } + } + }, + { + "commandType": "dispense", + "id": "11abc123", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "volume": 4.5, + "flowRate": 2.5, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 1 } + } + } + }, + { + "commandType": "moveToWell", + "id": "15abc123", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B2" + } + }, + { + "commandType": "moveToWell", + "id": "16abc123", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B2", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 2, "y": 3, "z": 10 } + }, + "minimumZHeight": 35, + "forceDirect": true, + "speed": 12.3 + } + }, + { + "commandType": "dropTip", + "id": "17abc123", + "params": { + "pipetteId": "pipetteId", + "labwareId": "fixedTrash", + "wellName": "A1" + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [ + { + "commandKeys": [ + ], + "annotationType": "custom" + } + ] +} diff --git a/robot-server/tests/maintenance_runs/test_engine_store.py b/robot-server/tests/maintenance_runs/test_engine_store.py index 7179a75ed74..907c244e71d 100644 --- a/robot-server/tests/maintenance_runs/test_engine_store.py +++ b/robot-server/tests/maintenance_runs/test_engine_store.py @@ -19,6 +19,7 @@ from robot_server.maintenance_runs.maintenance_engine_store import ( MaintenanceEngineStore, EngineConflictError, + NoRunnerEnginePairError, get_estop_listener, ) @@ -111,10 +112,10 @@ async def test_clear_engine(subject: MaintenanceEngineStore) -> None: assert subject.current_run_id is None assert isinstance(result, RunResult) - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.engine - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.runner @@ -142,9 +143,9 @@ async def test_clear_idle_engine(subject: MaintenanceEngineStore) -> None: await subject.clear() # TODO: test engine finish is called - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.engine - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.runner diff --git a/robot-server/tests/protocols/test_protocols_router.py b/robot-server/tests/protocols/test_protocols_router.py index 56027ff5970..fbecaabe425 100644 --- a/robot-server/tests/protocols/test_protocols_router.py +++ b/robot-server/tests/protocols/test_protocols_router.py @@ -450,7 +450,7 @@ async def test_create_protocol_not_readable( assert exc_info.value.status_code == 422 assert exc_info.value.content["errors"][0]["id"] == "ProtocolFilesInvalid" - assert exc_info.value.content["errors"][0]["detail"] == "oh no" + assert "oh no" in exc_info.value.content["errors"][0]["detail"] async def test_create_protocol_different_robot_type( diff --git a/robot-server/tests/robot/calibration/check/test_user_flow.py b/robot-server/tests/robot/calibration/check/test_user_flow.py index 1f0531c708b..9f5ba4a9961 100644 --- a/robot-server/tests/robot/calibration/check/test_user_flow.py +++ b/robot-server/tests/robot/calibration/check/test_user_flow.py @@ -30,7 +30,7 @@ ) -PIP_OFFSET = calibration_storage.models.v1.InstrumentOffsetModel( +PIP_OFFSET = calibration_storage.ot2.models.v1.InstrumentOffsetModel( offset=robot_configs.defaults_ot2.DEFAULT_PIPETTE_OFFSET, tiprack="some_tiprack", uri="custom/some_tiprack/1", @@ -156,7 +156,7 @@ async def test_switching_to_second_pipette(pipettes, target_mount, hardware): def build_mock_stored_pipette_offset(kind="normal"): if kind == "normal": return MagicMock( - return_value=calibration_storage.models.v1.InstrumentOffsetModel( + return_value=calibration_storage.ot2.models.v1.InstrumentOffsetModel( offset=[0, 1, 2], tiprack="tiprack-id", uri="opentrons/opentrons_96_filtertiprack_200ul/1", @@ -166,7 +166,7 @@ def build_mock_stored_pipette_offset(kind="normal"): ) elif kind == "custom_tiprack": return MagicMock( - return_value=calibration_storage.models.v1.InstrumentOffsetModel( + return_value=calibration_storage.ot2.models.v1.InstrumentOffsetModel( offset=[0, 1, 2], tiprack="tiprack-id", uri="custom/minimal_labware_def/1", @@ -180,7 +180,7 @@ def build_mock_stored_pipette_offset(kind="normal"): def build_mock_stored_tip_length(kind="normal"): if kind == "normal": - tip_length = calibration_storage.models.v1.TipLengthCalibration( + tip_length = calibration_storage.ot2.models.v1.TipLengthCalibration( tipLength=30, pipette="fake id", tiprack="fake_hash", @@ -190,7 +190,7 @@ def build_mock_stored_tip_length(kind="normal"): ) return MagicMock(return_value=tip_length) elif kind == "custom_tiprack": - tip_length = calibration_storage.models.v1.TipLengthCalibration( + tip_length = calibration_storage.ot2.models.v1.TipLengthCalibration( tipLength=30, pipette="fake id", tiprack="fake_hash", @@ -207,7 +207,7 @@ def build_mock_deck_calibration(kind="normal"): if kind == "normal": attitude = [[1.0008, 0.0052, 0.0], [-0.1, 0.9, 0.0], [0.0, 0.0, 1.0]] return MagicMock( - return_value=calibration_storage.models.v1.DeckCalibrationModel( + return_value=calibration_storage.ot2.models.v1.DeckCalibrationModel( attitude=attitude, source=calibration_storage.types.SourceType.user, last_modified=datetime.datetime.now(), @@ -215,7 +215,7 @@ def build_mock_deck_calibration(kind="normal"): ) elif kind == "identity": return MagicMock( - return_value=calibration_storage.models.v1.DeckCalibrationModel( + return_value=calibration_storage.ot2.models.v1.DeckCalibrationModel( attitude=robot_configs.defaults_ot2.DEFAULT_DECK_CALIBRATION_V2, source=calibration_storage.types.SourceType.user, last_modified=None, diff --git a/robot-server/tests/robot/calibration/deck/test_user_flow.py b/robot-server/tests/robot/calibration/deck/test_user_flow.py index c27758655bc..f162a291e75 100644 --- a/robot-server/tests/robot/calibration/deck/test_user_flow.py +++ b/robot-server/tests/robot/calibration/deck/test_user_flow.py @@ -3,7 +3,8 @@ from mock import MagicMock, call from typing import List, Tuple from pathlib import Path -from opentrons.calibration_storage import types as cal_types, models +from opentrons.calibration_storage import types as cal_types +from opentrons.calibration_storage.ot2 import models from opentrons.types import Mount, Point from opentrons.hardware_control.instruments.ot2 import pipette from opentrons.config import robot_configs diff --git a/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py b/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py index 8ac068b85d1..a9d3bfb7ee7 100644 --- a/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py +++ b/robot-server/tests/robot/calibration/pipette_offset/test_user_flow.py @@ -9,7 +9,8 @@ mutable_configurations, pipette_load_name_conversions as pipette_load_name, ) -from opentrons.calibration_storage import helpers, types as CSTypes, models +from opentrons.calibration_storage import helpers, types as CSTypes +from opentrons.calibration_storage.ot2 import models from opentrons.types import Mount, Point from opentrons.hardware_control.instruments.ot2 import pipette from opentrons.protocol_api import labware diff --git a/robot-server/tests/robot/calibration/tip_length/test_user_flow.py b/robot-server/tests/robot/calibration/tip_length/test_user_flow.py index 79b21c22129..a0e88204d8b 100644 --- a/robot-server/tests/robot/calibration/tip_length/test_user_flow.py +++ b/robot-server/tests/robot/calibration/tip_length/test_user_flow.py @@ -12,7 +12,8 @@ from opentrons.hardware_control.instruments.ot2 import pipette from opentrons.protocol_api.labware import get_labware_definition from opentrons.util.helpers import utc_now -from opentrons.calibration_storage import types as cal_types, models +from opentrons.calibration_storage import types as cal_types +from opentrons.calibration_storage.ot2 import models from robot_server.service.errors import RobotServerError from robot_server.service.session.models.command_definitions import CalibrationCommand diff --git a/robot-server/tests/runs/test_engine_store.py b/robot-server/tests/runs/test_engine_store.py index b2e0b4eb482..fc22e4ea900 100644 --- a/robot-server/tests/runs/test_engine_store.py +++ b/robot-server/tests/runs/test_engine_store.py @@ -22,6 +22,7 @@ from robot_server.runs.engine_store import ( EngineStore, EngineConflictError, + NoRunnerEnginePairError, get_estop_listener, ) @@ -147,10 +148,10 @@ async def test_clear_engine(subject: EngineStore) -> None: assert subject.current_run_id is None assert isinstance(result, RunResult) - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.engine - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.runner @@ -174,9 +175,9 @@ async def test_clear_idle_engine(subject: EngineStore) -> None: await subject.clear() # TODO: test engine finish is called - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.engine - with pytest.raises(AssertionError): + with pytest.raises(NoRunnerEnginePairError): subject.runner diff --git a/robot-server/tests/service/legacy/routers/test_settings.py b/robot-server/tests/service/legacy/routers/test_settings.py index 05f4a187045..0e5b337c9d9 100644 --- a/robot-server/tests/service/legacy/routers/test_settings.py +++ b/robot-server/tests/service/legacy/routers/test_settings.py @@ -15,6 +15,7 @@ pipette_definition as pip_def, ) from opentrons.types import Mount +from opentrons_shared_data.robot.dev_types import RobotTypeEnum from robot_server import app @@ -592,7 +593,7 @@ def test_reset_success( ): resp = api_client.post("/settings/reset", json=body) assert resp.status_code == 200 - mock_reset.assert_called_once_with(called_with) + mock_reset.assert_called_once_with(called_with, RobotTypeEnum.OT2) def test_reset_invalid_option(api_client, mock_reset, mock_persistence_resetter): diff --git a/shared-data/command/schemas/8.json b/shared-data/command/schemas/8.json new file mode 100644 index 00000000000..81f1d37ca5f --- /dev/null +++ b/shared-data/command/schemas/8.json @@ -0,0 +1,3347 @@ +{ + "title": "CreateCommandUnion", + "description": "Model that validates a union of all CommandCreate models.", + "anyOf": [ + { + "$ref": "#/definitions/AspirateCreate" + }, + { + "$ref": "#/definitions/AspirateInPlaceCreate" + }, + { + "$ref": "#/definitions/CommentCreate" + }, + { + "$ref": "#/definitions/ConfigureForVolumeCreate" + }, + { + "$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/LoadLiquidCreate" + }, + { + "$ref": "#/definitions/LoadModuleCreate" + }, + { + "$ref": "#/definitions/LoadPipetteCreate" + }, + { + "$ref": "#/definitions/MoveLabwareCreate" + }, + { + "$ref": "#/definitions/MoveRelativeCreate" + }, + { + "$ref": "#/definitions/MoveToCoordinatesCreate" + }, + { + "$ref": "#/definitions/MoveToWellCreate" + }, + { + "$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/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/OpenLidCreate" + }, + { + "$ref": "#/definitions/CloseLidCreate" + }, + { + "$ref": "#/definitions/RunProfileCreate" + }, + { + "$ref": "#/definitions/CalibrateGripperCreate" + }, + { + "$ref": "#/definitions/CalibratePipetteCreate" + }, + { + "$ref": "#/definitions/CalibrateModuleCreate" + }, + { + "$ref": "#/definitions/MoveToMaintenancePositionCreate" + } + ], + "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", + "enum": ["top", "bottom", "center"], + "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" + } + } + }, + "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" + } + } + }, + "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/WellLocation" + } + ] + }, + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "Amount of liquid in uL. Must be greater than 0 and less than a pipette-specific maximum volume.", + "exclusiveMinimum": 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"], + "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": "Amount of liquid in uL. Must be greater than 0 and less than a pipette-specific maximum volume.", + "exclusiveMinimum": 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": { + "volume": { + "title": "Volume", + "description": "Amount of liquid in uL. Must be greater than 0 and less than a pipette-specific maximum volume.", + "exclusiveMinimum": 0, + "type": "number" + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["volume", "pipetteId"] + }, + "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"] + }, + "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/WellLocation" + } + ] + }, + "flowRate": { + "title": "Flowrate", + "description": "Speed in \u00b5L/s configured for the pipette", + "exclusiveMinimum": 0, + "type": "number" + }, + "volume": { + "title": "Volume", + "description": "Amount of liquid in uL. Must be greater than 0 and less than a pipette-specific maximum volume.", + "exclusiveMinimum": 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": "Amount of liquid in uL. Must be greater than 0 and less than a pipette-specific maximum volume.", + "exclusiveMinimum": 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"] + }, + "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" + ], + "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"] + }, + "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" + } + ] + }, + "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"] + }, + "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" + ], + "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" + } + }, + "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" + } + ] + }, + "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"] + }, + "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"] + }, + "PickUpTipParams": { + "title": "PickUpTipParams", + "description": "Payload needed to move a pipette 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/WellLocation" + } + ] + }, + "pipetteId": { + "title": "Pipetteid", + "description": "Identifier of pipette to use for liquid handling.", + "type": "string" + } + }, + "required": ["labwareId", "wellName", "pipetteId"] + }, + "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" + } + }, + "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"] + }, + "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"] + }, + "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"] + }, + "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/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"] + }, + "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"] + }, + "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/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"] + }, + "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"] + } + }, + "$id": "opentronsCommandSchemaV8", + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/shared-data/command/types/annotation.ts b/shared-data/command/types/annotation.ts new file mode 100644 index 00000000000..5d9ed3aa4d6 --- /dev/null +++ b/shared-data/command/types/annotation.ts @@ -0,0 +1,37 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' + +export type AnnotationCreateCommand = CommentCreateCommand | CustomCreateCommand + +export type AnnotationRunTimeCommand = + | CommentRunTimeCommand + | CustomRunTimeCommand + +export interface CommentCreateCommand extends CommonCommandCreateInfo { + commandType: 'comment' + params: CommentParams +} + +export interface CommentRunTimeCommand + extends CommonCommandRunTimeInfo, + CommentCreateCommand { + result?: any +} + +interface CommentParams { + message: string +} + +export interface CustomCreateCommand extends CommonCommandCreateInfo { + commandType: 'custom' + params: CustomParams +} + +export interface CustomRunTimeCommand + extends CommonCommandRunTimeInfo, + CustomCreateCommand { + result?: any +} + +interface CustomParams { + [key: string]: any +} diff --git a/shared-data/command/types/calibration.ts b/shared-data/command/types/calibration.ts new file mode 100644 index 00000000000..dad6b486fef --- /dev/null +++ b/shared-data/command/types/calibration.ts @@ -0,0 +1,84 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' +import type { + PipetteMount, + GantryMount, + LabwareOffset, + Coordinates, +} from '../../js/types' +// TODO (sb 10/26/22): Separate out calibration commands from protocol schema in RAUT-272 +export interface CalibratePipetteCreateCommand extends CommonCommandCreateInfo { + commandType: 'calibration/calibratePipette' + params: CalibratePipetteParams +} +export interface CalibrateGripperCreateCommand extends CommonCommandCreateInfo { + commandType: 'calibration/calibrateGripper' + params: CalibrateGripperParams +} +export interface CalibrateModuleCreateCommand extends CommonCommandCreateInfo { + commandType: 'calibration/calibrateModule' + params: CalibrateModuleParams +} +export interface MoveToMaintenancePositionCreateCommand + extends CommonCommandCreateInfo { + commandType: 'calibration/moveToMaintenancePosition' + params: MoveToMaintenancePositionParams +} + +export interface CalibratePipetteRunTimeCommand + extends CommonCommandRunTimeInfo, + CalibratePipetteCreateCommand { + result?: CalibratePipetteResult +} +export interface CalibrateGripperRunTimeCommand + extends CommonCommandRunTimeInfo, + CalibrateGripperCreateCommand { + result?: CalibrateGripperResult +} +export interface CalibrateModuleRunTimeCommand + extends CommonCommandRunTimeInfo, + CalibrateModuleCreateCommand { + result?: CalibrateModuleResult +} +export interface MoveToMaintenancePositionRunTimeCommand + extends CommonCommandRunTimeInfo, + MoveToMaintenancePositionCreateCommand { + result?: {} +} + +export type CalibrationRunTimeCommand = + | CalibratePipetteRunTimeCommand + | CalibrateGripperRunTimeCommand + | CalibrateModuleRunTimeCommand + | MoveToMaintenancePositionRunTimeCommand + +export type CalibrationCreateCommand = + | CalibratePipetteCreateCommand + | CalibrateGripperCreateCommand + | CalibrateModuleCreateCommand + | MoveToMaintenancePositionCreateCommand + +interface CalibratePipetteParams { + mount: PipetteMount +} +interface CalibrateGripperParams { + jaw: 'front' | 'rear' + otherJawOffset?: Coordinates +} +interface CalibrateModuleParams { + moduleId: string + labwareId: string + mount: PipetteMount +} +interface CalibratePipetteResult { + pipetteOffset: LabwareOffset +} +interface CalibrateGripperResult { + jawOffset: Coordinates +} +interface MoveToMaintenancePositionParams { + mount: GantryMount + maintenancePosition?: 'attachPlate' | 'attachInstrument' +} +interface CalibrateModuleResult { + moduleOffset: Coordinates +} diff --git a/shared-data/command/types/gantry.ts b/shared-data/command/types/gantry.ts new file mode 100644 index 00000000000..9d194255fba --- /dev/null +++ b/shared-data/command/types/gantry.ts @@ -0,0 +1,154 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' +import type { + Coordinates, + MotorAxes, + MotorAxis, + GantryMount, +} from '../../js/types' + +export interface MoveToSlotCreateCommand extends CommonCommandCreateInfo { + commandType: 'moveToSlot' + params: MoveToSlotParams +} +export interface MoveToSlotRunTimeCommand + extends CommonCommandRunTimeInfo, + MoveToSlotCreateCommand { + result?: {} +} +export interface MoveToWellCreateCommand extends CommonCommandCreateInfo { + commandType: 'moveToWell' + params: MoveToWellParams +} +export interface MoveToWellRunTimeCommand + extends CommonCommandRunTimeInfo, + MoveToWellCreateCommand { + result?: {} +} +export interface MoveToCoordinatesCreateCommand + extends CommonCommandCreateInfo { + commandType: 'moveToCoordinates' + params: MoveToCoordinatesParams +} +export interface MoveToCoordinatesRunTimeCommand + extends CommonCommandRunTimeInfo, + MoveToCoordinatesCreateCommand { + result?: {} +} +export interface MoveRelativeCreateCommand extends CommonCommandCreateInfo { + commandType: 'moveRelative' + params: MoveRelativeParams +} +export interface MoveRelativeRunTimeCommand + extends CommonCommandRunTimeInfo, + MoveRelativeCreateCommand { + result?: { + position: Coordinates + } +} +export interface SavePositionCreateCommand extends CommonCommandCreateInfo { + commandType: 'savePosition' + params: SavePositionParams +} +export interface SavePositionRunTimeCommand + extends CommonCommandRunTimeInfo, + SavePositionCreateCommand { + result?: { + positionId: string + position: Coordinates + } +} +export interface HomeCreateCommand extends CommonCommandCreateInfo { + commandType: 'home' + params: HomeParams +} +export interface HomeRunTimeCommand + extends CommonCommandRunTimeInfo, + HomeCreateCommand { + result?: {} +} + +export interface RetractAxisCreateCommand extends CommonCommandCreateInfo { + commandType: 'retractAxis' + params: RetractAxisParams +} +export interface RetractAxisRunTimeCommand + extends CommonCommandRunTimeInfo, + RetractAxisCreateCommand { + result?: {} +} + +export type GantryRunTimeCommand = + | MoveToSlotRunTimeCommand + | MoveToWellRunTimeCommand + | MoveToCoordinatesRunTimeCommand + | MoveRelativeRunTimeCommand + | SavePositionRunTimeCommand + | HomeRunTimeCommand + | RetractAxisRunTimeCommand + +export type GantryCreateCommand = + | MoveToSlotCreateCommand + | MoveToWellCreateCommand + | MoveToCoordinatesCreateCommand + | MoveRelativeCreateCommand + | SavePositionCreateCommand + | HomeCreateCommand + | RetractAxisCreateCommand +interface MoveToSlotParams { + pipetteId: string + slotName: string + offset?: { + x: number + y: number + z: number + } + minimumZHeight?: number + forceDirect?: boolean +} + +export interface MoveToWellParams { + pipetteId: string + labwareId: string + wellName: string + wellLocation?: { + origin?: 'top' | 'bottom' + offset?: { + x?: number + y?: number + z?: number + } + } + minimumZHeight?: number + forceDirect?: boolean +} + +interface MoveToCoordinatesParams { + pipetteId: string + coordinates: { + x: number + y: number + z: number + } + minimumZHeight?: number + forceDirect?: boolean +} + +interface MoveRelativeParams { + pipetteId: string + axis: 'x' | 'y' | 'z' + distance: number +} + +interface SavePositionParams { + pipetteId: string // pipette to use in measurement + positionId?: string // position ID, auto-assigned if left blank +} + +interface HomeParams { + axes?: MotorAxes + skipIfMountPositionOk?: GantryMount // If specified, only home if position invalid +} + +interface RetractAxisParams { + axis: MotorAxis +} diff --git a/shared-data/command/types/incidental.ts b/shared-data/command/types/incidental.ts new file mode 100644 index 00000000000..9c67ea05106 --- /dev/null +++ b/shared-data/command/types/incidental.ts @@ -0,0 +1,21 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' +import type { StatusBarAnimation } from '../../js/types' + +export type IncidentalCreateCommand = SetStatusBarCreateCommand + +export type IncidentalRunTimeCommand = SetStatusBarRunTimeCommand + +export interface SetStatusBarCreateCommand extends CommonCommandCreateInfo { + commandType: 'setStatusBar' + params: SetStatusBarParams +} + +export interface SetStatusBarRunTimeCommand + extends CommonCommandRunTimeInfo, + SetStatusBarCreateCommand { + result?: any +} + +interface SetStatusBarParams { + animation: StatusBarAnimation +} diff --git a/shared-data/command/types/index.ts b/shared-data/command/types/index.ts new file mode 100644 index 00000000000..d3994fc5337 --- /dev/null +++ b/shared-data/command/types/index.ts @@ -0,0 +1,77 @@ +import type { + PipettingRunTimeCommand, + PipettingCreateCommand, +} from './pipetting' +import type { GantryRunTimeCommand, GantryCreateCommand } from './gantry' +import type { ModuleRunTimeCommand, ModuleCreateCommand } from './module' +import type { SetupRunTimeCommand, SetupCreateCommand } from './setup' +import type { TimingRunTimeCommand, TimingCreateCommand } from './timing' +import type { + IncidentalCreateCommand, + IncidentalRunTimeCommand, +} from './incidental' +import type { + AnnotationRunTimeCommand, + AnnotationCreateCommand, +} from './annotation' +import type { + CalibrationRunTimeCommand, + CalibrationCreateCommand, +} from './calibration' + +export * from './annotation' +export * from './calibration' +export * from './gantry' +export * from './incidental' +export * from './module' +export * from './pipetting' +export * from './setup' +export * from './timing' + +// NOTE: these key/value pairs will only be present on commands at analysis/run time +// they pertain only to the actual execution status of a command on hardware, as opposed to +// the command's identity and parameters which can be known prior to runtime + +export type CommandStatus = 'queued' | 'running' | 'succeeded' | 'failed' +export interface CommonCommandRunTimeInfo { + key?: string + id: string + status: CommandStatus + error?: RunCommandError | null + createdAt: string + startedAt: string | null + completedAt: string | null + intent?: 'protocol' | 'setup' +} +export interface CommonCommandCreateInfo { + key?: string + meta?: { [key: string]: any } +} + +export type CreateCommand = + | PipettingCreateCommand // involves the pipettes plunger motor + | GantryCreateCommand // movement that only effects the x,y,z position of the gantry/pipette + | ModuleCreateCommand // directed at a hardware module + | SetupCreateCommand // only effecting robot's equipment setup (pipettes, labware, modules, liquid), no hardware side-effects + | TimingCreateCommand // effecting the timing of command execution + | CalibrationCreateCommand // for automatic pipette calibration + | AnnotationCreateCommand // annotating command execution + | IncidentalCreateCommand // command with only incidental effects (status bar animations) + +// commands will be required to have a key, but will not be created with one +export type RunTimeCommand = + | PipettingRunTimeCommand // involves the pipettes plunger motor + | GantryRunTimeCommand // movement that only effects the x,y,z position of the gantry/pipette + | ModuleRunTimeCommand // directed at a hardware module + | SetupRunTimeCommand // only effecting robot's equipment setup (pipettes, labware, modules, liquid), no hardware side-effects + | TimingRunTimeCommand // effecting the timing of command execution + | CalibrationRunTimeCommand // for automatic pipette calibration + | AnnotationRunTimeCommand // annotating command execution + | IncidentalRunTimeCommand // command with only incidental effects (status bar animations) + +interface RunCommandError { + id: string + errorType: string + createdAt: string + detail: string +} diff --git a/shared-data/command/types/module.ts b/shared-data/command/types/module.ts new file mode 100644 index 00000000000..ff2787bfa25 --- /dev/null +++ b/shared-data/command/types/module.ts @@ -0,0 +1,307 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' + +export type ModuleRunTimeCommand = + | MagneticModuleEngageMagnetRunTimeCommand + | MagneticModuleDisengageRunTimeCommand + | TemperatureModuleSetTargetTemperatureRunTimeCommand + | TemperatureModuleDeactivateRunTimeCommand + | TemperatureModuleAwaitTemperatureRunTimeCommand + | TCSetTargetBlockTemperatureRunTimeCommand + | TCSetTargetLidTemperatureRunTimeCommand + | TCWaitForBlockTemperatureRunTimeCommand + | TCWaitForLidTemperatureRunTimeCommand + | TCOpenLidRunTimeCommand + | TCCloseLidRunTimeCommand + | TCDeactivateBlockRunTimeCommand + | TCDeactivateLidRunTimeCommand + | TCRunProfileRunTimeCommand + | TCAwaitProfileCompleteRunTimeCommand + | HeaterShakerSetTargetTemperatureRunTimeCommand + | HeaterShakerWaitForTemperatureRunTimeCommand + | HeaterShakerSetAndWaitForShakeSpeedRunTimeCommand + | HeaterShakerOpenLatchRunTimeCommand + | HeaterShakerCloseLatchRunTimeCommand + | HeaterShakerDeactivateHeaterRunTimeCommand + | HeaterShakerDeactivateShakerRunTimeCommand + +export type ModuleCreateCommand = + | MagneticModuleEngageMagnetCreateCommand + | MagneticModuleDisengageCreateCommand + | TemperatureModuleSetTargetTemperatureCreateCommand + | TemperatureModuleDeactivateCreateCommand + | TemperatureModuleAwaitTemperatureCreateCommand + | TCSetTargetBlockTemperatureCreateCommand + | TCSetTargetLidTemperatureCreateCommand + | TCWaitForBlockTemperatureCreateCommand + | TCWaitForLidTemperatureCreateCommand + | TCOpenLidCreateCommand + | TCCloseLidCreateCommand + | TCDeactivateBlockCreateCommand + | TCDeactivateLidCreateCommand + | TCRunProfileCreateCommand + | TCAwaitProfileCompleteCreateCommand + | HeaterShakerWaitForTemperatureCreateCommand + | HeaterShakerSetAndWaitForShakeSpeedCreateCommand + | HeaterShakerOpenLatchCreateCommand + | HeaterShakerCloseLatchCreateCommand + | HeaterShakerDeactivateHeaterCreateCommand + | HeaterShakerDeactivateShakerCreateCommand + | HeaterShakerSetTargetTemperatureCreateCommand + +export interface MagneticModuleEngageMagnetCreateCommand + extends CommonCommandCreateInfo { + commandType: 'magneticModule/engage' + params: EngageMagnetParams +} +export interface MagneticModuleEngageMagnetRunTimeCommand + extends CommonCommandRunTimeInfo, + MagneticModuleEngageMagnetCreateCommand { + result?: any +} +export interface MagneticModuleDisengageCreateCommand + extends CommonCommandCreateInfo { + commandType: 'magneticModule/disengage' + params: ModuleOnlyParams +} +export interface MagneticModuleDisengageRunTimeCommand + extends CommonCommandRunTimeInfo, + MagneticModuleDisengageCreateCommand { + result?: any +} +export interface TemperatureModuleSetTargetTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'temperatureModule/setTargetTemperature' + params: TemperatureParams +} +export interface TemperatureModuleSetTargetTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + TemperatureModuleSetTargetTemperatureCreateCommand { + result?: any +} +export interface TemperatureModuleDeactivateCreateCommand + extends CommonCommandCreateInfo { + commandType: 'temperatureModule/deactivate' + params: ModuleOnlyParams +} +export interface TemperatureModuleDeactivateRunTimeCommand + extends CommonCommandRunTimeInfo, + TemperatureModuleDeactivateCreateCommand { + result?: any +} +export interface TemperatureModuleAwaitTemperatureParams { + // same params as TemperatureParams except celsius is optional + moduleId: string + celsius?: number +} +export interface TemperatureModuleAwaitTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'temperatureModule/waitForTemperature' + params: TemperatureModuleAwaitTemperatureParams +} +export interface TemperatureModuleAwaitTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + TemperatureModuleAwaitTemperatureCreateCommand { + result?: any +} +export interface TCSetTargetBlockTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'thermocycler/setTargetBlockTemperature' + params: ThermocyclerSetTargetBlockTemperatureParams +} +export interface TCSetTargetBlockTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + TCSetTargetBlockTemperatureCreateCommand { + result?: any +} +export interface TCSetTargetLidTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'thermocycler/setTargetLidTemperature' + params: TemperatureParams +} +export interface TCSetTargetLidTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + TCSetTargetLidTemperatureCreateCommand { + result?: any +} +export interface TCWaitForBlockTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'thermocycler/waitForBlockTemperature' + params: ModuleOnlyParams +} +export interface TCWaitForBlockTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + TCWaitForBlockTemperatureCreateCommand { + result?: any +} +export interface TCWaitForLidTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'thermocycler/waitForLidTemperature' + params: ModuleOnlyParams +} +export interface TCWaitForLidTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + TCWaitForLidTemperatureCreateCommand { + result?: any +} +export interface TCOpenLidCreateCommand extends CommonCommandCreateInfo { + commandType: 'thermocycler/openLid' + params: ModuleOnlyParams +} +export interface TCOpenLidRunTimeCommand + extends CommonCommandRunTimeInfo, + TCOpenLidCreateCommand { + result?: any +} +export interface TCCloseLidCreateCommand extends CommonCommandCreateInfo { + commandType: 'thermocycler/closeLid' + params: ModuleOnlyParams +} +export interface TCCloseLidRunTimeCommand + extends CommonCommandRunTimeInfo, + TCCloseLidCreateCommand { + result?: any +} +export interface TCDeactivateBlockCreateCommand + extends CommonCommandCreateInfo { + commandType: 'thermocycler/deactivateBlock' + params: ModuleOnlyParams +} +export interface TCDeactivateBlockRunTimeCommand + extends CommonCommandRunTimeInfo, + TCDeactivateBlockCreateCommand { + result?: any +} +export interface TCDeactivateLidCreateCommand extends CommonCommandCreateInfo { + commandType: 'thermocycler/deactivateLid' + params: ModuleOnlyParams +} +export interface TCDeactivateLidRunTimeCommand + extends CommonCommandRunTimeInfo, + TCDeactivateLidCreateCommand { + result?: any +} +export interface TCRunProfileCreateCommand extends CommonCommandCreateInfo { + commandType: 'thermocycler/runProfile' + params: TCProfileParams +} +export interface TCRunProfileRunTimeCommand + extends CommonCommandRunTimeInfo, + TCRunProfileCreateCommand { + result?: any +} +export interface TCAwaitProfileCompleteCreateCommand + extends CommonCommandCreateInfo { + commandType: 'thermocycler/awaitProfileComplete' + params: ModuleOnlyParams +} +export interface TCAwaitProfileCompleteRunTimeCommand + extends CommonCommandRunTimeInfo, + TCAwaitProfileCompleteCreateCommand { + result?: any +} +export interface HeaterShakerSetTargetTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/setTargetTemperature' + params: TemperatureParams +} +export interface HeaterShakerSetTargetTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerSetTargetTemperatureCreateCommand { + result?: any +} +export interface HeaterShakerWaitForTemperatureCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/waitForTemperature' + params: ModuleOnlyParams +} +export interface HeaterShakerWaitForTemperatureRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerWaitForTemperatureCreateCommand { + result?: any +} +export interface HeaterShakerSetAndWaitForShakeSpeedCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/setAndWaitForShakeSpeed' + params: ShakeSpeedParams +} +export interface HeaterShakerSetAndWaitForShakeSpeedRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerSetAndWaitForShakeSpeedCreateCommand { + result?: any +} +export interface HeaterShakerDeactivateHeaterCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/deactivateHeater' + params: ModuleOnlyParams +} +export interface HeaterShakerDeactivateHeaterRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerDeactivateHeaterCreateCommand { + result?: any +} +export interface HeaterShakerOpenLatchCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/openLabwareLatch' + params: ModuleOnlyParams +} +export interface HeaterShakerOpenLatchRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerOpenLatchCreateCommand { + result?: any +} +export interface HeaterShakerCloseLatchCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/closeLabwareLatch' + params: ModuleOnlyParams +} +export interface HeaterShakerCloseLatchRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerCloseLatchCreateCommand { + result?: any +} +export interface HeaterShakerDeactivateShakerCreateCommand + extends CommonCommandCreateInfo { + commandType: 'heaterShaker/deactivateShaker' + params: ModuleOnlyParams +} +export interface HeaterShakerDeactivateShakerRunTimeCommand + extends CommonCommandRunTimeInfo, + HeaterShakerDeactivateShakerCreateCommand { + result?: any +} + +export interface EngageMagnetParams { + moduleId: string + height: number +} + +export interface TemperatureParams { + moduleId: string + celsius: number +} +export interface ShakeSpeedParams { + moduleId: string + rpm: number +} + +export interface AtomicProfileStep { + holdSeconds: number + celsius: number +} + +export interface TCProfileParams { + moduleId: string + profile: AtomicProfileStep[] + blockMaxVolumeUl?: number +} + +export interface ModuleOnlyParams { + moduleId: string +} + +export interface ThermocyclerSetTargetBlockTemperatureParams { + moduleId: string + celsius: number + volume?: number + holdTimeSeconds?: number +} diff --git a/shared-data/command/types/pipetting.ts b/shared-data/command/types/pipetting.ts new file mode 100644 index 00000000000..1a0c0dd36c8 --- /dev/null +++ b/shared-data/command/types/pipetting.ts @@ -0,0 +1,190 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' +export type PipettingRunTimeCommand = + | AspirateRunTimeCommand + | DispenseRunTimeCommand + | BlowoutRunTimeCommand + | BlowoutInPlaceRunTimeCommand + | TouchTipRunTimeCommand + | PickUpTipRunTimeCommand + | DropTipRunTimeCommand + | DropTipInPlaceRunTimeCommand + | ConfigureForVolumeRunTimeCommand + | PrepareToAspirateRunTimeCommand + +export type PipettingCreateCommand = + | AspirateCreateCommand + | DispenseCreateCommand + | BlowoutCreateCommand + | BlowoutInPlaceCreateCommand + | TouchTipCreateCommand + | PickUpTipCreateCommand + | DropTipCreateCommand + | DropTipInPlaceCreateCommand + | ConfigureForVolumeCreateCommand + | PrepareToAspirateCreateCommand + +export interface ConfigureForVolumeCreateCommand + extends CommonCommandCreateInfo { + commandType: 'configureForVolume' + params: ConfigureForVolumeParams +} + +export interface ConfigureForVolumeParams { + pipetteId: string + volume: number +} +export interface ConfigureForVolumeRunTimeCommand + extends CommonCommandRunTimeInfo, + ConfigureForVolumeCreateCommand { + result?: BasicLiquidHandlingResult +} +export interface AspirateCreateCommand extends CommonCommandCreateInfo { + commandType: 'aspirate' + params: AspDispAirgapParams +} +export interface AspirateRunTimeCommand + extends CommonCommandRunTimeInfo, + AspirateCreateCommand { + result?: BasicLiquidHandlingResult +} + +export type DispenseParams = AspDispAirgapParams & { pushOut?: number } +export interface DispenseCreateCommand extends CommonCommandCreateInfo { + commandType: 'dispense' + params: DispenseParams +} +export interface DispenseRunTimeCommand + extends CommonCommandRunTimeInfo, + DispenseCreateCommand { + result?: BasicLiquidHandlingResult +} +export interface BlowoutCreateCommand extends CommonCommandCreateInfo { + commandType: 'blowout' + params: BlowoutParams +} +export interface BlowoutRunTimeCommand + extends CommonCommandRunTimeInfo, + BlowoutCreateCommand { + result?: BasicLiquidHandlingResult +} +export interface BlowoutInPlaceCreateCommand extends CommonCommandCreateInfo { + commandType: 'blowOutInPlace' + params: BlowoutInPlaceParams +} +export interface BlowoutInPlaceRunTimeCommand + extends CommonCommandRunTimeInfo, + BlowoutInPlaceCreateCommand { + result?: BasicLiquidHandlingResult +} + +export interface TouchTipCreateCommand extends CommonCommandCreateInfo { + commandType: 'touchTip' + params: TouchTipParams +} +export interface TouchTipRunTimeCommand + extends CommonCommandRunTimeInfo, + TouchTipCreateCommand { + result?: BasicLiquidHandlingResult +} +export interface PickUpTipCreateCommand extends CommonCommandCreateInfo { + commandType: 'pickUpTip' + params: PickUpTipParams +} +export interface PickUpTipRunTimeCommand + extends CommonCommandRunTimeInfo, + PickUpTipCreateCommand { + result?: any +} +export interface DropTipCreateCommand extends CommonCommandCreateInfo { + commandType: 'dropTip' + params: DropTipParams +} +export interface DropTipRunTimeCommand + extends CommonCommandRunTimeInfo, + DropTipCreateCommand { + result?: any +} +export interface DropTipInPlaceCreateCommand extends CommonCommandCreateInfo { + commandType: 'dropTipInPlace' + params: DropTipInPlaceParams +} +export interface DropTipInPlaceRunTimeCommand + extends CommonCommandRunTimeInfo, + DropTipInPlaceCreateCommand { + result?: any +} + +export interface PrepareToAspirateCreateCommand + extends CommonCommandCreateInfo { + commandType: 'prepareToAspirate' + params: PipetteIdentityParams +} + +export interface PrepareToAspirateRunTimeCommand + extends CommonCommandRunTimeInfo, + PrepareToAspirateCreateCommand { + result?: any +} + +export type AspDispAirgapParams = FlowRateParams & + PipetteAccessParams & + VolumeParams & + WellLocationParam +export type BlowoutParams = FlowRateParams & + PipetteAccessParams & + WellLocationParam +export type TouchTipParams = PipetteAccessParams & WellLocationParam +export type DropTipParams = PipetteAccessParams & { + wellLocation?: { + origin?: 'default' | 'top' | 'center' | 'bottom' + offset?: { + // mm values all default to 0 + x?: number + y?: number + z?: number + } + } +} +export type PickUpTipParams = TouchTipParams +export interface DropTipInPlaceParams { + pipetteId: string +} +export interface BlowoutInPlaceParams { + pipetteId: string + flowRate: number // µL/s +} + +interface FlowRateParams { + flowRate: number // µL/s +} + +interface PipetteIdentityParams { + pipetteId: string +} + +interface PipetteAccessParams extends PipetteIdentityParams { + labwareId: string + wellName: string +} + +interface VolumeParams { + volume: number // µL +} + +interface WellLocationParam { + wellLocation?: { + // default value is 'top' + origin?: 'top' | 'center' | 'bottom' + offset?: { + // mm + // all values default to 0 + x?: number + y?: number + z?: number + } + } +} + +interface BasicLiquidHandlingResult { + volume: number // Amount of liquid in uL handled in the operation +} diff --git a/shared-data/command/types/setup.ts b/shared-data/command/types/setup.ts new file mode 100644 index 00000000000..edb5fd6b6d0 --- /dev/null +++ b/shared-data/command/types/setup.ts @@ -0,0 +1,160 @@ +import type { + CommonCommandRunTimeInfo, + CommonCommandCreateInfo, + LabwareDefinition2, + LabwareOffset, + PipetteName, + ModuleModel, + FixtureLoadName, + Cutout, +} from '../../js' + +export interface LoadPipetteCreateCommand extends CommonCommandCreateInfo { + commandType: 'loadPipette' + params: LoadPipetteParams +} +export interface LoadPipetteRunTimeCommand + extends CommonCommandRunTimeInfo, + Omit { + params: LoadPipetteParams & { + pipetteName: PipetteName + } + result?: LoadPipetteResult +} +export interface LoadLabwareCreateCommand extends CommonCommandCreateInfo { + commandType: 'loadLabware' + params: LoadLabwareParams +} +export interface LoadLabwareRunTimeCommand + extends CommonCommandRunTimeInfo, + LoadLabwareCreateCommand { + result?: LoadLabwareResult +} +export interface MoveLabwareCreateCommand extends CommonCommandCreateInfo { + commandType: 'moveLabware' + params: MoveLabwareParams +} +export interface MoveLabwareRunTimeCommand + extends CommonCommandRunTimeInfo, + MoveLabwareCreateCommand { + result?: MoveLabwareResult +} +export interface LoadModuleCreateCommand extends CommonCommandCreateInfo { + commandType: 'loadModule' + params: LoadModuleParams +} +export interface LoadModuleRunTimeCommand + extends CommonCommandRunTimeInfo, + Omit { + params: LoadModuleParams & { + model: ModuleModel + } + result?: LoadModuleResult +} +export interface LoadLiquidCreateCommand extends CommonCommandCreateInfo { + commandType: 'loadLiquid' + params: LoadLiquidParams +} +export interface LoadLiquidRunTimeCommand + extends CommonCommandRunTimeInfo, + LoadLiquidCreateCommand { + result?: LoadLiquidResult +} +// TODO(jr, 10/31/23): update `loadFixture` to `loadAddressableArea` +export interface LoadFixtureCreateCommand extends CommonCommandCreateInfo { + commandType: 'loadFixture' + params: LoadFixtureParams +} +export interface LoadFixtureRunTimeCommand + extends CommonCommandRunTimeInfo, + LoadFixtureCreateCommand { + result?: LoadLabwareResult +} + +export type SetupRunTimeCommand = + | LoadPipetteRunTimeCommand + | LoadLabwareRunTimeCommand + | LoadFixtureRunTimeCommand + | LoadModuleRunTimeCommand + | LoadLiquidRunTimeCommand + | MoveLabwareRunTimeCommand + +export type SetupCreateCommand = + | LoadPipetteCreateCommand + | LoadLabwareCreateCommand + | LoadFixtureCreateCommand + | LoadModuleCreateCommand + | LoadLiquidCreateCommand + | MoveLabwareCreateCommand + +export type LabwareLocation = + | 'offDeck' + | { slotName: string } + | { moduleId: string } + | { labwareId: string } + +export type NonStackedLocation = + | 'offDeck' + | { slotName: string } + | { moduleId: string } + +export interface ModuleLocation { + slotName: string +} +export interface LoadPipetteParams { + pipetteName: string + pipetteId: string + mount: 'left' | 'right' +} +interface LoadPipetteResult { + pipetteId: string +} +interface LoadLabwareParams { + location: LabwareLocation + version: number + namespace: string + loadName: string + displayName?: string + labwareId?: string +} +interface LoadLabwareResult { + labwareId: string + definition: LabwareDefinition2 + offset: LabwareOffset +} + +export type LabwareMovementStrategy = + | 'usingGripper' + | 'manualMoveWithPause' + | 'manualMoveWithoutPause' + +export interface MoveLabwareParams { + labwareId: string + newLocation: LabwareLocation + strategy: LabwareMovementStrategy +} +interface MoveLabwareResult { + offsetId: string +} +interface LoadModuleParams { + moduleId?: string + location: ModuleLocation + model: ModuleModel +} +interface LoadModuleResult { + moduleId: string +} +interface LoadLiquidParams { + liquidId: string + labwareId: string + volumeByWell: { [wellName: string]: number } +} +interface LoadLiquidResult { + liquidId: string +} + +interface LoadFixtureParams { + location: { cutout: Cutout } + loadName: FixtureLoadName + fixtureId?: string +} diff --git a/shared-data/command/types/timing.ts b/shared-data/command/types/timing.ts new file mode 100644 index 00000000000..7902f613167 --- /dev/null +++ b/shared-data/command/types/timing.ts @@ -0,0 +1,58 @@ +import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' + +export type TimingCreateCommand = + | WaitForResumeCreateCommand + | WaitForDurationCreateCommand + | DeprecatedDelayCreateCommand + +export type TimingRunTimeCommand = + | WaitForResumeRunTimeCommand + | WaitForDurationRunTimeCommand + | DeprecatedDelayRunTimeCommand + +export interface WaitForResumeCreateCommand extends CommonCommandCreateInfo { + // NOTE: `pause` accepted for backwards compatibility + commandType: 'waitForResume' | 'pause' + params: WaitForResumeParams +} + +export interface WaitForResumeRunTimeCommand + extends CommonCommandRunTimeInfo, + WaitForResumeCreateCommand { + result?: any +} + +interface WaitForResumeParams { + message?: string +} + +export interface WaitForDurationCreateCommand extends CommonCommandCreateInfo { + commandType: 'waitForDuration' + params: WaitForDurationParams +} + +export interface WaitForDurationRunTimeCommand + extends CommonCommandRunTimeInfo, + WaitForDurationCreateCommand { + result?: any +} + +interface WaitForDurationParams { + seconds: number + message?: string +} + +export interface DeprecatedDelayCreateCommand extends CommonCommandCreateInfo { + commandType: 'delay' + params: DeprecatedDelayParams +} + +export interface DeprecatedDelayRunTimeCommand + extends CommonCommandRunTimeInfo, + DeprecatedDelayCreateCommand { + result?: {} +} + +type DeprecatedDelayParams = + | { waitForResume: true; message?: string } + | { seconds: number; message?: string } diff --git a/shared-data/commandAnnotation/schemas/1.json b/shared-data/commandAnnotation/schemas/1.json new file mode 100644 index 00000000000..65448d2d03d --- /dev/null +++ b/shared-data/commandAnnotation/schemas/1.json @@ -0,0 +1,74 @@ +{ + "$id": "opentronsCommandAnnotationSchemaV1#", + "$schema": "http://json-schema.org/draft-07/schema#", + "$defs": { + "baseAnnotation": { + "description": "Things all annotations have", + "type": "object", + "required": ["annotationType", "commandKeys"], + "properties": { + "commandKeys": { + "type": "array", + "items": { + "description": "Command keys to which this annotation applies", + "type": "string" + } + }, + "annotationType": { + "description": "The type of annotation (for machine parsing)", + "type": "string" + } + } + }, + "secondOrderCommand": { + "description": "Annotates a group of atomic commands which were the direct result of a second order command (e.g. transfer, consolidate, mix)", + "allOf": [{ "$ref": "#/$defs/baseAnnotation" }], + "type": "object", + "required": [ + "annotationType", + "commandKeys", + "params", + "machineReadableName" + ], + "properties": { + "annotationType": { + "type": "string", + "enum": ["secondOrderCommand"] + }, + "params": { + "description": "key value pairs of the parameters that were passed to the second order command that this annotates", + "type": "object" + }, + "machineReadableName": { + "description": "The name of the second order command in the form that the generating software refers to it. (e.g. 'transfer', 'thermocyclerStep')", + "type": "string" + }, + "userSpecifiedName": { + "description": "The optional user-specified name of the second order command", + "type": "string" + }, + "userSpecifiedDescription": { + "description": "The optional user-specified description of the second order command", + "type": "string" + } + } + }, + "custom": { + "description": "Annotates a group of atomic commands in some manner that Opentrons software does not anticipate or originate", + "allOf": [{ "$ref": "#/$defs/baseAnnotation" }], + "type": "object", + "required": ["annotationType", "commandKeys"], + "properties": { + "annotationType": { + "type": "string", + "enum": ["custom"] + } + }, + "additionalProperties": true + } + }, + "oneOf": [ + { "$ref": "#/$defs/secondOrderCommand" }, + { "$ref": "#/$defs/custom" } + ] +} diff --git a/shared-data/commandAnnotation/types/index.ts b/shared-data/commandAnnotation/types/index.ts new file mode 100644 index 00000000000..83bddd72a96 --- /dev/null +++ b/shared-data/commandAnnotation/types/index.ts @@ -0,0 +1,19 @@ +interface SharedCommandAnnotationProperties { + commandKeys: string[] +} +export type CustomCommandAnnotation = SharedCommandAnnotationProperties & { + [key: string]: any +} & { + annotationType: 'custom' +} +export interface SecondOrderCommandAnnotation + extends SharedCommandAnnotationProperties { + annotationType: 'secondOrderCommand' + machineReadableName: string + params: { [key: string]: any } + userSpecifiedName?: string + userSpecifiedDescription?: string +} +export type CommandAnnotation = + | SecondOrderCommandAnnotation + | CustomCommandAnnotation diff --git a/shared-data/deck/definitions/4/ot2_short_trash.json b/shared-data/deck/definitions/4/ot2_short_trash.json index fd1d904be30..64ebd34a511 100644 --- a/shared-data/deck/definitions/4/ot2_short_trash.json +++ b/shared-data/deck/definitions/4/ot2_short_trash.json @@ -216,76 +216,64 @@ ], "cutouts": [ { - "id": "1", + "id": "cutout1", "position": [0.0, 0.0, 0.0], - "displayName": "Cutout 1", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 1" }, { - "id": "2", + "id": "cutout2", "position": [132.5, 0.0, 0.0], - "displayName": "Cutout 2", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 2" }, { - "id": "3", + "id": "cutout3", "position": [265.0, 0.0, 0.0], - "displayName": "Cutout 3", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 3" }, { - "id": "4", + "id": "cutout4", "position": [0.0, 90.5, 0.0], - "displayName": "Cutout 4", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 4" }, { - "id": "5", + "id": "cutout5", "position": [132.5, 90.5, 0.0], - "displayName": "Cutout 5", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 5" }, { - "id": "6", + "id": "cutout6", "position": [265.0, 90.5, 0.0], - "displayName": "Cutout 6", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 6" }, { - "id": "7", + "id": "cutout7", "position": [0.0, 181.0, 0.0], - "displayName": "Cutout 7", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 7" }, { - "id": "8", + "id": "cutout8", "position": [132.5, 181.0, 0.0], - "displayName": "Cutout 8", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 8" }, { - "id": "9", + "id": "cutout9", "position": [265.0, 181.0, 0.0], - "displayName": "Cutout 9", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 9" }, { - "id": "10", + "id": "cutout10", "position": [0.0, 271.5, 0.0], - "displayName": "Slot 10", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Slot 10" }, { - "id": "11", + "id": "cutout11", "position": [132.5, 271.5, 0.0], - "displayName": "Cutout 11", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 11" }, { - "id": "12", + "id": "cutout12", "position": [265.0, 271.5, 0.0], - "displayName": "Cutout 12", - "compatibleFixtureTypes": ["fixedTrashSlot"] + "displayName": "Cutout 12" } ], "calibrationPoints": [ @@ -349,33 +337,53 @@ "position": [248.37, 351.5, 0.0], "displayName": "Slot 11 Top Right Dot" } + ], + "legacyFixtures": [ + { + "id": "fixedTrash", + "slot": "12", + "labware": "opentrons_1_trash_850ml_fixed", + "displayName": "Fixed Trash" + } ] }, "cutoutFixtures": [ { "id": "singleStandardSlot", - "mayMountTo": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], + "mayMountTo": [ + "cutout1", + "cutout2", + "cutout3", + "cutout4", + "cutout5", + "cutout6", + "cutout7", + "cutout8", + "cutout9", + "cutout10", + "cutout11" + ], "displayName": "Standard Slot", "providesAddressableAreas": { - "1": ["1"], - "2": ["2"], - "3": ["3"], - "4": ["4"], - "5": ["5"], - "6": ["6"], - "7": ["7"], - "8": ["8"], - "9": ["9"], - "10": ["10"], - "11": ["11"] + "cutout1": ["1"], + "cutout2": ["2"], + "cutout3": ["3"], + "cutout4": ["4"], + "cutout5": ["5"], + "cutout6": ["6"], + "cutout7": ["7"], + "cutout8": ["8"], + "cutout9": ["9"], + "cutout10": ["10"], + "cutout11": ["11"] } }, { "id": "fixedTrashSlot", - "mayMountTo": ["12"], + "mayMountTo": ["cutout12"], "displayName": "Fixed Trash", "providesAddressableAreas": { - "12": ["shortFixedTrash"] + "cutout12": ["shortFixedTrash"] } } ] diff --git a/shared-data/deck/definitions/4/ot2_standard.json b/shared-data/deck/definitions/4/ot2_standard.json index 5f7856e379a..e28257ca332 100644 --- a/shared-data/deck/definitions/4/ot2_standard.json +++ b/shared-data/deck/definitions/4/ot2_standard.json @@ -216,76 +216,64 @@ ], "cutouts": [ { - "id": "1", + "id": "cutout1", "position": [0.0, 0.0, 0.0], - "displayName": "Cutout 1", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 1" }, { - "id": "2", + "id": "cutout2", "position": [132.5, 0.0, 0.0], - "displayName": "Cutout 2", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 2" }, { - "id": "3", + "id": "cutout3", "position": [265.0, 0.0, 0.0], - "displayName": "Cutout 3", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 3" }, { - "id": "4", + "id": "cutout4", "position": [0.0, 90.5, 0.0], - "displayName": "Cutout 4", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 4" }, { - "id": "5", + "id": "cutout5", "position": [132.5, 90.5, 0.0], - "displayName": "Cutout 5", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 5" }, { - "id": "6", + "id": "cutout6", "position": [265.0, 90.5, 0.0], - "displayName": "Cutout 6", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 6" }, { - "id": "7", + "id": "cutout7", "position": [0.0, 181.0, 0.0], - "displayName": "Cutout 7", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 7" }, { - "id": "8", + "id": "cutout8", "position": [132.5, 181.0, 0.0], - "displayName": "Cutout 8", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 8" }, { - "id": "9", + "id": "cutout9", "position": [265.0, 181.0, 0.0], - "displayName": "Cutout 9", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 9" }, { - "id": "10", + "id": "cutout10", "position": [0.0, 271.5, 0.0], - "displayName": "Slot 10", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Slot 10" }, { - "id": "11", + "id": "cutout11", "position": [132.5, 271.5, 0.0], - "displayName": "Cutout 11", - "compatibleFixtureTypes": ["singleStandardSlot"] + "displayName": "Cutout 11" }, { - "id": "12", + "id": "cutout12", "position": [265.0, 271.5, 0.0], - "displayName": "Cutout 12", - "compatibleFixtureTypes": ["fixedTrashSlot"] + "displayName": "Cutout 12" } ], "calibrationPoints": [ @@ -349,33 +337,53 @@ "position": [248.37, 351.5, 0.0], "displayName": "Slot 11 Top Right Dot" } + ], + "legacyFixtures": [ + { + "id": "fixedTrash", + "slot": "12", + "labware": "opentrons_1_trash_1100ml_fixed", + "displayName": "Fixed Trash" + } ] }, "cutoutFixtures": [ { "id": "singleStandardSlot", - "mayMountTo": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"], + "mayMountTo": [ + "cutout1", + "cutout2", + "cutout3", + "cutout4", + "cutout5", + "cutout6", + "cutout7", + "cutout8", + "cutout9", + "cutout10", + "cutout11" + ], "displayName": "Standard Slot", "providesAddressableAreas": { - "1": ["1"], - "2": ["2"], - "3": ["3"], - "4": ["4"], - "5": ["5"], - "6": ["6"], - "7": ["7"], - "8": ["8"], - "9": ["9"], - "10": ["10"], - "11": ["11"] + "cutout1": ["1"], + "cutout2": ["2"], + "cutout3": ["3"], + "cutout4": ["4"], + "cutout5": ["5"], + "cutout6": ["6"], + "cutout7": ["7"], + "cutout8": ["8"], + "cutout9": ["9"], + "cutout10": ["10"], + "cutout11": ["11"] } }, { "id": "fixedTrashSlot", - "mayMountTo": ["12"], + "mayMountTo": ["cutout12"], "displayName": "Fixed Trash", "providesAddressableAreas": { - "12": ["fixedTrash"] + "cutout12": ["fixedTrash"] } } ] diff --git a/shared-data/deck/definitions/4/ot3_standard.json b/shared-data/deck/definitions/4/ot3_standard.json index 4a15ae4987a..333fd6d59c3 100644 --- a/shared-data/deck/definitions/4/ot3_standard.json +++ b/shared-data/deck/definitions/4/ot3_standard.json @@ -304,172 +304,159 @@ ], "cutouts": [ { - "id": "D1", + "id": "cutoutD1", "position": [0.0, 0.0, 0.0], - "displayName": "Cutout D1", - "compatibleFixtureTypes": ["singleLeftSlot", "trashBinAdapter"] + "displayName": "Cutout D1" }, { - "id": "D2", + "id": "cutoutD2", "position": [164.0, 0.0, 0.0], - "displayName": "Cutout D2", - "compatibleFixtureTypes": ["singleCenterSlot"] + "displayName": "Cutout D2" }, { - "id": "D3", + "id": "cutoutD3", "position": [328.0, 0.0, 0.0], - "displayName": "Cutout D3", - "compatibleFixtureTypes": [ - "singleRightSlot", - "stagingAreaRightSlot", - "trashBinAdapter", - "wasteChuteRightAdapter", - "wasteChuteRightAdapterWithStagingAreaSlot" - ] + "displayName": "Cutout D3" }, { - "id": "C1", + "id": "cutoutC1", "position": [0.0, 107, 0.0], - "displayName": "Cutout C1", - "compatibleFixtureTypes": ["singleLeftSlot", "trashBinAdapter"] + "displayName": "Cutout C1" }, { - "id": "C2", + "id": "cutoutC2", "position": [164.0, 107, 0.0], - "displayName": "Cutout C2", - "compatibleFixtureTypes": ["singleCenterSlot"] + "displayName": "Cutout C2" }, { - "id": "C3", + "id": "cutoutC3", "position": [328.0, 107, 0.0], - "displayName": "Cutout C3", - "compatibleFixtureTypes": [ - "singleRightSlot", - "stagingAreaRightSlot", - "trashBinAdapter" - ] + "displayName": "Cutout C3" }, { - "id": "B1", + "id": "cutoutB1", "position": [0.0, 214.0, 0.0], - "displayName": "Cutout B1", - "compatibleFixtureTypes": ["singleLeftSlot", "trashBinAdapter"] + "displayName": "Cutout B1" }, { - "id": "B2", + "id": "cutoutB2", "position": [164.0, 214.0, 0.0], - "displayName": "Cutout B2", - "compatibleFixtureTypes": ["singleCenterSlot"] + "displayName": "Cutout B2" }, { - "id": "B3", + "id": "cutoutB3", "position": [328.0, 214.0, 0.0], - "displayName": "Cutout B3", - "compatibleFixtureTypes": [ - "singleRightSlot", - "stagingAreaRightSlot", - "trashBinAdapter" - ] + "displayName": "Cutout B3" }, { - "id": "A1", + "id": "cutoutA1", "position": [0.0, 321.0, 0.0], - "displayName": "Cutout A1", - "compatibleFixtureTypes": ["singleLeftSlot", "trashBinAdapter"] + "displayName": "Cutout A1" }, { - "id": "A2", + "id": "cutoutA2", "position": [164.0, 321.0, 0.0], - "displayName": "Cutout A2", - "compatibleFixtureTypes": ["singleCenterSlot"] + "displayName": "Cutout A2" }, { - "id": "A3", + "id": "cutoutA3", "position": [328.0, 321.0, 0.0], - "displayName": "Cutout A3", - "compatibleFixtureTypes": [ - "singleRightSlot", - "stagingAreaRightSlot", - "trashBinAdapter" - ] + "displayName": "Cutout A3" } ], - "calibrationPoints": [] + "calibrationPoints": [], + "legacyFixtures": [ + { + "id": "fixedTrash", + "slot": "A3", + "labware": "opentrons_1_trash_3200ml_fixed", + "displayName": "Fixed Trash" + } + ] }, "cutoutFixtures": [ { "id": "singleLeftSlot", - "mayMountTo": ["D1", "C1", "B1", "A1"], + "mayMountTo": ["cutoutD1", "cutoutC1", "cutoutB1", "cutoutA1"], "displayName": "Standard Slot Left", "providesAddressableAreas": { - "D1": ["D1"], - "C1": ["C1"], - "B1": ["B1"], - "A1": ["A1"] + "cutoutD1": ["D1"], + "cutoutC1": ["C1"], + "cutoutB1": ["B1"], + "cutoutA1": ["A1"] } }, { "id": "singleCenterSlot", - "mayMountTo": ["D2", "C2", "B2", "A2"], + "mayMountTo": ["cutoutD2", "cutoutC2", "cutoutB2", "cutoutA2"], "displayName": "Standard Slot Center", "providesAddressableAreas": { - "D2": ["D2"], - "C2": ["C2"], - "B2": ["B2"], - "A2": ["A2"] + "cutoutD2": ["D2"], + "cutoutC2": ["C2"], + "cutoutB2": ["B2"], + "cutoutA2": ["A2"] } }, { "id": "singleRightSlot", - "mayMountTo": ["D3", "C3", "B3", "A3"], + "mayMountTo": ["cutoutD3", "cutoutC3", "cutoutB3", "cutoutA3"], "displayName": "Standard Slot Right", "providesAddressableAreas": { - "D3": ["D3"], - "C3": ["C3"], - "B3": ["B3"], - "A3": ["A3"] + "cutoutD3": ["D3"], + "cutoutC3": ["C3"], + "cutoutB3": ["B3"], + "cutoutA3": ["A3"] } }, { "id": "stagingAreaRightSlot", - "mayMountTo": ["D3", "C3", "B3", "A3"], + "mayMountTo": ["cutoutD3", "cutoutC3", "cutoutB3", "cutoutA3"], "displayName": "Staging Area Slot", "providesAddressableAreas": { - "D3": ["D3", "D4"], - "C3": ["C3", "C4"], - "B3": ["B3", "B4"], - "A3": ["A3", "A4"] + "cutoutD3": ["D3", "D4"], + "cutoutC3": ["C3", "C4"], + "cutoutB3": ["B3", "B4"], + "cutoutA3": ["A3", "A4"] } }, { "id": "trashBinAdapter", - "mayMountTo": ["D1", "C1", "B1", "A1", "D3", "C3", "B3", "A3"], + "mayMountTo": [ + "cutoutD1", + "cutoutC1", + "cutoutB1", + "cutoutA1", + "cutoutD3", + "cutoutC3", + "cutoutB3", + "cutoutA3" + ], "displayName": "Slot With Movable Trash", "providesAddressableAreas": { - "D1": ["movableTrash"], - "C1": ["movableTrash"], - "B1": ["movableTrash"], - "A1": ["movableTrash"], - "D3": ["movableTrash"], - "C3": ["movableTrash"], - "B3": ["movableTrash"], - "A3": ["movableTrash"] + "cutoutD1": ["movableTrash"], + "cutoutC1": ["movableTrash"], + "cutoutB1": ["movableTrash"], + "cutoutA1": ["movableTrash"], + "cutoutD3": ["movableTrash"], + "cutoutC3": ["movableTrash"], + "cutoutB3": ["movableTrash"], + "cutoutA3": ["movableTrash"] } }, { "id": "wasteChuteRightAdapterCovered", - "mayMountTo": ["D3"], + "mayMountTo": ["cutoutD3"], "displayName": "Waste Chute Adapter for 1 or 8 Channel Pipettes", "providesAddressableAreas": { - "D3": ["1and8ChannelWasteChute"] + "cutoutD3": ["1and8ChannelWasteChute"] } }, { "id": "wasteChuteRightAdapterNoCover", - "mayMountTo": ["D3"], + "mayMountTo": ["cutoutD3"], "displayName": "Waste Chute Adapter for 96 Channel Pipette or Gripper", "providesAddressableAreas": { - "D3": [ + "cutoutD3": [ "1and8ChannelWasteChute", "96ChannelWasteChute", "gripperWasteChute" @@ -478,18 +465,18 @@ }, { "id": "stagingAreaSlotWithWasteChuteRightAdapterCovered", - "mayMountTo": ["D3"], + "mayMountTo": ["cutoutD3"], "displayName": "Staging Slot With Waste Chute Adapter for 96 Channel Pipette or Gripper", "providesAddressableAreas": { - "D3": ["1and8ChannelWasteChute", "D4"] + "cutoutD3": ["1and8ChannelWasteChute", "D4"] } }, { "id": "stagingAreaSlotWithWasteChuteRightAdapterNoCover", - "mayMountTo": ["D3"], + "mayMountTo": ["cutoutD3"], "displayName": "Staging Slot With Waste Chute Adapter and Staging Area Slot", "providesAddressableAreas": { - "D3": [ + "cutoutD3": [ "1and8ChannelWasteChute", "96ChannelWasteChute", "gripperWasteChute", diff --git a/shared-data/deck/schemas/2.json b/shared-data/deck/schemas/2.json index 545a7e715d1..59bc711fe15 100644 --- a/shared-data/deck/schemas/2.json +++ b/shared-data/deck/schemas/2.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "opentronsDeckSchemaV2", "definitions": { "positiveNumber": { "type": "number", diff --git a/shared-data/deck/schemas/3.json b/shared-data/deck/schemas/3.json index c973ed1c987..efd370b9337 100644 --- a/shared-data/deck/schemas/3.json +++ b/shared-data/deck/schemas/3.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "opentronsDeckSchemaV3", "definitions": { "positiveNumber": { "type": "number", diff --git a/shared-data/deck/schemas/4.json b/shared-data/deck/schemas/4.json index f1d8c8b8fac..7b6f6693b58 100644 --- a/shared-data/deck/schemas/4.json +++ b/shared-data/deck/schemas/4.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "opentronsDeckSchemaV4", "definitions": { "positiveNumber": { "type": "number", @@ -109,7 +110,12 @@ }, "locations": { "type": "object", - "required": ["addressableAreas", "calibrationPoints", "cutouts"], + "required": [ + "addressableAreas", + "calibrationPoints", + "cutouts", + "legacyFixtures" + ], "properties": { "addressableAreas": { "type": "array", @@ -200,12 +206,7 @@ "description": "The machined cutout slots on the deck surface.", "items": { "type": "object", - "required": [ - "id", - "position", - "displayName", - "compatibleFixtureTypes" - ], + "required": ["id", "position", "displayName"], "properties": { "id": { "description": "Unique identifier for the cutout", @@ -218,13 +219,32 @@ "displayName": { "description": "An optional human-readable nickname for this cutout e.g. \"Cutout A1\"", "type": "string" + } + } + } + }, + "legacyFixtures": { + "type": "array", + "description": "Fixed position objects on the deck.", + "items": { + "type": "object", + "required": ["id", "displayName"], + "properties": { + "id": { + "description": "Unique identifier for fixed object", + "type": "string" }, - "compatibleFixtureTypes": { - "description": "A list of cutout fixture types that can be loaded onto this cutout.", - "type": "array", - "items": { - "type": "string" - } + "labware": { + "description": "Valid labware loadName for fixed object", + "type": "string" + }, + "slot": { + "description": "Slot location of the fixed object", + "type": "string" + }, + "displayName": { + "description": "An optional human-readable nickname for this fixture Eg \"Tall Fixed Trash\" or \"Short Fixed Trash\"", + "type": "string" } } } @@ -247,7 +267,7 @@ "type": "string" }, "mayMountTo": { - "description": "A list of compatible cutouts this fixture may be mounted to.", + "description": "A list of compatible cutouts this fixture may be mounted to. These must match `id`s in `cutouts`.", "type": "array", "items": { "type": "string" diff --git a/shared-data/errors/definitions/1/errors.json b/shared-data/errors/definitions/1/errors.json index 8c92d9a0486..28caa98cb7a 100644 --- a/shared-data/errors/definitions/1/errors.json +++ b/shared-data/errors/definitions/1/errors.json @@ -182,6 +182,10 @@ "detail": "Invalid Liquid Class Name", "category": "roboticsInteractionError" }, + "3018": { + "detail": "Tip Detector Not Created", + "category": "roboticsInteractionError" + }, "4000": { "detail": "Unknown or Uncategorized Error", "category": "generalError" @@ -205,6 +209,10 @@ "4005": { "detail": "Command Parameter Limit Violated", "category": "generalError" + }, + "4006": { + "detail": "Invalid Protocol Data", + "category": "generalError" } } } diff --git a/shared-data/js/__tests__/protocolValidation.test.ts b/shared-data/js/__tests__/protocolValidation.test.ts new file mode 100644 index 00000000000..ef5ff808a4c --- /dev/null +++ b/shared-data/js/__tests__/protocolValidation.test.ts @@ -0,0 +1,83 @@ +/** Ensure that we can parse all our fixture json protocols, which also + * ensures that our protocol schemas are correct */ +import path from 'path' +import glob from 'glob' +import { validate } from '../protocols' +import { omit } from 'lodash' + +const relRoot = path.join(__dirname, '../../protocol/fixtures/') + +const protocolFixtures4 = glob.sync( + path.join(__dirname, '../../protocol/fixtures/4/*.json') +) +const protocolFixtures5 = glob.sync( + path.join(__dirname, '../../protocol/fixtures/5/*.json') +) +const protocolFixtures6 = glob.sync( + path.join(__dirname, '../../protocol/fixtures/6/*.json') +) +const protocolFixtures7 = glob.sync( + path.join(__dirname, '../../protocol/fixtures/7/*.json') +) +const protocolFixtures8 = glob.sync( + path.join(__dirname, '../../protocol/fixtures/8/*.json') +) + +describe('check that all fixtures can validate', () => { + const protocolPaths = [ + ...protocolFixtures4, + ...protocolFixtures5, + ...protocolFixtures6, + ...protocolFixtures7, + ...protocolFixtures8, + ] + protocolPaths.forEach(protocolPath => + it(`${path.relative(relRoot, protocolPath)}`, () => { + const protocol = require(protocolPath) + return validate(protocol) + }) + ) +}) + +describe('test that mutating v8 fixtures causes failure', () => { + const base = require('../../protocol/fixtures/8/simpleV8.json') + it('should fail validation with no schema spec', () => { + const mangled = omit(base, '$otSharedSchema') + expect.assertions(1) + return expect(validate(mangled)).rejects.toMatchObject([ + { keyword: 'Invalid protocol schema requested' }, + ]) + }) + it('should fail validation with a schema spec from the future', () => { + const mangled = { ...base, $otSharedSchema: 'opentronsProtocolSchemaV9' } + expect.assertions(1) + return expect(validate(mangled)).rejects.toMatchObject([ + { keyword: 'Invalid protocol schema requested' }, + ]) + }) + it('should fail validation with a mangled schema spec', () => { + const mangled = { ...base, $otSharedSchema: 'asdhasda' } + expect.assertions(1) + return expect(validate(mangled)).rejects.toMatchObject([ + { keyword: 'Invalid protocol schema requested' }, + ]) + }) + it('should fail validation with a mangled command schema spec', () => { + const mangled = { ...base, commandSchemaId: 'asdasd' } + return expect(validate(mangled)).rejects.toMatchObject([ + { keyword: 'Invalid command schema requested' }, + ]) + }) + it('should fail validation with a mangled liquid schema spec', () => { + const mangled = { ...base, liquidSchemaId: 'asdhasdas' } + return expect(validate(mangled)).rejects.toMatchObject([ + { keyword: 'Invalid liquid schema requested' }, + ]) + }) + it('should fail validation with a mangled command annotation schema spec', () => { + const mangled = { ...base, commandAnnotationSchemaId: 'asdasd' } + return expect(validate(mangled)).rejects.toMatchObject([ + { keyword: 'Invalid command annotation schema requested' }, + ]) + }) +}) diff --git a/shared-data/js/helpers/parseProtocolData.ts b/shared-data/js/helpers/parseProtocolData.ts index f6295d8556e..8bf6998bcb7 100644 --- a/shared-data/js/helpers/parseProtocolData.ts +++ b/shared-data/js/helpers/parseProtocolData.ts @@ -1,5 +1,4 @@ import Ajv from 'ajv' -import size from 'lodash/size' import labwareV2Schema from '../../labware/schemas/2.json' import protocolSchemaV1 from '../../protocol/schemas/1.json' import protocolSchemaV2 from '../../protocol/schemas/2.json' @@ -8,12 +7,7 @@ import protocolSchemaV4 from '../../protocol/schemas/4.json' import protocolSchemaV5 from '../../protocol/schemas/5.json' import type { ErrorObject } from 'ajv' -import type { - JsonProtocolFile, - ProtocolAnalysisFile, - ProtocolAnalysisOutput, - ProtocolFileV1, -} from '../../protocol' +import type { JsonProtocolFile } from '../../protocol' export type ProtocolParseErrorKey = 'INVALID_FILE_TYPE' | 'INVALID_JSON_FILE' @@ -42,9 +36,7 @@ const SCHEMA_BY_VERSION: { [version: string]: ProtocolSchema } = { '5': protocolSchemaV5, } -export type PythonProtocolMetadata = ProtocolFileV1<{ - [key: string]: unknown -}> & { +export interface PythonProtocolMetadata { source?: string [key: string]: unknown } @@ -128,12 +120,6 @@ export function validateJsonProtocolFileContents( } } -export function protocolHasLiquids( - protocol: ProtocolAnalysisFile<{}> | ProtocolAnalysisOutput -): boolean { - return 'liquids' in protocol && size(protocol.liquids) > 0 -} - export function getProtocolDesignerApplicationName( protocol: JsonProtocolFile ): string | null { diff --git a/shared-data/js/protocols.ts b/shared-data/js/protocols.ts new file mode 100644 index 00000000000..fc9f1f0a5d8 --- /dev/null +++ b/shared-data/js/protocols.ts @@ -0,0 +1,290 @@ +// Helper functions for validating protocols. Used only in tests right now but +// intended for use as the thing that turns something from a json document into +// a protocol inside the js ecosystem. + +import Ajv from 'ajv' + +import commandSchema8 from '../command/schemas/8.json' +import commandSchema7 from '../command/schemas/7.json' +import commandAnnotationSchema1 from '../commandAnnotation/schemas/1.json' +import liquidSchema1 from '../liquid/schemas/1.json' +import labwareSchema2 from '../labware/schemas/2.json' + +import protocolSchema8 from '../protocol/schemas/8.json' +import protocolSchema7 from '../protocol/schemas/7.json' +import protocolSchema6 from '../protocol/schemas/6.json' +import protocolSchema5 from '../protocol/schemas/5.json' +import protocolSchema4 from '../protocol/schemas/4.json' +import protocolSchema3 from '../protocol/schemas/3.json' +import protocolSchema1 from '../protocol/schemas/1.json' + +import type * as ProtocolSchemas from '../protocol' +import type { CreateCommand } from '../command/types' +import type { CommandAnnotation } from '../commandAnnotation/types' + +export type { ProtocolSchemas } + +const validateCommands8 = ( + toValidate: ProtocolSchemas.ProtocolStructureV8 +): Promise => + new Promise((resolve, reject) => { + const requestedSchema = toValidate.commandSchemaId + switch (requestedSchema) { + case 'opentronsCommandSchemaV8': + resolve(commandSchema8) + break + default: + // eslint-disable-next-line prefer-promise-reject-errors + reject([ + { + keyword: 'Invalid command schema requested', + dataPath: requestedSchema, + schemaPath: '#/properties/commandSchemaId', + params: { allowedValues: ['opentronsCommandSchemaV8'] }, + }, + ]) + break + } + }).then( + schema => + new Promise((resolve, reject) => { + const generatedSchema = { type: 'array', items: schema } + const commandAjv = new Ajv({ allErrors: true, jsonPointers: true }) + const validateCommands = commandAjv.compile(generatedSchema) + const ok = validateCommands(toValidate.commands) + if (!ok) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateCommands.errors) + } + resolve(toValidate.commands) + }) + ) + +const validateCommandAnnotations8 = ( + toValidate: ProtocolSchemas.ProtocolStructureV8 +): Promise => + new Promise((resolve, reject) => { + const requestedSchema = toValidate.commandAnnotationSchemaId + switch (requestedSchema) { + case 'opentronsCommandAnnotationSchemaV1': + resolve(commandAnnotationSchema1) + break + default: + // eslint-disable-next-line prefer-promise-reject-errors + reject([ + { + keyword: 'Invalid command annotation schema requested', + dataPath: requestedSchema, + schemaPath: '#/properties/commandAnnotationSchemaId', + params: { allowedValues: ['opentronsCommandAnnotationSchemaV1'] }, + }, + ]) + } + }).then( + (schema: any) => + new Promise((resolve, reject) => { + const generatedSchema = { + type: 'array', + items: { $ref: 'opentronsCommandAnnotationSchemaV1' }, + } + const annotationAjv = new Ajv({ allErrors: true, jsonPointers: true }) + annotationAjv.addSchema(schema) + const validateAnnotations = annotationAjv.compile(generatedSchema) + const ok = validateAnnotations(toValidate.commandAnnotations) + if (!ok) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateAnnotations.errors) + } + resolve(toValidate.commandAnnotations) + }) + ) + +const validateLiquids8 = ( + toValidate: ProtocolSchemas.ProtocolStructureV8 +): Promise => + new Promise((resolve, reject) => { + const requestedSchema = toValidate.liquidSchemaId + switch (requestedSchema) { + case 'opentronsLiquidSchemaV1': + resolve(liquidSchema1) + break + default: + // eslint-disable-next-line prefer-promise-reject-errors + reject([ + { + keyword: 'Invalid liquid schema requested', + dataPath: requestedSchema, + schemaPath: '#/properties/liquidSchemaId', + params: { allowedValues: ['opentronsLiquidSchemaV1'] }, + }, + ]) + } + }).then( + schema => + new Promise((resolve, reject) => { + const generatedSchema = { + type: 'object', + patternProperties: { '.+': schema }, + } + const liquidAjv = new Ajv({ allErrors: true, jsonPointers: true }) + const validateLiquids = liquidAjv.compile(generatedSchema) + const ok = validateLiquids(toValidate.liquids) + if (!ok) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateLiquids.errors) + } + resolve(toValidate.liquids) + }) + ) + +const validateLabware8 = ( + toValidate: ProtocolSchemas.ProtocolStructureV8 +): Promise => + new Promise((resolve, reject) => { + const requestedSchema = toValidate.labwareDefinitionSchemaId + switch (requestedSchema) { + case 'opentronsLabwareSchemaV2': + resolve(labwareSchema2) + break + default: + // eslint-disable-next-line prefer-promise-reject-errors + reject([ + { + keyword: 'Invalid labware schema requested', + dataPath: requestedSchema, + schemaPath: '#/properties/labwareSchemaId', + params: { allowedValues: ['opentronsLabwareSchemaV2'] }, + }, + ]) + } + }).then( + schema => + new Promise((resolve, reject) => { + const generatedSchema = { + type: 'object', + patternProperties: { '.+': schema }, + } + const labwareAjv = new Ajv({ allErrors: true, jsonPointers: true }) + const validateLabware = labwareAjv.compile(generatedSchema) + const ok = validateLabware(toValidate.labwareDefinitions) + if (!ok) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateLabware.errors) + } + resolve(toValidate.labwareDefinitions) + }) + ) + +const validate8 = (toValidate: any): Promise => + new Promise((resolve, reject) => { + const protoAjv = new Ajv({ allErrors: true, jsonPointers: true }) + const validateProtocol = protoAjv.compile(protocolSchema8) + const valid = validateProtocol(toValidate) + if (!valid) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateProtocol.errors) + } + const validatedProtocol = toValidate as ProtocolSchemas.ProtocolStructureV8 + resolve(validatedProtocol) + }) + .then(protocol => + Promise.all([ + validateCommands8(protocol), + validateCommandAnnotations8(protocol), + validateLiquids8(protocol), + validateLabware8(protocol), + ]) + ) + .then(() => Promise.resolve(toValidate as ProtocolSchemas.ProtocolFileV8)) + +const fakeAjvErrorForBadOTSharedSchema = ( + requestedValue: any +): Ajv.ErrorObject => ({ + keyword: 'Invalid protocol schema requested', + dataPath: requestedValue, + schemaPath: '#/properties/$otSharedSchema', + params: { + allowedValues: [ + '#/protocol/schemas/8', + '#/protocol/schemas/7', + '#/protocol/schemas/6', + '#/protocol/schemas/5', + '#/protocol/schemas/4', + '#/protocol/schemas/3', + '#/protocol/schemas/2', + '#/protocol/schemas/1', + ], + }, +}) + +type ProtocolFileSub7 = + | ProtocolSchemas.ProtocolFileV6 + | ProtocolSchemas.ProtocolFileV5<{}> + | ProtocolSchemas.ProtocolFileV4<{}> + | ProtocolSchemas.ProtocolFileV3<{}> + | ProtocolSchemas.ProtocolFileV1<{}> + +const validateSub7 = ( + toValidate: any, + schemaObj: any +): Promise => + new Promise((resolve, reject) => { + const ajv = new Ajv({ allErrors: true, jsonPointers: true }) + ajv.addSchema(labwareSchema2) + const validateProtocol = ajv.compile(schemaObj) + const ok = validateProtocol(toValidate) + if (!ok) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateProtocol.errors) + } + resolve(toValidate as ProtocolFileSub7) + }) + +const validate7 = (toValidate: any): Promise => + new Promise((resolve, reject) => { + const ajv = new Ajv({ allErrors: true, jsonPointers: true }) + ajv.addSchema([commandSchema7, labwareSchema2]) + const validateProtocol = ajv.compile(protocolSchema7) + const ok = validateProtocol(toValidate) + if (!ok) { + // eslint-disable-next-line prefer-promise-reject-errors + reject(validateProtocol.errors) + } + resolve(toValidate as ProtocolSchemas.ProtocolFileV7) + }) + +// note: rejects with an array of ajv errors +export function validate( + toValidate: any +): Promise { + // eslint-disable-next-line @typescript-eslint/dot-notation + const requestedProtocolSchema = toValidate['$otSharedSchema'] + switch (requestedProtocolSchema) { + case '#/protocol/schemas/8': + return validate8(toValidate) + case '#/protocol/schemas/7': + return validate7(toValidate) + case '#/protocol/schemas/6': + return validateSub7(toValidate, protocolSchema6) + case '#/protocol/schemas/5': + return validateSub7(toValidate, protocolSchema5) + case '#/protocol/schemas/4': + return validateSub7(toValidate, protocolSchema4) + default: + if (!Object.keys(toValidate).includes('$otSharedSchema')) { + const v3SchemaVersion = toValidate.schemaVersion + if (v3SchemaVersion === '3.0.0') { + return validateSub7(toValidate, protocolSchema3) + } + const v1SchemaVersion = toValidate['protocol-schema'] + if (v1SchemaVersion === '1.0.0') { + return validateSub7(toValidate, protocolSchema1) + } + } + + return new Promise((resolve, reject) => { + // eslint-disable-next-line prefer-promise-reject-errors + reject([fakeAjvErrorForBadOTSharedSchema(requestedProtocolSchema)]) + }) + } +} diff --git a/shared-data/js/types.ts b/shared-data/js/types.ts index e3164bfa2b9..07b5302b63d 100644 --- a/shared-data/js/types.ts +++ b/shared-data/js/types.ts @@ -30,7 +30,7 @@ import { WASTE_CHUTE_LOAD_NAME, } from './constants' import type { INode } from 'svgson' -import type { RunTimeCommand } from '../protocol' +import type { RunTimeCommand } from '../command/types' import type { PipetteName } from './pipettes' import type { LabwareLocation } from '../protocol/types/schemaV7/command/setup' @@ -448,6 +448,7 @@ export interface AnalysisError { createdAt: string } +// TODO(BC, 10/25/2023): this type (and others in this file) probably belong in api-client, not here export interface CompletedProtocolAnalysis { id: string status?: 'completed' diff --git a/shared-data/liquid/schemas/1.json b/shared-data/liquid/schemas/1.json new file mode 100644 index 00000000000..9c53175840d --- /dev/null +++ b/shared-data/liquid/schemas/1.json @@ -0,0 +1,21 @@ +{ + "$id": "opentronsLiquidSchemaV1", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Fields describing a single liquid", + "type": "object", + "required": ["displayName", "description"], + "properties": { + "displayName": { + "description": "An human-readable name for this liquid.", + "type": "string" + }, + "description": { + "description": "A description of this liquid.", + "type": "string" + }, + "displayColor": { + "description": "Hex color code, with hash included, to represent the specified liquid. Standard three-value, four-value, six-value, and eight-value syntax are all acceptable.", + "type": "string" + } + } +} diff --git a/shared-data/liquid/types/index.ts b/shared-data/liquid/types/index.ts new file mode 100644 index 00000000000..a68baa5d4c7 --- /dev/null +++ b/shared-data/liquid/types/index.ts @@ -0,0 +1,5 @@ +export interface Liquid { + displayName: string + description: string + displayColor: string +} diff --git a/shared-data/protocol/fixtures/8/moveLabware8.json b/shared-data/protocol/fixtures/8/moveLabware8.json new file mode 100644 index 00000000000..3d114f90262 --- /dev/null +++ b/shared-data/protocol/fixtures/8/moveLabware8.json @@ -0,0 +1,1309 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Move labware", + "author": "engineering ", + "description": "A short test protocol that involves moving labware.", + "tags": ["unitTest"] + }, + "robot": { + "model": "OT-2 Standard", + "deckId": "ot2_standard" + }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": ["fixedTrash", "centerMultichannelOnWells"] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 77, + "x": 82.84, + "y": 53.56, + "z": 5 + } + }, + "brand": { + "brand": "Opentrons" + }, + "groups": [ + { + "wells": ["A1"], + "metadata": {} + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/corning_6_wellplate_16.8ml_flat/1": { + "ordering": [ + ["A1", "B1"], + ["A2", "B2"], + ["A3", "B3"] + ], + "metadata": { + "displayName": "Corning 6 Well Plate 16.8 mL Flat", + "displayVolumeUnits": "mL", + "displayCategory": "wellPlate", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.47, + "zDimension": 20.27 + }, + "parameters": { + "format": "irregular", + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "corning_6_wellplate_16.8ml_flat" + }, + "wells": { + "B1": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 24.76, + "y": 23.16, + "z": 2.87 + }, + "A1": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 24.76, + "y": 62.28, + "z": 2.87 + }, + "B2": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 63.88, + "y": 23.16, + "z": 2.87 + }, + "A2": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 63.88, + "y": 62.28, + "z": 2.87 + }, + "B3": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 103, + "y": 23.16, + "z": 2.87 + }, + "A3": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 103, + "y": 62.28, + "z": 2.87 + } + }, + "brand": { + "brand": "Corning", + "brandId": ["3335", "3506", "3516", "3471"], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Microplates/Assay-Microplates/96-Well-Microplates/Costar%C2%AE-Multiple-Well-Cell-Culture-Plates/p/3335" + ] + }, + "groups": [ + { + "wells": ["A1", "B1", "A2", "B2", "A3", "B3"], + "metadata": { + "wellBottomShape": "flat" + } + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "commandType": "loadLabware", + "params": { + "labwareId": "fixedTrash", + "location": { + "slotName": "12" + }, + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "displayName": "Trash" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "tipRackId", + "location": { "slotName": "1" }, + "loadName": "opentrons_96_tiprack_1000ul", + "namespace": "opentrons", + "version": 1, + "displayName": "Opentrons 96 Tip Rack 10 µL" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "wellPlateId", + "location": { + "slotName": "2" + }, + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "displayName": "Super Duper Plate" + } + }, + { + "commandType": "loadPipette", + "params": { + "pipetteId": "pipetteId", + "pipetteName": "p1000_96", + "mount": "left" + } + }, + { + "commandType": "home", + "params": {} + }, + { + "commandType": "pickUpTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "tipRackId", + "wellName": "A1" + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "B3", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + }, + { + "commandType": "moveLabware", + "params": { + "labwareId": "wellPlateId", + "newLocation": { "slotName": "3" }, + "strategy": "manualMoveWithPause" + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "B3", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + } + ], + "liquids": {}, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "commandAnnotations": [], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1" +} diff --git a/shared-data/protocol/fixtures/8/moveLabwareFlex8.json b/shared-data/protocol/fixtures/8/moveLabwareFlex8.json new file mode 100644 index 00000000000..7ff5ecf582d --- /dev/null +++ b/shared-data/protocol/fixtures/8/moveLabwareFlex8.json @@ -0,0 +1,1309 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Move labware", + "author": "engineering ", + "description": "A short test protocol that involves moving labware.", + "tags": ["unitTest"] + }, + "robot": { + "model": "OT-3 Standard", + "deckId": "ot3_standard" + }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": ["fixedTrash", "centerMultichannelOnWells"] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 77, + "x": 82.84, + "y": 53.56, + "z": 5 + } + }, + "brand": { + "brand": "Opentrons" + }, + "groups": [ + { + "wells": ["A1"], + "metadata": {} + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/corning_6_wellplate_16.8ml_flat/1": { + "ordering": [ + ["A1", "B1"], + ["A2", "B2"], + ["A3", "B3"] + ], + "metadata": { + "displayName": "Corning 6 Well Plate 16.8 mL Flat", + "displayVolumeUnits": "mL", + "displayCategory": "wellPlate", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.47, + "zDimension": 20.27 + }, + "parameters": { + "format": "irregular", + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "corning_6_wellplate_16.8ml_flat" + }, + "wells": { + "B1": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 24.76, + "y": 23.16, + "z": 2.87 + }, + "A1": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 24.76, + "y": 62.28, + "z": 2.87 + }, + "B2": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 63.88, + "y": 23.16, + "z": 2.87 + }, + "A2": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 63.88, + "y": 62.28, + "z": 2.87 + }, + "B3": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 103, + "y": 23.16, + "z": 2.87 + }, + "A3": { + "shape": "circular", + "depth": 17.4, + "diameter": 35.43, + "totalLiquidVolume": 16800, + "x": 103, + "y": 62.28, + "z": 2.87 + } + }, + "brand": { + "brand": "Corning", + "brandId": ["3335", "3506", "3516", "3471"], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Microplates/Assay-Microplates/96-Well-Microplates/Costar%C2%AE-Multiple-Well-Cell-Culture-Plates/p/3335" + ] + }, + "groups": [ + { + "wells": ["A1", "B1", "A2", "B2", "A3", "B3"], + "metadata": { + "wellBottomShape": "flat" + } + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "commandType": "loadLabware", + "params": { + "labwareId": "fixedTrash", + "location": { + "slotName": "12" + }, + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "displayName": "Trash" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "tipRackId", + "location": { "slotName": "1" }, + "loadName": "opentrons_96_tiprack_1000ul", + "namespace": "opentrons", + "version": 1, + "displayName": "Opentrons 96 Tip Rack 10 µL" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "wellPlateId", + "location": { + "slotName": "2" + }, + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "displayName": "Super Duper Plate" + } + }, + { + "commandType": "loadPipette", + "params": { + "pipetteId": "pipetteId", + "pipetteName": "p1000_96", + "mount": "left" + } + }, + { + "commandType": "home", + "params": {} + }, + { + "commandType": "pickUpTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "tipRackId", + "wellName": "A1" + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "B3", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + }, + { + "commandType": "moveLabware", + "params": { + "labwareId": "wellPlateId", + "newLocation": { "slotName": "3" }, + "strategy": "manualMoveWithPause" + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "wellPlateId", + "wellName": "B3", + "wellLocation": { + "origin": "bottom", + "offset": { "z": 2 } + } + } + } + ], + "liquids": {}, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "commandAnnotations": [], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1" +} diff --git a/shared-data/protocol/fixtures/8/simpleFlexV8.json b/shared-data/protocol/fixtures/8/simpleFlexV8.json new file mode 100644 index 00000000000..277d7e636fe --- /dev/null +++ b/shared-data/protocol/fixtures/8/simpleFlexV8.json @@ -0,0 +1,1475 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Simple test protocol", + "author": "engineering ", + "description": "A short test protocol", + "created": 1223131231, + "tags": ["unitTest"] + }, + "robot": { + "model": "OT-3 Standard", + "deckId": "ot3_standard" + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "waterId": { + "displayName": "Water", + "description": "Liquid H2O", + "displayColor": "#7332a8" + } + }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": ["fixedTrash", "centerMultichannelOnWells"] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 77, + "x": 82.84, + "y": 53.56, + "z": 5 + } + }, + "brand": { + "brand": "Opentrons" + }, + "groups": [ + { + "wells": ["A1"], + "metadata": {} + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "example/plate/1": { + "ordering": [ + ["A1", "B1", "C1", "D1"], + ["A2", "B2", "C2", "D2"] + ], + "brand": { + "brand": "foo", + "brandId": [] + }, + "metadata": { + "displayName": "Foo 8 Well Plate 33uL", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL" + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 100 + }, + "wells": { + "A1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 75.43, + "z": 75 + }, + "B1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 56.15, + "z": 75 + }, + "C1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 36.87, + "z": 75 + }, + "D1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 17.59, + "z": 75 + }, + "A2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 75.43, + "z": 75 + }, + "B2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 56.15, + "z": 75 + }, + "C2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 36.87, + "z": 75 + }, + "D2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 17.59, + "z": 75 + } + }, + "groups": [ + { + "metadata": {}, + "wells": ["A1", "B1", "C1", "A2", "B2", "C2"] + } + ], + "parameters": { + "format": "irregular", + "quirks": [], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "foo_8_plate_33ul" + }, + "namespace": "example", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "commandType": "loadPipette", + "params": { + "pipetteId": "pipetteId", + "pipetteName": "p1000_96", + "mount": "left" + } + }, + { + "commandType": "loadModule", + "params": { + "moduleId": "magneticModuleId", + "model": "magneticModuleV2", + "location": { "slotName": "3" } + } + }, + { + "commandType": "loadModule", + "params": { + "moduleId": "temperatureModuleId", + "model": "temperatureModuleV2", + "location": { "slotName": "1" } + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "sourcePlateId", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "temperatureModuleId" + }, + "displayName": "Source Plate" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "destPlateId", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "magneticModuleId" + }, + "displayName": "Sample Collection Plate" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "tipRackId", + "location": { "slotName": "8" }, + "loadName": "opentrons_96_tiprack_1000ul", + "namespace": "opentrons", + "version": 1, + "displayName": "Opentrons 96 Tip Rack 1000 µL" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "fixedTrash", + "location": { + "slotName": "12" + }, + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "displayName": "Trash" + } + }, + { + "commandType": "loadLiquid", + "params": { + "liquidId": "waterId", + "labwareId": "sourcePlateId", + "volumeByWell": { + "A1": 100, + "B1": 100 + } + } + }, + { + "commandType": "home", + "params": {} + }, + { + "commandType": "pickUpTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "tipRackId", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "params": { + "pipetteId": "pipetteId", + "labwareId": "sourcePlateId", + "wellName": "A1", + "volume": 5, + "flowRate": 3, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 2 } + } + } + }, + { + "commandType": "waitForDuration", + "params": { + "seconds": 42 + } + }, + { + "commandType": "dispense", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "volume": 4.5, + "flowRate": 2.5, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 1 } + } + } + }, + { + "commandType": "touchTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "speed": 42.0, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 11 } + } + } + }, + { + "commandType": "blowout", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "flowRate": 2, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 12 } + } + } + }, + { + "commandType": "moveToCoordinates", + "params": { + "pipetteId": "pipetteId", + "coordinates": { "x": 100, "y": 100, "z": 100 } + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B2", + "speed": 12.3 + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B2", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 2, "y": 3, "z": 10 } + }, + "minimumZHeight": 35, + "forceDirect": true + } + }, + { + "commandType": "dropTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "fixedTrash", + "wellName": "A1" + } + }, + { + "commandType": "waitForResume", + "params": { + "message": "pause command" + } + }, + { + "commandType": "moveToCoordinates", + "params": { + "pipetteId": "pipetteId", + "coordinates": { "x": 0, "y": 0, "z": 0 }, + "minimumZHeight": 35, + "forceDirect": true + } + }, + { + "commandType": "moveRelative", + "params": { + "pipetteId": "pipetteId", + "axis": "x", + "distance": 1 + } + }, + { + "commandType": "moveRelative", + "params": { + "pipetteId": "pipetteId", + "axis": "y", + "distance": 0.1 + } + }, + { + "commandType": "savePosition", + "params": { + "pipetteId": "pipetteId" + } + }, + { + "commandType": "moveRelative", + "params": { + "pipetteId": "pipetteId", + "axis": "z", + "distance": 10 + } + }, + { + "commandType": "savePosition", + "params": { + "pipetteId": "pipetteId", + "positionId": "positionId" + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [ + { + "commandKeys": [ + "1abc123", + "2abc123", + "3abc123", + "4abc123", + "5abc123", + "6abc123", + "7abc123" + ], + "annotationType": "custom" + } + ] +} diff --git a/shared-data/protocol/fixtures/8/simpleV8.json b/shared-data/protocol/fixtures/8/simpleV8.json new file mode 100644 index 00000000000..a0b7571745d --- /dev/null +++ b/shared-data/protocol/fixtures/8/simpleV8.json @@ -0,0 +1,1473 @@ +{ + "$otSharedSchema": "#/protocol/schemas/8", + "schemaVersion": 8, + "metadata": { + "protocolName": "Simple test protocol", + "author": "engineering ", + "description": "A short test protocol", + "created": 1223131231, + "tags": ["unitTest"] + }, + "robot": { + "model": "OT-2 Standard", + "deckId": "ot2_standard" + }, + "liquidSchemaId": "opentronsLiquidSchemaV1", + "liquids": { + "waterId": { + "displayName": "Water", + "description": "Liquid H2O", + "displayColor": "#7332a8" + } + }, + "labwareDefinitionSchemaId": "opentronsLabwareSchemaV2", + "labwareDefinitions": { + "opentrons/opentrons_1_trash_1100ml_fixed/1": { + "ordering": [["A1"]], + "metadata": { + "displayCategory": "trash", + "displayVolumeUnits": "mL", + "displayName": "Opentrons Fixed Trash", + "tags": [] + }, + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "dimensions": { + "xDimension": 172.86, + "yDimension": 165.86, + "zDimension": 82 + }, + "parameters": { + "format": "trash", + "isTiprack": false, + "loadName": "opentrons_1_trash_1100ml_fixed", + "isMagneticModuleCompatible": false, + "quirks": ["fixedTrash", "centerMultichannelOnWells"] + }, + "wells": { + "A1": { + "shape": "rectangular", + "yDimension": 165.67, + "xDimension": 107.11, + "totalLiquidVolume": 1100000, + "depth": 77, + "x": 82.84, + "y": 53.56, + "z": 5 + } + }, + "brand": { + "brand": "Opentrons" + }, + "groups": [ + { + "wells": ["A1"], + "metadata": {} + } + ], + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "opentrons/opentrons_96_tiprack_10ul/1": { + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "metadata": { + "displayName": "Opentrons 96 Tip Rack 10 µL", + "displayCategory": "tipRack", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "wells": { + "A1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 86.38, + "y": 11.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "shape": "circular", + "diameter": 3.27, + "totalLiquidVolume": 10, + "x": 113.38, + "y": 11.24, + "z": 25.49 + } + }, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "96Standard", + "isTiprack": true, + "tipLength": 39.2, + "tipOverlap": 3.29, + "isMagneticModuleCompatible": false, + "loadName": "opentrons_96_tiprack_10ul" + }, + "namespace": "opentrons", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + }, + "example/plate/1": { + "ordering": [ + ["A1", "B1", "C1", "D1"], + ["A2", "B2", "C2", "D2"] + ], + "brand": { + "brand": "foo", + "brandId": [] + }, + "metadata": { + "displayName": "Foo 8 Well Plate 33uL", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL" + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 100 + }, + "wells": { + "A1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 75.43, + "z": 75 + }, + "B1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 56.15, + "z": 75 + }, + "C1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 36.87, + "z": 75 + }, + "D1": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 18.21, + "y": 17.59, + "z": 75 + }, + "A2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 75.43, + "z": 75 + }, + "B2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 56.15, + "z": 75 + }, + "C2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 36.87, + "z": 75 + }, + "D2": { + "depth": 25, + "totalLiquidVolume": 33, + "shape": "circular", + "diameter": 10, + "x": 38.1, + "y": 17.59, + "z": 75 + } + }, + "groups": [ + { + "metadata": {}, + "wells": ["A1", "B1", "C1", "A2", "B2", "C2"] + } + ], + "parameters": { + "format": "irregular", + "quirks": [], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "foo_8_plate_33ul" + }, + "namespace": "example", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } + } + }, + "commandSchemaId": "opentronsCommandSchemaV8", + "commands": [ + { + "commandType": "loadPipette", + "params": { + "pipetteId": "pipetteId", + "pipetteName": "p10_single", + "mount": "left" + } + }, + { + "commandType": "loadModule", + "params": { + "moduleId": "magneticModuleId", + "model": "magneticModuleV2", + "location": { "slotName": "3" } + } + }, + { + "commandType": "loadModule", + "params": { + "moduleId": "temperatureModuleId", + "model": "temperatureModuleV2", + "location": { "slotName": "1" } + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "sourcePlateId", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "temperatureModuleId" + }, + "displayName": "Source Plate" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "destPlateId", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 1, + "location": { + "moduleId": "magneticModuleId" + }, + "displayName": "Sample Collection Plate" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "tipRackId", + "location": { "slotName": "8" }, + "loadName": "opentrons_96_tiprack_10ul", + "namespace": "opentrons", + "version": 1, + "displayName": "Opentrons 96 Tip Rack 10 µL" + } + }, + { + "commandType": "loadLabware", + "params": { + "labwareId": "fixedTrash", + "location": { + "slotName": "12" + }, + "loadName": "opentrons_1_trash_1100ml_fixed", + "namespace": "opentrons", + "version": 1, + "displayName": "Trash" + } + }, + { + "commandType": "loadLiquid", + "params": { + "liquidId": "waterId", + "labwareId": "sourcePlateId", + "volumeByWell": { + "A1": 100, + "B1": 100 + } + } + }, + { + "commandType": "home", + "params": {} + }, + { + "commandType": "pickUpTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "tipRackId", + "wellName": "B1" + } + }, + { + "commandType": "aspirate", + "params": { + "pipetteId": "pipetteId", + "labwareId": "sourcePlateId", + "wellName": "A1", + "volume": 5, + "flowRate": 3, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 2 } + } + } + }, + { + "commandType": "waitForDuration", + "params": { + "seconds": 42 + } + }, + { + "commandType": "dispense", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "volume": 4.5, + "flowRate": 2.5, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 1 } + } + } + }, + { + "commandType": "touchTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 11 } + } + } + }, + { + "commandType": "blowout", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B1", + "flowRate": 2, + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0, "y": 0, "z": 12 } + } + } + }, + { + "commandType": "moveToCoordinates", + "params": { + "pipetteId": "pipetteId", + "coordinates": { "x": 100, "y": 100, "z": 100 } + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B2" + } + }, + { + "commandType": "moveToWell", + "params": { + "pipetteId": "pipetteId", + "labwareId": "destPlateId", + "wellName": "B2", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 2, "y": 3, "z": 10 } + }, + "minimumZHeight": 35, + "forceDirect": true + } + }, + { + "commandType": "dropTip", + "params": { + "pipetteId": "pipetteId", + "labwareId": "fixedTrash", + "wellName": "A1" + } + }, + { + "commandType": "waitForResume", + "params": { + "message": "pause command" + } + }, + { + "commandType": "moveToCoordinates", + "params": { + "pipetteId": "pipetteId", + "coordinates": { "x": 0, "y": 0, "z": 0 }, + "minimumZHeight": 35, + "forceDirect": true + } + }, + { + "commandType": "moveRelative", + "params": { + "pipetteId": "pipetteId", + "axis": "x", + "distance": 1 + } + }, + { + "commandType": "moveRelative", + "params": { + "pipetteId": "pipetteId", + "axis": "y", + "distance": 0.1 + } + }, + { + "commandType": "savePosition", + "params": { + "pipetteId": "pipetteId" + } + }, + { + "commandType": "moveRelative", + "params": { + "pipetteId": "pipetteId", + "axis": "z", + "distance": 10 + } + }, + { + "commandType": "savePosition", + "params": { + "pipetteId": "pipetteId", + "positionId": "positionId" + } + } + ], + "commandAnnotationSchemaId": "opentronsCommandAnnotationSchemaV1", + "commandAnnotations": [ + { + "commandKeys": [ + "1abc123", + "2abc123", + "3abc123", + "4abc123", + "5abc123", + "6abc123", + "7abc123" + ], + "annotationType": "custom" + } + ] +} diff --git a/shared-data/protocol/index.ts b/shared-data/protocol/index.ts index 3b8772dcbf1..341eb78c7e2 100644 --- a/shared-data/protocol/index.ts +++ b/shared-data/protocol/index.ts @@ -1,16 +1,24 @@ -import type { ProtocolFile as _ProtocolFileV1 } from './types/schemaV1' -import type { ProtocolFile as _ProtocolFileV3 } from './types/schemaV3' -import type { ProtocolFile as _ProtocolFileV4 } from './types/schemaV4' -import type { ProtocolFile as _ProtocolFileV5 } from './types/schemaV5' -import type { ProtocolFile as _ProtocolFileV6 } from './types/schemaV6' +import type { ProtocolFile as ProtocolFileV1 } from './types/schemaV1' +import type { ProtocolFile as ProtocolFileV3 } from './types/schemaV3' +import type { ProtocolFile as ProtocolFileV4 } from './types/schemaV4' +import type { ProtocolFile as ProtocolFileV5 } from './types/schemaV5' +import type { ProtocolFile as ProtocolFileV6 } from './types/schemaV6' +import type { ProtocolFile as ProtocolFileV7 } from './types/schemaV7' +import type { + ProtocolFile as ProtocolFileV8, + ProtocolStructure as ProtocolStructureV8, +} from './types/schemaV8' -// TODO(mc, 2021-04-27): these awkward re-exports only for flowgen support -// remove when able -export type ProtocolFileV1 = _ProtocolFileV1 -export type ProtocolFileV3 = _ProtocolFileV3 -export type ProtocolFileV4 = _ProtocolFileV4 -export type ProtocolFileV5 = _ProtocolFileV5 -export type ProtocolFileV6 = _ProtocolFileV6 +export type { + ProtocolFileV1, + ProtocolFileV3, + ProtocolFileV4, + ProtocolFileV5, + ProtocolFileV6, + ProtocolFileV7, + ProtocolFileV8, + ProtocolStructureV8, +} export type JsonProtocolFile = | Readonly> @@ -18,5 +26,7 @@ export type JsonProtocolFile = | Readonly> | Readonly> | Readonly> + | Readonly> + | Readonly> -export * from './types/schemaV7' +export * from './types/schemaV8' diff --git a/shared-data/protocol/schemas/8.json b/shared-data/protocol/schemas/8.json new file mode 100644 index 00000000000..ec16453d3d1 --- /dev/null +++ b/shared-data/protocol/schemas/8.json @@ -0,0 +1,157 @@ +{ + "$id": "opentronsProtocolSchemaV8", + "$schema": "http://json-schema.org/draft-07/schema#", + + "definitions": {}, + + "type": "object", + "additionalProperties": false, + "required": [ + "$otSharedSchema", + "schemaVersion", + "metadata", + "robot", + "labwareDefinitions", + "labwareDefinitionSchemaId", + "commands", + "commandSchemaId", + "commandAnnotations", + "commandAnnotationSchemaId", + "liquids", + "liquidSchemaId" + ], + "properties": { + "$otSharedSchema": { + "description": "The path to a valid Opentrons shared schema relative to the shared-data directory, without its extension.", + "enum": ["#/protocol/schemas/8"] + }, + + "schemaVersion": { + "description": "Schema version of a protocol is a single integer", + "enum": [8] + }, + + "metadata": { + "description": "Optional metadata about the protocol", + "type": "object", + + "properties": { + "protocolName": { + "description": "A short, human-readable name for the protocol", + "type": "string" + }, + "author": { + "description": "The author or organization who created the protocol", + "type": "string" + }, + "description": { + "description": "A text description of the protocol.", + "type": ["string", "null"] + }, + + "created": { + "description": "UNIX timestamp when this file was created", + "type": "number" + }, + "lastModified": { + "description": "UNIX timestamp when this file was last modified", + "type": ["number", "null"] + }, + + "category": { + "description": "Category of protocol (eg, \"Basic Pipetting\")", + "type": ["string", "null"] + }, + "subcategory": { + "description": "Subcategory of protocol (eg, \"Cell Plating\")", + "type": ["string", "null"] + }, + "tags": { + "description": "Tags to be used in searching for this protocol", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + + "designerApplication": { + "description": "Optional data & metadata not required to execute the protocol, used by the application that created this protocol", + "type": "object", + "properties": { + "name": { + "description": "Name of the application that created the protocol. Should be namespaced under the organization or individual who owns the organization, eg \"opentrons/protocol-designer\"", + "type": "string" + }, + "version": { + "description": "Version of the application that created the protocol", + "type": "string" + }, + "data": { + "description": "Any data used by the application that created this protocol", + "type": "object" + } + } + }, + + "robot": { + "required": ["model", "deckId"], + "properties": { + "model": { + "description": "Model of the robot this protocol is written for", + "type": "string", + "enum": ["OT-2 Standard", "OT-3 Standard"] + }, + "deckId": { + "description": "Identifier of physical deck this protocol is written for. This should match a definition in the shared-data directory matching the schema id", + "type": "string" + } + } + }, + + "labwareDefinitionSchemaId": { + "description": "The schema to use for labware definitions.", + "type": "string" + }, + + "labwareDefinitions": { + "description": "All labware definitions used by labware in this protocol, keyed by a unique identifier (definitionId)", + "patternProperties": { + ".+": { "type": "object" } + } + }, + + "liquidSchemaId": { + "description": "The schema to use for liquid definitions.", + "type": "string" + }, + + "liquids": { + "description": "All instances of liquid used in this protocol, keyed by a unique identifier (liquidId)", + "patternProperties": { + ".+": { "type": "object" } + } + }, + "commandSchemaId": { + "description": "The schema to which the commands adhere.", + "type": "string" + }, + + "commands": { + "description": "An array of command objects representing steps to be executed on the robot.", + "type": "array", + "items": { "type": "object" } + }, + + "commandAnnotationSchemaId": { + "description": "The schema to which the command annotations adhere.", + "type": "string" + }, + + "commandAnnotations": { + "type": "array", + "items": { "type": "object" } + } + } +} diff --git a/shared-data/protocol/types/schemaV8/index.ts b/shared-data/protocol/types/schemaV8/index.ts new file mode 100644 index 00000000000..0a6972fe271 --- /dev/null +++ b/shared-data/protocol/types/schemaV8/index.ts @@ -0,0 +1,160 @@ +import type { CreateCommand } from '../../../command/types' +import type { + LoadedPipette, + LoadedLabware, + LoadedModule, + Liquid, +} from '../../../js' +import type { CommandAnnotation } from '../../../commandAnnotation/types' +import type { LabwareDefinition2, RobotType } from '../../../js/types' +import type { RunTimeCommand } from '../schemaV8' + +export * from '../../../command/types' + +export interface CommandsStructure { + commandSchemaId: string + commands: any[] +} + +export interface CommandV8Mixin { + commandSchemaId: 'opentronsCommandSchemaV8' + commands: CreateCommand[] +} + +export interface CommandAnnotationsStructure { + commandAnnotationSchemaId: string + commandAnnotations: any[] +} + +export interface CommandAnnotationV1Mixin { + commandAnnotationSchemaId: 'opentronsCommandAnnotationSchemaV1' + commandAnnotations: CommandAnnotation[] +} + +export interface LabwareStructure { + labwareDefinitionSchemaId: string + labwareDefinitions: { + [definitionId: string]: any + } +} + +export interface LabwareV2Mixin { + labwareDefinitionSchemaId: 'opentronsLabwareSchemaV2' + labwareDefinitions: { + [definitionId: string]: LabwareDefinition2 + } +} + +export interface LiquidStructure { + liquidSchemaId: string + liquids: { + [liquidId: string]: any + } +} + +export interface LiquidV1Mixin { + liquidSchemaId: 'opentronsLiquidSchemaV1' + liquids: { + [liquidId: string]: { + displayName: string + description: string + displayColor?: string + } + } +} + +export interface RobotStructure { + model: string + deckId: string +} + +export interface OT2RobotMixin { + robot: { + model: 'OT-2 Standard' + deckId: 'ot2_standard' | 'ot2_short_trash' + } +} + +export interface OT3RobotMixin { + robot: { + model: 'OT-3 Standard' + deckId: 'ot3_standard' + } +} + +export interface ProtocolBase { + $otSharedSchema: '#/protocol/schemas/8' + schemaVersion: 8 + metadata: { + protocolName?: string + author?: string + description?: string | null | undefined + created?: number + lastModified?: number | null | undefined + category?: string | null | undefined + subcategory?: string | null | undefined + tags?: string[] + } + designerApplication?: { + name?: string + version?: string + data?: DesignerApplicationData + } +} + +// NOTE: must be kept in sync with '../schemas/8.json' +export type ProtocolFile< + DesignerApplicationData = {} +> = ProtocolBase & + (OT2RobotMixin | OT3RobotMixin) & + LabwareV2Mixin & + LiquidV1Mixin & + CommandV8Mixin & + CommandAnnotationV1Mixin + +export type ProtocolStructure = ProtocolBase<{}> & + RobotStructure & + LabwareStructure & + LiquidStructure & + CommandsStructure & + CommandAnnotationsStructure + +/** + * This type interface is represents the output of the opentrons analyze cli tool + * which contains the protocol analysis engine + * TODO: reconcile this type with that of the analysis returned from + * the protocols record endpoints on the robot-server + */ +export interface ProtocolAnalysisOutput { + createdAt: string + files: AnalysisSourceFile[] + config: JsonConfig | PythonConfig + metadata: { [key: string]: any } + commands: RunTimeCommand[] + labware: LoadedLabware[] + pipettes: LoadedPipette[] + modules: LoadedModule[] + liquids: Liquid[] + errors: AnalysisError[] + robotType?: RobotType +} + +interface AnalysisSourceFile { + name: string + role: 'main' | 'labware' +} +export interface JsonConfig { + protocolType: 'json' + schemaVersion: number +} +export interface PythonConfig { + protocolType: 'python' + apiVersion: [major: number, minor: number] +} + +interface AnalysisError { + id: string + errorType: string + createdAt: string + detail: string +} diff --git a/shared-data/python/Makefile b/shared-data/python/Makefile index 5b8778e09ef..439d5901574 100644 --- a/shared-data/python/Makefile +++ b/shared-data/python/Makefile @@ -45,7 +45,7 @@ gripper_sources = $(wildcard ../gripper/definitions/*.json) $(wildcard ../grippe json_sources = $(deck_sources) $(labware_sources) $(module_sources) $(pipette_sources) $(protocol_sources) $(gripper_sources) - +tests ?= tests twine_auth_args := --username $(pypi_username) --password $(pypi_password) twine_repository_url ?= $(pypi_test_upload_url) @@ -123,7 +123,7 @@ deploy: wheel .PHONY: test test: - $(python) -m pytest --cov=opentrons_shared_data --cov-report xml:coverage.xml tests + $(python) -m pytest --cov=opentrons_shared_data --cov-report xml:coverage.xml $(tests) $(test_opts) .PHONY: generate-schema diff --git a/shared-data/python/opentrons_shared_data/command/__init__.py b/shared-data/python/opentrons_shared_data/command/__init__.py index 7acd7d31e2e..d92d4653c0e 100644 --- a/shared-data/python/opentrons_shared_data/command/__init__.py +++ b/shared-data/python/opentrons_shared_data/command/__init__.py @@ -4,8 +4,12 @@ import os import re +from opentrons_shared_data.errors.exceptions import InvalidProtocolData, PythonException + from ..load import load_shared_data, get_shared_data_root +SCHEMA_REF_VERSION_RE = re.compile(r"opentronsCommandSchemaV(\d+)") + def get_newest_schema_version() -> str: """Get the version string of the most modern command schema currently in shared-data.""" @@ -23,4 +27,26 @@ def get_newest_schema_version() -> str: def load_schema_string(version: str) -> str: """Get the string containing the command JSON schema for the given version string.""" path = Path("command") / "schemas" / f"{version}.json" - return json.dumps(json.loads(load_shared_data(path)), indent=2) + try: + return json.dumps(json.loads(load_shared_data(path)), indent=2) + except OSError as ose: + raise InvalidProtocolData( + message=f"Command schema version {version} is not available", + detail={ + "type": "bad-schema-version", + "schema-kind": "command", + "version": version, + }, + wrapping=[PythonException(ose)], + ) + + +def schema_version_from_ref(ref: str) -> str: + """Parse the command schema version from a command schema ref.""" + version = SCHEMA_REF_VERSION_RE.match(ref) + if not version: + raise InvalidProtocolData( + message=f"Could not parse version from command schema ${ref}", + detail={"ref": ref, "type": "bad-schema-ref", "schema-kind": "command"}, + ) + return version.group(1) diff --git a/shared-data/python/opentrons_shared_data/deck/__init__.py b/shared-data/python/opentrons_shared_data/deck/__init__.py index 7b3da99e98a..1c8d140e762 100644 --- a/shared-data/python/opentrons_shared_data/deck/__init__.py +++ b/shared-data/python/opentrons_shared_data/deck/__init__.py @@ -17,7 +17,7 @@ DeckSchemaVersion4, ) -DEFAULT_DECK_DEFINITION_VERSION: Final = 3 +DEFAULT_DECK_DEFINITION_VERSION: Final = 4 class Offset(NamedTuple): diff --git a/shared-data/python/opentrons_shared_data/deck/deck_definitions.py b/shared-data/python/opentrons_shared_data/deck/deck_definitions.py index 8fc15c7b99f..8c1278e7a78 100644 --- a/shared-data/python/opentrons_shared_data/deck/deck_definitions.py +++ b/shared-data/python/opentrons_shared_data/deck/deck_definitions.py @@ -43,7 +43,8 @@ class AreaType(Enum): class CutoutFixture(BaseModel): id: str = Field(..., description="Unique identifier for the cutout fixture.") mayMountTo: List[str] = Field( - ..., description="A list of compatible cutouts this fixture may be mounted to." + ..., + description="A list of compatible cutouts this fixture may be mounted to. These must match `id`s in `cutouts`.", ) displayName: str = Field( ..., @@ -148,10 +149,6 @@ class Cutout(BaseModel): ..., description='An optional human-readable nickname for this cutout e.g. "Cutout A1"', ) - compatibleFixtureTypes: List[str] = Field( - ..., - description="A list of cutout fixture types that can be loaded onto this cutout.", - ) class Locations(BaseModel): diff --git a/shared-data/python/opentrons_shared_data/deck/dev_types.py b/shared-data/python/opentrons_shared_data/deck/dev_types.py index 9a43f6f8fba..3ccc5357ec2 100644 --- a/shared-data/python/opentrons_shared_data/deck/dev_types.py +++ b/shared-data/python/opentrons_shared_data/deck/dev_types.py @@ -17,7 +17,7 @@ DeckSchema = NewType("DeckSchema", Dict[str, Any]) - +DeckSchemaId = Literal["opentronsDeckSchemaV3", "opentronsDeckSchemaV4"] RobotModel = Union[Literal["OT-2 Standard"], Literal["OT-3 Standard"]] @@ -109,7 +109,6 @@ class Cutout(TypedDict): id: str position: List[float] displayName: str - compatibleFixtureTypes: List[str] class CutoutFixture(TypedDict): @@ -134,6 +133,7 @@ class LocationsV4(TypedDict): addressableAreas: List[AddressableArea] calibrationPoints: List[CalibrationPoint] cutouts: List[Cutout] + legacyFixtures: List[Fixture] class NamedOffset(TypedDict): diff --git a/shared-data/python/opentrons_shared_data/errors/codes.py b/shared-data/python/opentrons_shared_data/errors/codes.py index 0b9473f8f18..1f104052cc8 100644 --- a/shared-data/python/opentrons_shared_data/errors/codes.py +++ b/shared-data/python/opentrons_shared_data/errors/codes.py @@ -75,12 +75,14 @@ class ErrorCodes(Enum): MODULE_NOT_PRESENT = _code_from_dict_entry("3015") INVALID_INSTRUMENT_DATA = _code_from_dict_entry("3016") INVALID_LIQUID_CLASS_NAME = _code_from_dict_entry("3017") + TIP_DETECTOR_NOT_FOUND = _code_from_dict_entry("3018") GENERAL_ERROR = _code_from_dict_entry("4000") ROBOT_IN_USE = _code_from_dict_entry("4001") API_REMOVED = _code_from_dict_entry("4002") NOT_SUPPORTED_ON_ROBOT_TYPE = _code_from_dict_entry("4003") COMMAND_PRECONDITION_VIOLATED = _code_from_dict_entry("4004") COMMAND_PARAMETER_LIMIT_VIOLATED = _code_from_dict_entry("4005") + INVALID_PROTOCOL_DATA = _code_from_dict_entry("4006") @classmethod @lru_cache(25) diff --git a/shared-data/python/opentrons_shared_data/errors/exceptions.py b/shared-data/python/opentrons_shared_data/errors/exceptions.py index f43ba260d3f..fa784e5fb90 100644 --- a/shared-data/python/opentrons_shared_data/errors/exceptions.py +++ b/shared-data/python/opentrons_shared_data/errors/exceptions.py @@ -806,6 +806,19 @@ def __init__( ) +class TipDetectorNotFound(RoboticsInteractionError): + """An error indicating that a tip detector has not been created for a pipette.""" + + def __init__( + self, + message: Optional[str] = None, + detail: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a TipDetectorNotFound.""" + super().__init__(ErrorCodes.TIP_DETECTOR_NOT_FOUND, message, detail, wrapping) + + class APIRemoved(GeneralError): """An error indicating that a specific API is no longer available.""" @@ -887,3 +900,16 @@ def __init__( super().__init__( ErrorCodes.NOT_SUPPORTED_ON_ROBOT_TYPE, message, detail, wrapping ) + + +class InvalidProtocolData(GeneralError): + """An error indicating that some protocol data is invalid.""" + + def __init__( + self, + message: Optional[str] = None, + detail: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an InvalidProtocolData.""" + super().__init__(ErrorCodes.INVALID_PROTOCOL_DATA, message, detail, wrapping) diff --git a/shared-data/python/opentrons_shared_data/protocol/models/__init__.py b/shared-data/python/opentrons_shared_data/protocol/models/__init__.py index aa25a97ebd4..76f8449d93d 100644 --- a/shared-data/python/opentrons_shared_data/protocol/models/__init__.py +++ b/shared-data/python/opentrons_shared_data/protocol/models/__init__.py @@ -1,7 +1,8 @@ """Protocol file reading interfaces.""" -from . import protocol_schema_v6, protocol_schema_v7 +from . import protocol_schema_v6, protocol_schema_v7, protocol_schema_v8 from .protocol_schema_v6 import ProtocolSchemaV6 from .protocol_schema_v7 import ProtocolSchemaV7 +from .protocol_schema_v8 import ProtocolSchemaV8 from .shared_models import ( Liquid, Labware, @@ -26,6 +27,8 @@ "protocol_schema_v6", "ProtocolSchemaV7", "protocol_schema_v7", + "ProtocolSchemaV8", + "protocol_schema_v8", "Liquid", "Labware", "CommandAnnotation", diff --git a/shared-data/python/opentrons_shared_data/protocol/models/protocol_schema_v8.py b/shared-data/python/opentrons_shared_data/protocol/models/protocol_schema_v8.py new file mode 100644 index 00000000000..4147afb1149 --- /dev/null +++ b/shared-data/python/opentrons_shared_data/protocol/models/protocol_schema_v8.py @@ -0,0 +1,104 @@ +from pydantic import BaseModel, Field, Extra +from typing import Any, List, Optional, Dict, Union +from typing_extensions import Literal + +from opentrons_shared_data.labware.labware_definition import LabwareDefinition + +from .shared_models import ( + Liquid, + Location, + ProfileStep, + WellLocation, + OffsetVector, + Metadata, + DesignerApplication, + Robot, +) + + +# TODO (tamar 3/15/22): split apart all the command payloads when we tackle #9583 +class Params(BaseModel): + slotName: Optional[str] + axes: Optional[List[str]] + pipetteId: Optional[str] + mount: Optional[str] + moduleId: Optional[str] + location: Optional[Union[Location, Literal["offDeck"]]] + labwareId: Optional[str] + displayName: Optional[str] + liquidId: Optional[str] + volumeByWell: Optional[Dict[str, Any]] + wellName: Optional[str] + volume: Optional[float] + flowRate: Optional[float] + wellLocation: Optional[WellLocation] + waitForResume: Optional[Literal[True]] + seconds: Optional[float] + minimumZHeight: Optional[float] + forceDirect: Optional[bool] + speed: Optional[float] + message: Optional[str] + coordinates: Optional[OffsetVector] + axis: Optional[str] + distance: Optional[float] + positionId: Optional[str] + temperature: Optional[float] + celsius: Optional[float] + blockMaxVolumeUl: Optional[float] + rpm: Optional[float] + height: Optional[float] + offset: Optional[OffsetVector] + profile: Optional[List[ProfileStep]] + radius: Optional[float] + newLocation: Optional[Union[Location, Literal["offDeck"]]] + strategy: Optional[str] + # schema v7 add-ons + homeAfter: Optional[bool] + alternateDropLocation: Optional[bool] + holdTimeSeconds: Optional[float] + maintenancePosition: Optional[str] + pipetteName: Optional[str] + model: Optional[str] + loadName: Optional[str] + namespace: Optional[str] + version: Optional[int] + pushOut: Optional[float] + + +class Command(BaseModel): + commandType: str + params: Params + key: Optional[str] + + +class CommandAnnotation(BaseModel): + commandKeys: List[str] + annotationType: str + + class Config: + extra = Extra.allow + + +class ProtocolSchemaV8(BaseModel): + otSharedSchema: Literal["#/protocol/schemas/8"] = Field( + ..., + alias="$otSharedSchema", + description="The path to a valid Opentrons shared schema relative to " + "the shared-data directory, without its extension.", + ) + schemaVersion: Literal[8] + metadata: Metadata + robot: Robot + liquidSchemaId: Literal["opentronsLiquidSchemaV1"] + liquids: Dict[str, Liquid] + labwareDefinitionSchemaId: Literal["opentronsLabwareSchemaV2"] + labwareDefinitions: Dict[str, LabwareDefinition] + commandSchemaId: Literal["opentronsCommandSchemaV8"] + commands: List[Command] + commandAnnotationSchemaId: Literal["opentronsCommandAnnotationSchemaV1"] + commandAnnotations: List[CommandAnnotation] + designerApplication: Optional[DesignerApplication] + + class Config: + # added for constructing the class with field name instead of alias + allow_population_by_field_name = True diff --git a/shared-data/python/setup.py b/shared-data/python/setup.py index 2fdcadcd0d3..ab592c2e944 100644 --- a/shared-data/python/setup.py +++ b/shared-data/python/setup.py @@ -30,6 +30,9 @@ "gripper", "robot", "errors", + "command", + "commandAnnotation", + "liquid", ] DATA_TYPES = ["definitions", "schemas"] DEST_BASE_PATH = "data" diff --git a/shared-data/python/tests/deck/test_position.py b/shared-data/python/tests/deck/test_position.py index 726c2da3158..13aefeb21b4 100644 --- a/shared-data/python/tests/deck/test_position.py +++ b/shared-data/python/tests/deck/test_position.py @@ -1,43 +1,119 @@ +from typing import Dict, Generator, List, Set, Tuple + import pytest from opentrons_shared_data.deck import load as load_deck_definition +from opentrons_shared_data.deck.dev_types import ( + AddressableArea, + Cutout, + CutoutFixture, + DeckDefinitionV3, + DeckDefinitionV4, +) from . import list_deck_def_paths +def as_tuple(list: List[float]) -> Tuple[float, float, float]: + """Convert an [x,y,z] list from the definitions to an (x,y,z) tuple, for hashability.""" + [x, y, z] = list + return (x, y, z) + + +def get_v3_slot_positions( + definition: DeckDefinitionV3, +) -> Set[Tuple[str, Tuple[float, float, float]]]: + """Return all the slot positions defined by the deck definition, as (slot_id, [x,y,z]) tuples.""" + return set( + (ordered_slot["id"], as_tuple(ordered_slot["position"])) + for ordered_slot in definition["locations"]["orderedSlots"] + ) + + +def get_v4_stacks( + definition: DeckDefinitionV4, +) -> Generator[Tuple[Cutout, CutoutFixture, AddressableArea], None, None]: + """Yield all the (cutout, cutoutFixture, addressableArea) combinations that the def allows.""" + cutout_fixtures = definition["cutoutFixtures"] + cutouts_by_id: Dict[str, Cutout] = { + cutout["id"]: cutout for cutout in definition["locations"]["cutouts"] + } + addressable_areas_by_id: Dict[str, AddressableArea] = { + addressable_area["id"]: addressable_area + for addressable_area in definition["locations"]["addressableAreas"] + } + + for cutout_fixture in cutout_fixtures: + for cutout_id, addressable_area_ids in cutout_fixture[ + "providesAddressableAreas" + ].items(): + for addressable_area_id in addressable_area_ids: + cutout = cutouts_by_id[cutout_id] + addressable_area = addressable_areas_by_id[addressable_area_id] + yield cutout, cutout_fixture, addressable_area + + +def compute_v4_position( + cutout: Cutout, addressable_area: AddressableArea +) -> List[float]: + return [ + a + b + for a, b in zip(cutout["position"], addressable_area["offsetFromCutoutFixture"]) + ] + + +def get_v4_slot_positions( + definition: DeckDefinitionV4, +) -> Set[Tuple[str, Tuple[float, float, float]]]: + """Return all the slot positions defined by the deck definition, as (addressable_area_id, [x,y,z]) tuples. + + It's important that this returns a set of (addressable_area_id, [x,y,z]) tuples instead of a + dict {addressable_area_id: [x,y,z]}. Deck schema v4 theoretically allows a single addressable + area ID to be associated with multiple positions. If that happens, we don't want to accidentally + overwrite any. + """ + stacks_with_slots = ( + (cutout, cutout_fixture, addressable_area) + for (cutout, cutout_fixture, addressable_area) in get_v4_stacks(definition) + # v3 has a "slot" for the fixed trash, but in v4, fixedTrash is its own area type. Count + # it as a "slot," since we still want to compare the positions across schema versions. + if addressable_area["areaType"] in {"slot", "fixedTrash"} + ) + + slot_positions = ( + ( + addressable_area["id"], + as_tuple(compute_v4_position(cutout, addressable_area)), + ) + for (cutout, cutout_fixture, addressable_area) in stacks_with_slots + ) + + return set(slot_positions) + + @pytest.mark.parametrize("definition_name", list_deck_def_paths(version=4)) def test_v3_and_v4_positional_equivalence(definition_name: str) -> None: deck_v3 = load_deck_definition(name=definition_name, version=3) deck_v4 = load_deck_definition(name=definition_name, version=4) - # Get a mapping of v3 slot names (ids) to the position - deck_v3_locations = { - orderedSlot["id"]: orderedSlot["position"] - for orderedSlot in deck_v3["locations"]["orderedSlots"] - } + v3_slot_positions = get_v3_slot_positions(deck_v3) + v4_slot_positions = get_v4_slot_positions(deck_v4) - # Get the base cutout locations (id is also slot name) - deck_v4_locations = { - cutout["id"]: cutout["position"] for cutout in deck_v4["locations"]["cutouts"] - } + # Exclude v4 staging area slots from this comparison, because they won't be present in v3 deck schemas. + v4_slot_positions = set( + (slot_id, slot_position) + for slot_id, slot_position in v4_slot_positions + if slot_id not in {"A4", "B4", "C4", "D4"} + ) + + # For the purposes of this comparison, the v4 addressable areas named "fixedTrash" and + # "shortFixedTrash" should be compared to slot 12 in v3. + v4_slot_positions = set( + ( + "12" if slot_id in {"fixedTrash", "shortFixedTrash"} else slot_id, + slot_position, + ) + for slot_id, slot_position in v4_slot_positions + ) - # Iterate through addressable areas that match to slot names and add their position to the cutout - # for the final slot position - for addressable_area in deck_v4["locations"]["addressableAreas"]: - addressable_area_id = addressable_area["id"] - try: - cutout_position = deck_v4_locations[addressable_area_id] - except KeyError: - continue - else: - area_offset = addressable_area["offsetFromCutoutFixture"] - x = cutout_position[0] + area_offset[0] - y = cutout_position[1] + area_offset[1] - z = cutout_position[2] + area_offset[2] - deck_v4_locations[addressable_area_id] = [x, y, z] - - assert len(deck_v3_locations.keys()) == len(deck_v4_locations.keys()) - slot_names = list(deck_v4_locations.keys()) - - for slot_name in slot_names: - assert deck_v3_locations[slot_name] == deck_v4_locations[slot_name] + assert v3_slot_positions == v4_slot_positions diff --git a/shared-data/python/tests/protocol/test_protocol_schema_v8.py b/shared-data/python/tests/protocol/test_protocol_schema_v8.py new file mode 100644 index 00000000000..0928441d328 --- /dev/null +++ b/shared-data/python/tests/protocol/test_protocol_schema_v8.py @@ -0,0 +1,20 @@ +import json +import pytest + +from opentrons_shared_data import load_shared_data +from opentrons_shared_data.protocol.models import protocol_schema_v8 + +from . import list_fixtures + + +@pytest.mark.parametrize("defpath", list_fixtures(8)) +def test_v8_types(defpath): + def_data = load_shared_data(defpath) + def_model = protocol_schema_v8.ProtocolSchemaV8.parse_raw(def_data) + def_dict_from_model = def_model.dict( + exclude_unset=True, + # 'schemaVersion' in python is '$schemaVersion' in JSON + by_alias=True, + ) + expected_def_dict = json.loads(def_data) + assert def_dict_from_model == expected_def_dict diff --git a/shared-data/tsconfig-data.json b/shared-data/tsconfig-data.json index 1a2eba36c79..216fb55edeb 100644 --- a/shared-data/tsconfig-data.json +++ b/shared-data/tsconfig-data.json @@ -10,7 +10,9 @@ "include": [ "deck/**/*.json", "labware/**/*.json", + "liquid/**/*.json", "command/**/*.json", + "commandAnnotation/**/*.json", "gripper/**/*.json", "module/**/*.json", "pipette/**/*.json", diff --git a/shared-data/tsconfig.json b/shared-data/tsconfig.json index b126e827541..1b1c50493db 100644 --- a/shared-data/tsconfig.json +++ b/shared-data/tsconfig.json @@ -6,5 +6,11 @@ "rootDir": ".", "outDir": "lib" }, - "include": ["js", "protocol"] + "include": [ + "js", + "protocol", + "command/types", + "liquid/types", + "commandAnnotation/types" + ] } diff --git a/step-generation/src/__tests__/consolidate.test.ts b/step-generation/src/__tests__/consolidate.test.ts index 3fc29e51b5f..1b1e10dd555 100644 --- a/step-generation/src/__tests__/consolidate.test.ts +++ b/step-generation/src/__tests__/consolidate.test.ts @@ -24,8 +24,7 @@ import { AIR_GAP_META, } from '../fixtures' import { DEST_WELL_BLOWOUT_DESTINATION } from '../utils' -import type { CreateCommand } from '@opentrons/shared-data' -import type { AspDispAirgapParams } from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' +import type { AspDispAirgapParams, CreateCommand } from '@opentrons/shared-data' import type { ConsolidateArgs, InvariantContext, RobotState } from '../types' const airGapHelper = makeAirGapHelper({ wellLocation: { diff --git a/step-generation/src/commandCreators/atomic/delay.ts b/step-generation/src/commandCreators/atomic/delay.ts index c2e65cce35b..b81bc132631 100644 --- a/step-generation/src/commandCreators/atomic/delay.ts +++ b/step-generation/src/commandCreators/atomic/delay.ts @@ -3,7 +3,7 @@ import type { PauseArgs, CommandCreator } from '../../types' import type { WaitForDurationCreateCommand, WaitForResumeCreateCommand, -} from '@opentrons/shared-data/protocol/types/schemaV7' +} from '@opentrons/shared-data' export const delay: CommandCreator = ( args, invariantContext, diff --git a/step-generation/src/fixtures/commandFixtures.ts b/step-generation/src/fixtures/commandFixtures.ts index cafa7942ff6..b3e881fa5e9 100644 --- a/step-generation/src/fixtures/commandFixtures.ts +++ b/step-generation/src/fixtures/commandFixtures.ts @@ -1,11 +1,11 @@ import { FIXED_TRASH_ID } from '../constants' import { tiprackWellNamesFlat } from './data' -import type { CreateCommand } from '@opentrons/shared-data' import type { AspDispAirgapParams, BlowoutParams, + CreateCommand, TouchTipParams, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/pipetting' +} from '@opentrons/shared-data' import type { CommandsAndWarnings, CommandCreatorErrorResponse } from '../types' /** Used to wrap command creators in tests, effectively casting their results diff --git a/step-generation/src/getNextRobotStateAndWarnings/temperatureUpdates.ts b/step-generation/src/getNextRobotStateAndWarnings/temperatureUpdates.ts index f4650580eb0..a15023f0299 100644 --- a/step-generation/src/getNextRobotStateAndWarnings/temperatureUpdates.ts +++ b/step-generation/src/getNextRobotStateAndWarnings/temperatureUpdates.ts @@ -9,7 +9,7 @@ import type { TemperatureModuleAwaitTemperatureParams, TemperatureParams, ModuleOnlyParams, -} from '@opentrons/shared-data/protocol/types/schemaV7/command/module' +} from '@opentrons/shared-data' import type { InvariantContext, RobotStateAndWarnings, diff --git a/tsconfig-eslint.json b/tsconfig-eslint.json index cf6c30cf854..555566fb1f5 100644 --- a/tsconfig-eslint.json +++ b/tsconfig-eslint.json @@ -22,6 +22,8 @@ "shared-data/js", "shared-data/protocol", "shared-data/pipette", + "shared-data/liquid", + "shared-data/commandAnnotations", "step-generation/src", "step-generation/typings", "protocol-designer/src",