diff --git a/hardware-testing/hardware_testing/data/csv_report.py b/hardware-testing/hardware_testing/data/csv_report.py index bc4032a95f7..ae05daf32c8 100644 --- a/hardware-testing/hardware_testing/data/csv_report.py +++ b/hardware-testing/hardware_testing/data/csv_report.py @@ -41,6 +41,8 @@ def print_csv_result(test: str, result: CSVResult) -> None: META_DATA_TEST_NAME = "test_name" META_DATA_TEST_TAG = "test_tag" META_DATA_TEST_RUN_ID = "test_run_id" +META_DATA_TEST_DEVICE_ID = "test_device_id" +META_DATA_TEST_ROBOT_ID = "test_robot_id" META_DATA_TEST_TIME_UTC = "test_time_utc" META_DATA_TEST_OPERATOR = "test_operator" META_DATA_TEST_VERSION = "test_version" @@ -260,8 +262,10 @@ def _generate_meta_data_section() -> CSVSection: CSVLine(tag=META_DATA_TEST_NAME, data=[str]), CSVLine(tag=META_DATA_TEST_TAG, data=[str]), CSVLine(tag=META_DATA_TEST_RUN_ID, data=[str]), + CSVLine(tag=META_DATA_TEST_DEVICE_ID, data=[str, CSVResult]), + CSVLine(tag=META_DATA_TEST_ROBOT_ID, data=[str]), CSVLine(tag=META_DATA_TEST_TIME_UTC, data=[str]), - CSVLine(tag=META_DATA_TEST_OPERATOR, data=[str]), + CSVLine(tag=META_DATA_TEST_OPERATOR, data=[str, CSVResult]), CSVLine(tag=META_DATA_TEST_VERSION, data=[str]), CSVLine(tag=META_DATA_TEST_FIRMWARE, data=[str]), ], @@ -291,7 +295,7 @@ def __init__( self._tag: Optional[str] = None self._file_name: Optional[str] = None _section_meta = _generate_meta_data_section() - _section_titles = [s.title for s in sections] + _section_titles = [META_DATA_TITLE] + [s.title for s in sections] _section_results = _generate_results_overview_section(_section_titles) self._sections = [_section_meta, _section_results] + sections self._cache_start_time(start_time) # must happen before storing any data @@ -330,9 +334,11 @@ def __getitem__(self, item: str) -> CSVSection: raise ValueError(f"unexpected section title: {item}") def _refresh_results_overview_values(self) -> None: - for s in self._sections[2:]: - section = self[RESULTS_OVERVIEW_TITLE] - line = section[f"RESULT_{s.title}"] + results_section = self[RESULTS_OVERVIEW_TITLE] + for s in self._sections: + if s == results_section: + continue + line = results_section[f"RESULT_{s.title}"] assert isinstance(line, CSVLine) line.store(CSVResult.PASS, print_results=False) if s.result_passed: @@ -382,9 +388,18 @@ def set_tag(self, tag: str) -> None: ) self.save_to_disk() + def set_device_id(self, device_id: str, result: CSVResult) -> None: + """Store DUT serial number.""" + self(META_DATA_TITLE, META_DATA_TEST_DEVICE_ID, [device_id, result]) + + def set_robot_id(self, robot_id: str) -> None: + """Store robot serial number.""" + self(META_DATA_TITLE, META_DATA_TEST_ROBOT_ID, [robot_id]) + def set_operator(self, operator: str) -> None: """Set operator.""" - self(META_DATA_TITLE, META_DATA_TEST_OPERATOR, [operator]) + result = CSVResult.from_bool(bool(operator)) + self(META_DATA_TITLE, META_DATA_TEST_OPERATOR, [operator, result]) def set_version(self, version: str) -> None: """Set version.""" diff --git a/hardware-testing/hardware_testing/gravimetric/report.py b/hardware-testing/hardware_testing/gravimetric/report.py index 9ccf7443efa..bac7f7b32f9 100644 --- a/hardware-testing/hardware_testing/gravimetric/report.py +++ b/hardware-testing/hardware_testing/gravimetric/report.py @@ -4,6 +4,7 @@ from typing import List, Tuple, Any from hardware_testing.data.csv_report import ( + CSVResult, CSVReport, CSVSection, CSVLine, @@ -310,6 +311,8 @@ def store_serial_numbers( liquid: str, ) -> None: """Report serial numbers.""" + report.set_robot_id(robot) + report.set_device_id(pipette, CSVResult.PASS) report("SERIAL-NUMBERS", "robot", [robot]) report("SERIAL-NUMBERS", "pipette", [pipette]) report("SERIAL-NUMBERS", "tips", [tips]) diff --git a/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py b/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py index 8250e8bb5d0..4dd8715ff72 100644 --- a/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py +++ b/hardware-testing/hardware_testing/opentrons_api/helpers_ot3.py @@ -976,6 +976,16 @@ def get_pipette_serial_ot3(pipette: Union[PipetteOT2, PipetteOT3]) -> str: return f"P{volume}{channels}V{version}{id}" +def get_robot_serial_ot3(api: OT3API) -> str: + """Get robot serial number.""" + if api.is_simulator: + return "FLXA1000000000000" + robot_id = api._backend.eeprom_data.serial_number + if not robot_id: + robot_id = "None" + return robot_id + + def clear_pipette_ul_per_mm(api: OT3API, mount: OT3Mount) -> None: """Clear pipette ul-per-mm.""" diff --git a/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/__main__.py b/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/__main__.py index daa9f5bb236..c893ddc97d8 100644 --- a/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/__main__.py +++ b/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/__main__.py @@ -4,7 +4,7 @@ from pathlib import Path from hardware_testing.data import ui, get_git_description -from hardware_testing.data.csv_report import RESULTS_OVERVIEW_TITLE +from hardware_testing.data.csv_report import RESULTS_OVERVIEW_TITLE, CSVResult from hardware_testing.opentrons_api import helpers_ot3 from hardware_testing.opentrons_api.types import OT3Mount, Axis @@ -12,6 +12,18 @@ async def _main(cfg: TestConfig) -> None: + # BUILD REPORT + test_name = Path(__file__).parent.name + ui.print_title(test_name.replace("_", " ").upper()) + report = build_report(test_name.replace("_", "-")) + version = get_git_description() + report.set_version(version) + print(f"version: {version}") + if not cfg.simulate: + report.set_operator(input("enter operator name: ")) + else: + report.set_operator("simulation") + # BUILD API api = await helpers_ot3.build_async_ot3_hardware_api( is_simulating=cfg.simulate, @@ -19,6 +31,25 @@ async def _main(cfg: TestConfig) -> None: pipette_right="p1000_single_v3.3", gripper="GRPV1120230323A01", ) + report.set_firmware(api.fw_version) + robot_id = helpers_ot3.get_robot_serial_ot3(api) + print(f"robot serial: {robot_id}") + report.set_robot_id(robot_id) + + # GRIPPER SERIAL NUMBER + gripper = api.attached_gripper + assert gripper + gripper_id = str(gripper["gripper_id"]) + report.set_tag(gripper_id) + if not api.is_simulator: + barcode = input("SCAN gripper serial number: ").strip() + else: + barcode = str(gripper_id) + barcode_pass = CSVResult.from_bool(barcode == gripper_id) + print(f"barcode: {barcode} ({barcode_pass})") + report.set_device_id(gripper_id, result=barcode_pass) + + # HOME and ATTACH await api.home_z(OT3Mount.GRIPPER) await api.home() home_pos = await api.gantry_position(OT3Mount.GRIPPER) @@ -30,22 +61,6 @@ async def _main(cfg: TestConfig) -> None: ui.get_user_ready("attach a gripper") await api.reset() - gripper = api.attached_gripper - assert gripper - gripper_id = str(gripper["gripper_id"]) - - # BUILD REPORT - test_name = Path(__file__).parent.name - ui.print_title(test_name.replace("_", " ").upper()) - report = build_report(test_name.replace("_", "-")) - report.set_tag(gripper_id) - if not cfg.simulate: - report.set_operator(input("enter operator name: ")) - else: - report.set_operator("simulation") - report.set_version(get_git_description()) - report.set_firmware(api.fw_version) - # RUN TESTS for section, test_run in cfg.tests.items(): ui.print_title(section.value) diff --git a/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/test_force.py b/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/test_force.py index b8f491ab817..7da282fc77a 100644 --- a/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/test_force.py +++ b/hardware-testing/hardware_testing/production_qc/gripper_assembly_qc_ot3/test_force.py @@ -33,7 +33,7 @@ FORCE_GAUGE_TRIAL_SAMPLE_INTERVAL = 0.25 # seconds FORCE_GAUGE_TRIAL_SAMPLE_COUNT = 20 # 20 samples = 5 seconds @ 4Hz -GAUGE_OFFSET = Point(x=2, y=42, z=75) +GAUGE_OFFSET = Point(x=2, y=-42, z=75) def _get_test_tag( @@ -151,7 +151,7 @@ async def _setup(api: OT3API) -> Union[Mark10, SimMark10]: await helpers_ot3.move_to_arched_ot3(api, mount, target_pos + Point(z=15)) if not api.is_simulator: ui.get_user_ready("please make sure the gauge in the middle of the gripper") - await api.move_to(mount, target_pos) + await helpers_ot3.jog_mount_ot3(api, OT3Mount.GRIPPER) if not api.is_simulator: ui.get_user_ready("about to grip") await api.grip(20) 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 f05e1ce031f..f48fcca9dba 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 @@ -4,7 +4,7 @@ from pathlib import Path from hardware_testing.data import ui, get_git_description -from hardware_testing.data.csv_report import RESULTS_OVERVIEW_TITLE +from hardware_testing.data.csv_report import RESULTS_OVERVIEW_TITLE, CSVResult from hardware_testing.opentrons_api import helpers_ot3 from hardware_testing.opentrons_api.types import OT3Mount, Axis @@ -12,14 +12,39 @@ async def _main(cfg: TestConfig) -> None: + # BUILD REPORT + test_name = Path(__file__).parent.name + ui.print_title(test_name.replace("_", " ").upper()) + report = build_report(test_name.replace("_", "-")) + report.set_version(get_git_description()) + if not cfg.simulate: + report.set_operator(input("enter operator name: ")) + else: + report.set_operator("simulation") + # BUILD API api = await helpers_ot3.build_async_ot3_hardware_api( is_simulating=cfg.simulate, pipette_left="p1000_96_v3.4", ) - await api.home() - home_pos = await api.gantry_position(OT3Mount.LEFT) + report.set_robot_id(helpers_ot3.get_robot_serial_ot3(api)) + + # PIPETTE SERIAL NUMBER mount = OT3Mount.LEFT + pipette = api.hardware_pipettes[mount.to_mount()] + assert pipette + pipette_id = str(pipette.pipette_id) + report.set_tag(pipette_id) + if not api.is_simulator: + barcode = input("scan pipette barcode: ").strip() + barcode_result = CSVResult(barcode == pipette_id) + report.set_device_id(pipette_id, barcode_result) + else: + report.set_device_id(pipette_id, CSVResult.PASS) + + # HOME and ATTACH + await api.home() + home_pos = await api.gantry_position(mount) attach_pos = helpers_ot3.get_slot_calibration_square_position_ot3(5) attach_pos = attach_pos._replace(z=home_pos.z) if not api.hardware_pipettes[mount.to_mount()]: @@ -30,27 +55,12 @@ async def _main(cfg: TestConfig) -> None: while not api.hardware_pipettes[mount.to_mount()]: ui.get_user_ready("attach a 96ch pipette") await api.reset() - await api.home_z(OT3Mount.LEFT) - - pipette = api.hardware_pipettes[mount.to_mount()] - assert pipette - pipette_id = str(pipette.pipette_id) + await api.home_z(mount) # FIXME: remove this once the "'L' format requires 0 <= number <= 4294967295" bug is gone await api._backend.home([Axis.P_L], api.gantry_load) await api.refresh_positions() - # BUILD REPORT - test_name = Path(__file__).parent.name - ui.print_title(test_name.replace("_", " ").upper()) - report = build_report(test_name.replace("_", "-")) - report.set_tag(pipette_id) - if not cfg.simulate: - report.set_operator(input("enter operator name: ")) - else: - report.set_operator("simulation") - report.set_version(get_git_description()) - # RUN TESTS for section, test_run in cfg.tests.items(): ui.print_title(section.value) diff --git a/hardware-testing/hardware_testing/production_qc/pipette_current_speed_qc_ot3.py b/hardware-testing/hardware_testing/production_qc/pipette_current_speed_qc_ot3.py index ab5e77d0eb1..8d050439dc6 100644 --- a/hardware-testing/hardware_testing/production_qc/pipette_current_speed_qc_ot3.py +++ b/hardware-testing/hardware_testing/production_qc/pipette_current_speed_qc_ot3.py @@ -10,6 +10,7 @@ ) from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError +from hardware_testing.data import get_git_description from hardware_testing.data.csv_report import ( CSVReport, CSVResult, @@ -42,17 +43,11 @@ MAX_SPEED = max(TEST_SPEEDS) -def _get_operator(is_simulating: bool) -> str: - if is_simulating: - return "simulating" - return input("enter OPERATOR name: ") - - def _get_test_tag(current: float, speed: float, direction: str, pos: str) -> str: return f"current-{current}-speed-{speed}-{direction}-{pos}" -def _build_csv_report(operator: str, pipette_sn: str) -> CSVReport: +def _build_csv_report() -> CSVReport: _report = CSVReport( test_name="pipette-current-speed-qc-ot3", sections=[ @@ -74,9 +69,6 @@ def _build_csv_report(operator: str, pipette_sn: str) -> CSVReport: ), ], ) - _report.set_tag(pipette_sn) - _report.set_version("unknown") - _report.set_operator(operator) return _report @@ -239,7 +231,10 @@ async def _main(is_simulating: bool) -> None: pipette_left="p1000_single_v3.4", pipette_right="p1000_multi_v3.4", ) - _operator = _get_operator(api.is_simulator) + if not api.is_simulator: + operator = input("enter OPERATOR name: ") + else: + operator = "simulation" # home and move to a safe position await _reset_gantry(api) @@ -252,7 +247,16 @@ async def _main(is_simulating: bool) -> None: ui.print_title(f"{pipette_sn} - {mount.name}") if not api.is_simulator and not ui.get_user_answer("QC this pipette"): continue - report = _build_csv_report(_operator, pipette_sn) + report = _build_csv_report() + report.set_version(get_git_description()) + report.set_operator(operator) + report.set_robot_id(helpers_ot3.get_robot_serial_ot3(api)) + report.set_tag(pipette_sn) + if not api.is_simulator: + barcode = input("scan pipette barcode: ") + else: + barcode = str(pipette_sn) + report.set_device_id(pipette_sn, CSVResult.from_bool(barcode == pipette_sn)) failing_current = await _test_plunger(api, mount, report) report( "OVERALL", diff --git a/hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/__main__.py b/hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/__main__.py index e626d617875..618764bc42d 100644 --- a/hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/__main__.py +++ b/hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/__main__.py @@ -4,7 +4,7 @@ from pathlib import Path from hardware_testing.data import ui, get_git_description -from hardware_testing.data.csv_report import RESULTS_OVERVIEW_TITLE +from hardware_testing.data.csv_report import RESULTS_OVERVIEW_TITLE, CSVResult from hardware_testing.opentrons_api import helpers_ot3 from .config import TestSection, TestConfig, build_report, TESTS @@ -15,17 +15,13 @@ async def _main(cfg: TestConfig) -> None: test_name = Path(__file__).parent.name report = build_report(test_name) ui.print_title(test_name.replace("_", " ").upper()) + report.set_version(get_git_description()) - # GET INFO + # GET OPERATOR if not cfg.simulate: - robot_id = input("enter robot serial number: ") - operator = input("enter operator name: ") + report.set_operator(input("enter operator name: ")) else: - robot_id = "ot3-simulated-A01" - operator = "simulation" - report.set_tag(robot_id) - report.set_operator(operator) - report.set_version(get_git_description()) + report.set_operator("simulation") # BUILD API api = await helpers_ot3.build_async_ot3_hardware_api( @@ -36,6 +32,16 @@ async def _main(cfg: TestConfig) -> None: gripper="GRPV102", ) + # GET ROBOT SERIAL NUMBER + robot_id = helpers_ot3.get_robot_serial_ot3(api) + report.set_tag(robot_id) + report.set_robot_id(robot_id) + if not api.is_simulator: + barcode = input("scan robot barcode: ").strip() + report.set_device_id(robot_id, CSVResult.from_bool(barcode == robot_id)) + else: + report.set_device_id(robot_id, CSVResult.PASS) + # RUN TESTS for section, test_run in cfg.tests.items(): ui.print_title(section.value) diff --git a/hardware-testing/hardware_testing/production_qc/tip_iqc_ot3.py b/hardware-testing/hardware_testing/production_qc/tip_iqc_ot3.py index d68ed43cb1b..64cbe195568 100644 --- a/hardware-testing/hardware_testing/production_qc/tip_iqc_ot3.py +++ b/hardware-testing/hardware_testing/production_qc/tip_iqc_ot3.py @@ -10,7 +10,7 @@ SimPressureFixture, ) -from hardware_testing.data.csv_report import CSVReport, CSVSection, CSVLine +from hardware_testing.data.csv_report import CSVReport, CSVSection, CSVLine, CSVResult from hardware_testing.data import ui, get_git_description from hardware_testing.opentrons_api import helpers_ot3 from hardware_testing.opentrons_api.types import Point, OT3Mount @@ -94,12 +94,6 @@ def _get_tip_offset_in_rack(tip_name: str) -> Point: async def _main(is_simulating: bool, volume: float) -> None: ui.print_title("TIP IQC") - # CONNECT - api = await helpers_ot3.build_async_ot3_hardware_api( - is_simulating=is_simulating, pipette_left="p50_single_v3.4" - ) - mount = OT3Mount.LEFT - # CREATE CSV REPORT report = CSVReport( test_name="tip-iqc-ot3", @@ -115,13 +109,31 @@ async def _main(is_simulating: bool, volume: float) -> None: for tip in WELL_NAMES ], ) - if api.is_simulator: + version = get_git_description() + report.set_version(version) + print(f"version: {version}") + if is_simulating: report.set_operator("simulation") report.set_tag("simulation") + report.set_device_id("simulation", CSVResult.PASS) else: - report.set_operator(input("enter OPERATOR: ")) - report.set_tag(input("enter TAG: ")) - report.set_version(get_git_description()) + report.set_operator(input("enter OPERATOR: ").strip()) + tag = input("enter TAG: ").strip() + report.set_tag(tag) + report.set_device_id(tag, CSVResult.from_bool(bool(tag))) + + # BUILD API + api = await helpers_ot3.build_async_ot3_hardware_api( + is_simulating=is_simulating, + pipette_left="p1000_single_v3.3", + pipette_right="p1000_single_v3.3", + gripper="GRPV1120230323A01", + ) + report.set_firmware(api.fw_version) + robot_id = helpers_ot3.get_robot_serial_ot3(api) + print(f"robot serial: {robot_id}") + report.set_robot_id(robot_id) + mount = OT3Mount.LEFT # SETUP DECK if not api.is_simulator: