From 1a8ea865cccd2d28281dfebbc8229f1e43e2c4c9 Mon Sep 17 00:00:00 2001 From: Ryan Howard Date: Wed, 13 Nov 2024 12:56:58 -0500 Subject: [PATCH] feat(hardware-testing): add the p200_96 to production qc test scripts (#16779) # Overview adds a new --pipette [200/1000] arg to the qc test script so we can test both pipettes ## Test Plan and Hands on Testing ## Changelog ## Review requests ## Risk assessment --- hardware-testing/Makefile | 1 + .../ninety_six_assembly_qc_ot3/__main__.py | 11 +++-- .../ninety_six_assembly_qc_ot3/config.py | 3 +- .../test_capacitance.py | 6 ++- .../test_droplets.py | 42 ++++++++++--------- .../test_environmental_sensor.py | 6 ++- .../ninety_six_assembly_qc_ot3/test_jaws.py | 6 ++- .../test_plunger.py | 6 ++- .../test_pressure.py | 6 ++- .../test_tip_sensor.py | 6 ++- 10 files changed, 57 insertions(+), 36 deletions(-) diff --git a/hardware-testing/Makefile b/hardware-testing/Makefile index 1249243415e..ae0571eb771 100755 --- a/hardware-testing/Makefile +++ b/hardware-testing/Makefile @@ -138,6 +138,7 @@ test-production-qc: $(python) -m hardware_testing.production_qc.robot_assembly_qc_ot3 --simulate $(python) -m hardware_testing.production_qc.gripper_assembly_qc_ot3 --simulate $(python) -m hardware_testing.production_qc.ninety_six_assembly_qc_ot3 --simulate + $(python) -m hardware_testing.production_qc.ninety_six_assembly_qc_ot3 --simulate --pipette 200 $(python) -m hardware_testing.production_qc.stress_test_qc_ot3 --simulate $(python) -m hardware_testing.production_qc.firmware_check --simulate $(python) -m hardware_testing.production_qc.belt_calibration_ot3 --simulate diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/__main__.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/__main__.py index 7495e9f5d2c..0fef18a5684 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/__main__.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/__main__.py @@ -14,11 +14,11 @@ async def _main(cfg: TestConfig) -> None: # BUILD REPORT test_name = Path(__file__).parent.name ui.print_title(test_name.replace("_", " ").upper()) - + pipette_string = "p1000_96_v3.4" if cfg.pipette == 1000 else "p200_96_v3.0" # BUILD API api = await helpers_ot3.build_async_ot3_hardware_api( is_simulating=cfg.simulate, - pipette_left="p1000_96_v3.4", + pipette_left=pipette_string, ) # CSV REPORT @@ -49,7 +49,7 @@ async def _main(cfg: TestConfig) -> None: # RUN TESTS for section, test_run in cfg.tests.items(): ui.print_title(section.value) - await test_run(api, report, section.value) + await test_run(api, report, section.value, cfg.pipette) # RELOAD PIPETTE ui.print_title("DONE") @@ -71,6 +71,7 @@ async def _main(cfg: TestConfig) -> None: parser.add_argument( f"--only-{s.value.lower()}".replace("_", "-"), action="store_true" ) + parser.add_argument("--pipette", type=int, choices=[200, 1000], default=1000) args = parser.parse_args() _t_sections = { s: f @@ -87,5 +88,7 @@ async def _main(cfg: TestConfig) -> None: for s, f in TESTS if not getattr(args, f"skip_{s.value.lower().replace('-', '_')}") } - _config = TestConfig(simulate=args.simulate, tests=_t_sections) + _config = TestConfig( + simulate=args.simulate, tests=_t_sections, pipette=args.pipette + ) asyncio.run(_main(_config)) diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/config.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/config.py index 1666eb0990f..54f19f6a660 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/config.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/config.py @@ -1,7 +1,7 @@ """Config.""" from dataclasses import dataclass import enum -from typing import Dict, Callable +from typing import Dict, Callable, Literal from hardware_testing.data.csv_report import CSVReport, CSVSection @@ -34,6 +34,7 @@ class TestConfig: simulate: bool tests: Dict[TestSection, Callable] + pipette: Literal[200, 1000] TESTS = [ diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_capacitance.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_capacitance.py index 795d78863be..2a431ecaf9c 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_capacitance.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_capacitance.py @@ -1,6 +1,6 @@ """Test Capacitance.""" from asyncio import sleep -from typing import List, Union, Tuple, Optional, cast +from typing import List, Union, Tuple, Optional, cast, Literal from opentrons_hardware.hardware_control.tool_sensors import capacitive_probe from opentrons_hardware.firmware_bindings.constants import NodeId, SensorId @@ -104,7 +104,9 @@ def _get_hover_and_probe_pos( return hover_pos + probe_offset, probe_pos + probe_offset -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" z_ax = Axis.Z_L p_ax = Axis.P_L diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_droplets.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_droplets.py index dc81f62eeb9..2f12e425576 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_droplets.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_droplets.py @@ -1,7 +1,7 @@ """Test Droplets.""" from asyncio import sleep from time import time -from typing import List, Union, Tuple, Optional, Dict +from typing import List, Union, Tuple, Optional, Dict, Literal from opentrons.hardware_control.ot3api import OT3API from opentrons.hardware_control.motion_utilities import target_position_from_relative @@ -16,14 +16,11 @@ from hardware_testing.opentrons_api import helpers_ot3 from hardware_testing.opentrons_api.types import OT3Mount, Point, Axis -TIP_VOLUME = 1000 -ASPIRATE_VOLUME = 1000 NUM_SECONDS_TO_WAIT = 30 HOVER_HEIGHT_MM = 50 DEPTH_INTO_RESERVOIR_FOR_ASPIRATE = -24 DEPTH_INTO_RESERVOIR_FOR_DISPENSE = DEPTH_INTO_RESERVOIR_FOR_ASPIRATE -TIP_RACK_LABWARE = f"opentrons_flex_96_tiprack_{TIP_VOLUME}ul" RESERVOIR_LABWARE = "nest_1_reservoir_195ml" TIP_RACK_96_SLOT = 4 @@ -86,31 +83,31 @@ def get_reservoir_nominal() -> Point: return reservoir_a1_nominal -def get_tiprack_96_nominal() -> Point: +def get_tiprack_96_nominal(pipette: Literal[200, 1000]) -> Point: """Get nominal tiprack position for 96-tip pick-up.""" tip_rack_a1_nominal = helpers_ot3.get_theoretical_a1_position( - TIP_RACK_96_SLOT, TIP_RACK_LABWARE + TIP_RACK_96_SLOT, f"opentrons_flex_96_tiprack_{pipette}ul" ) return tip_rack_a1_nominal + Point(z=TIP_RACK_96_ADAPTER_HEIGHT) -def get_tiprack_partial_nominal() -> Point: +def get_tiprack_partial_nominal(pipette: Literal[200, 1000]) -> Point: """Get nominal tiprack position for partial-tip pick-up.""" tip_rack_a1_nominal = helpers_ot3.get_theoretical_a1_position( - TIP_RACK_PARTIAL_SLOT, TIP_RACK_LABWARE + TIP_RACK_PARTIAL_SLOT, f"opentrons_flex_96_tiprack_{pipette}ul" ) return tip_rack_a1_nominal async def aspirate_and_wait( - api: OT3API, reservoir: Point, seconds: int = 30 + api: OT3API, reservoir: Point, pipette: Literal[200, 1000], seconds: int = 30 ) -> Tuple[bool, float]: """Aspirate and wait.""" await helpers_ot3.move_to_arched_ot3(api, OT3Mount.LEFT, reservoir) await api.move_to( OT3Mount.LEFT, reservoir + Point(z=DEPTH_INTO_RESERVOIR_FOR_ASPIRATE) ) - await api.aspirate(OT3Mount.LEFT, ASPIRATE_VOLUME) + await api.aspirate(OT3Mount.LEFT, pipette) await api.move_to(OT3Mount.LEFT, reservoir + Point(z=HOVER_HEIGHT_MM)) start_time = time() @@ -136,7 +133,7 @@ async def aspirate_and_wait( return result, duration_seconds -async def _drop_tip(api: OT3API, trash: Point) -> None: +async def _drop_tip(api: OT3API, trash: Point, pipette: Literal[200, 1000]) -> None: print("drop in trash") await helpers_ot3.move_to_arched_ot3(api, OT3Mount.LEFT, trash + Point(z=20)) await api.move_to(OT3Mount.LEFT, trash) @@ -144,7 +141,7 @@ async def _drop_tip(api: OT3API, trash: Point) -> None: # NOTE: a FW bug (as of v14) will sometimes not fully drop tips. # so here we ask if the operator needs to try again while not api.is_simulator and ui.get_user_answer("try dropping again"): - api.add_tip(OT3Mount.LEFT, helpers_ot3.get_default_tip_length(TIP_VOLUME)) + api.add_tip(OT3Mount.LEFT, helpers_ot3.get_default_tip_length(pipette)) await api.drop_tip(OT3Mount.LEFT) await api.home_z(OT3Mount.LEFT) @@ -164,7 +161,9 @@ async def _partial_pick_up_z_motion( await api._update_position_estimation([Axis.Z_L]) -async def _partial_pick_up(api: OT3API, position: Point, current: float) -> None: +async def _partial_pick_up( + api: OT3API, position: Point, current: float, pipette: Literal[200, 1000] +) -> None: await helpers_ot3.move_to_arched_ot3( api, OT3Mount.LEFT, @@ -172,16 +171,18 @@ async def _partial_pick_up(api: OT3API, position: Point, current: float) -> None safe_height=position.z + 10, ) await _partial_pick_up_z_motion(api, current=current, distance=13, speed=5) - api.add_tip(OT3Mount.LEFT, helpers_ot3.get_default_tip_length(TIP_VOLUME)) + api.add_tip(OT3Mount.LEFT, helpers_ot3.get_default_tip_length(pipette)) await api.prepare_for_aspirate(OT3Mount.LEFT) await api.home_z(OT3Mount.LEFT) -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" # GATHER NOMINAL POSITIONS trash_nominal = get_trash_nominal() - tip_rack_96_a1_nominal = get_tiprack_96_nominal() + tip_rack_96_a1_nominal = get_tiprack_96_nominal(pipette) # tip_rack_partial_a1_nominal = get_tiprack_partial_nominal() reservoir_a1_nominal = get_reservoir_nominal() reservoir_a1_actual: Optional[Point] = None @@ -208,7 +209,7 @@ async def _find_reservoir_pos() -> None: ) await helpers_ot3.jog_mount_ot3(api, OT3Mount.LEFT) print("picking up tips") - await api.pick_up_tip(OT3Mount.LEFT, helpers_ot3.get_default_tip_length(TIP_VOLUME)) + await api.pick_up_tip(OT3Mount.LEFT, helpers_ot3.get_default_tip_length(pipette)) await api.home_z(OT3Mount.LEFT) if not api.is_simulator: ui.get_user_ready("about to move to RESERVOIR") @@ -218,10 +219,13 @@ async def _find_reservoir_pos() -> None: await _find_reservoir_pos() assert reservoir_a1_actual result, duration = await aspirate_and_wait( - api, reservoir_a1_actual, seconds=NUM_SECONDS_TO_WAIT + api, + reservoir_a1_actual, + pipette=pipette, + seconds=NUM_SECONDS_TO_WAIT, ) report(section, "droplets-96-tips", [duration, CSVResult.from_bool(result)]) - await _drop_tip(api, trash_nominal) + await _drop_tip(api, trash_nominal, pipette) # if not api.is_simulator: # ui.get_user_ready(f"REMOVE 96 tip-rack from slot #{TIP_RACK_96_SLOT}") diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_environmental_sensor.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_environmental_sensor.py index 5892cfe276f..114b78bb815 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_environmental_sensor.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_environmental_sensor.py @@ -1,6 +1,6 @@ """Test Environmental Sensor.""" from asyncio import sleep -from typing import List, Union +from typing import List, Union, Literal from opentrons.hardware_control.ot3api import OT3API @@ -33,7 +33,9 @@ def _remove_outliers_and_average(values: List[float]) -> float: return sum(no_outliers) / len(no_outliers) -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" await api.home_z(OT3Mount.LEFT) slot_5 = helpers_ot3.get_slot_calibration_square_position_ot3(5) diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_jaws.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_jaws.py index 0bb42a81c0f..c91ed0999b6 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_jaws.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_jaws.py @@ -1,5 +1,5 @@ """Test Jaws.""" -from typing import List, Union, Tuple, Dict +from typing import List, Union, Tuple, Dict, Literal from opentrons.hardware_control.ot3api import OT3API @@ -98,7 +98,9 @@ async def jaw_precheck(api: OT3API, ax: Axis, speed: float) -> Tuple[bool, bool] return led_check, jaws_aligned -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" ax = Axis.Q settings = helpers_ot3.get_gantry_load_per_axis_motion_settings_ot3(api, ax) diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_plunger.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_plunger.py index 1f802e47599..50e79bc3946 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_plunger.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_plunger.py @@ -1,5 +1,5 @@ """Test Plunger.""" -from typing import List, Union, Tuple, Dict +from typing import List, Union, Tuple, Dict, Literal from opentrons.hardware_control.ot3api import OT3API @@ -53,7 +53,9 @@ async def _is_plunger_still_aligned_with_encoder( return p_enc, p_est, is_aligned -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" ax = Axis.P_L mount = OT3Mount.LEFT diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_pressure.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_pressure.py index a73f64ef729..d3b04619aa7 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_pressure.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_pressure.py @@ -1,6 +1,6 @@ """Test Pressure.""" from asyncio import sleep -from typing import List, Union +from typing import List, Union, Literal from opentrons_hardware.firmware_bindings.constants import SensorId @@ -94,7 +94,9 @@ def check_value(test_value: float, test_name: str) -> CSVResult: return CSVResult.FAIL -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" await api.home_z(OT3Mount.LEFT) slot_5 = helpers_ot3.get_slot_calibration_square_position_ot3(5) diff --git a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_tip_sensor.py b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_tip_sensor.py index e3e9d0571f3..8282cdab616 100644 --- a/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_tip_sensor.py +++ b/hardware-testing/hardware_testing/production_qc/ninety_six_assembly_qc_ot3/test_tip_sensor.py @@ -1,6 +1,6 @@ """Test Tip Sensor.""" import asyncio -from typing import List, Union, cast +from typing import List, Union, cast, Literal from opentrons_hardware.firmware_bindings import ArbitrationId from opentrons_hardware.firmware_bindings.constants import MessageId @@ -75,7 +75,9 @@ def _listener(message: MessageDefinition, arbitration_id: ArbitrationId) -> None return result -async def run(api: OT3API, report: CSVReport, section: str) -> None: +async def run( + api: OT3API, report: CSVReport, section: str, pipette: Literal[200, 1000] +) -> None: """Run.""" ax = Axis.Q await api.home_z(OT3Mount.LEFT)