Skip to content

Commit 8ffae38

Browse files
authored
chore(hardware-testing): Upgrade Robot Line and Hardware Testing to latest (#19015)
Updates and synchronizes changes for hardware assembly QC scripts across factory tags: - robot.z_stage-24.4.3 - robot.diagnostics-23.08.18-2 - robot.diagnostics-24.07.03 - robot.stress-23.08.08
1 parent 7bf339f commit 8ffae38

File tree

7 files changed

+349
-33
lines changed

7 files changed

+349
-33
lines changed

hardware-testing/hardware_testing/examples/jog_ot3.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from hardware_testing.opentrons_api import types
99
from hardware_testing.opentrons_api import helpers_ot3
10+
from opentrons_hardware.hardware_control.motion_planning import move_utils
1011

1112

1213
async def _exercise_pipette(api: OT3API, mount: types.OT3Mount) -> None:
@@ -66,6 +67,7 @@ async def _main(
6667
) -> None:
6768
api = await helpers_ot3.build_async_ot3_hardware_api(is_simulating=is_simulating)
6869
await api.home()
70+
move_utils.MINIMUM_DISPLACEMENT = 0.009
6971
while True:
7072
await helpers_ot3.jog_mount_ot3(api, mount, speed=speed)
7173
if mount == types.OT3Mount.GRIPPER:

hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/test_connectivity.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@
110110

111111
# Start UI Prompts
112112
PROMPT_UNPLUGGED = "ENSURE AUX TESTER IS NOT PLUGGED IN"
113-
PROMPT_AUX_1 = "PLUG IN AUX PORT 1 RIGHT"
114-
PROMPT_PLUGGED = "PLUG IN AUX PORT 2 LEFT"
113+
PROMPT_AUX_1 = "PLUG IN AUX PORT 1"
114+
PROMPT_PLUGGED = "PLUG IN AUX PORT 2"
115115
PROMPT_ESTOP_1 = "PRESS ESTOP 1"
116116
PROMPT_ESTOP_2 = "RELEASE ESTOP 1, PRESS ESTOP 2"
117-
PROMPT_AUX_2 = "UNPLUG AUX PORT 1 RIGHT"
117+
PROMPT_AUX_2 = "UNPLUG AUX PORT 1"
118118
PROMPT_DOOR = "UNPLUG AUX PORT 2 LEFT AND CLOSE DOOR"
119119

120120

hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/test_peripherals.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
CAM_PIC_FILE_NAME = "camera_{0}.jpg"
2929

3030
CAM_CMD_OT3 = (
31-
"v4l2-ctl --device /dev/video0 --set-fmt-video=width=640,height=480,pixelformat=MJPG "
31+
"v4l2-ctl --device /dev/video2 --set-fmt-video=width=640,height=480,pixelformat=MJPG "
3232
"--stream-mmap --stream-to={0} --stream-count=1"
3333
)
3434

@@ -244,7 +244,10 @@ def _get_user_confirmation(question: str) -> bool:
244244

245245
# CAMERA
246246
ui.print_header("CAMERA")
247-
cam_pic_path = await _take_picture(api, report, section)
247+
try:
248+
cam_pic_path = await _take_picture(api, report, section)
249+
except Exception as e:
250+
print(f"Take a picture failed with the following error: {e}")
248251
if cam_pic_path:
249252
await _run_image_check_server(api, report, section, cam_pic_path)
250253
cam_pic_path.unlink()

hardware-testing/hardware_testing/production_qc/robot_assembly_qc_ot3/test_signals.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ async def _sleep_then_activate_stop_signal() -> None:
104104
finally:
105105
print(f"deactivating {sig_name}")
106106
await release()
107+
backend.estop_acknowledge_and_clear()
107108
await asyncio.sleep(0.5)
108109

109110
async def _do_the_moving() -> None:
@@ -112,6 +113,10 @@ async def _do_the_moving() -> None:
112113
await runner.run(can_messenger=messenger)
113114
except MotionFailedError:
114115
print("caught MotionFailedError from estop")
116+
except Exception:
117+
# Ensure we have cleared the estop
118+
await backend.release_estop()
119+
backend.estop_acknowledge_and_clear()
115120

116121
await asyncio.sleep(0.25) # what is this doing?
117122
move_coro = _do_the_moving()
@@ -131,6 +136,12 @@ async def _home() -> None:
131136
print(e)
132137
ui.get_user_ready("release the E-STOP")
133138
await _home()
139+
except Exception:
140+
# Ensure we have cleared the estop
141+
backend: OT3Controller = api._backend # type: ignore[assignment]
142+
await backend.release_estop()
143+
backend.estop_acknowledge_and_clear()
144+
await _home()
134145

135146
for sig_name in SIGNAL_TEST_NAMES:
136147
ui.print_header(sig_name.upper())

hardware-testing/hardware_testing/production_qc/z_stage_qc_ot3.py

Lines changed: 122 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,55 @@
3232

3333
# Test Parameters
3434
FORCE_SPEED = 10
35-
FORCE_MARGIN = 15 # Percentage
36-
FORCE_TEST_SETTINGS = [
37-
{"CURRENT": 0.15, "F_MAX": 50},
38-
{"CURRENT": 0.2, "F_MAX": 73},
39-
{"CURRENT": 0.3, "F_MAX": 120},
40-
{"CURRENT": 0.4, "F_MAX": 160},
41-
{"CURRENT": 0.5, "F_MAX": 200},
42-
{"CURRENT": 0.6, "F_MAX": 230},
43-
{"CURRENT": 0.7, "F_MAX": 260},
44-
{"CURRENT": 1.4, "F_MAX": 480},
45-
{"CURRENT": 1.5, "F_MAX": 520},
35+
FORCE_MARGIN = 35 # Percentage
36+
FORCE_TEST_LEFT_SETTINGS = [
37+
{"CURRENT": 0.15, "F_MAX": 39},
38+
{"CURRENT": 0.2, "F_MAX": 63},
39+
{"CURRENT": 0.3, "F_MAX": 107},
40+
{"CURRENT": 0.4, "F_MAX": 148},
41+
{"CURRENT": 0.5, "F_MAX": 189},
42+
{"CURRENT": 0.6, "F_MAX": 226},
43+
{"CURRENT": 0.7, "F_MAX": 259},
44+
{"CURRENT": 1.4, "F_MAX": 498},
45+
{"CURRENT": 1.5, "F_MAX": 528},
4646
]
47+
FORCE_TEST_RIGHT_SETTINGS = [
48+
{"CURRENT": 0.15, "F_MAX": 35},
49+
{"CURRENT": 0.2, "F_MAX": 57},
50+
{"CURRENT": 0.3, "F_MAX": 98},
51+
{"CURRENT": 0.4, "F_MAX": 129},
52+
{"CURRENT": 0.5, "F_MAX": 168},
53+
{"CURRENT": 0.6, "F_MAX": 196},
54+
{"CURRENT": 0.7, "F_MAX": 228},
55+
{"CURRENT": 1.4, "F_MAX": 410},
56+
{"CURRENT": 1.5, "F_MAX": 448},
57+
]
58+
59+
ONLY_COUNT_USING_CURRENT_YIELD = True
4760
CYCLES_CURRENT = 5
4861

49-
TEST_PARAMETERS: Dict[str, float] = {
62+
TEST_LEFT_PARAMETERS: Dict[str, float] = {
63+
"SPEED": FORCE_SPEED,
64+
"FORCE_MARGIN": FORCE_MARGIN,
65+
"CYCLES": CYCLES_CURRENT,
66+
}
67+
68+
TEST_RIGHT_PARAMETERS: Dict[str, float] = {
5069
"SPEED": FORCE_SPEED,
5170
"FORCE_MARGIN": FORCE_MARGIN,
5271
"CYCLES": CYCLES_CURRENT,
5372
}
54-
for i in FORCE_TEST_SETTINGS:
55-
TEST_PARAMETERS[str(i["CURRENT"])] = i["F_MAX"]
5673

74+
for i in FORCE_TEST_LEFT_SETTINGS:
75+
TEST_LEFT_PARAMETERS[str(i["CURRENT"])] = i["F_MAX"]
76+
77+
for i in FORCE_TEST_RIGHT_SETTINGS:
78+
TEST_RIGHT_PARAMETERS[str(i["CURRENT"])] = i["F_MAX"]
5779

5880
# Global variables
5981
thread_sensor = False
6082
force_output = []
83+
valid_fail = []
6184

6285

6386
def _connect_to_mark10_fixture(simulate: bool) -> Union[Mark10, SimMark10]:
@@ -81,7 +104,7 @@ def build_test_lines() -> List[Union[CSVLine, CSVLineRepeating]]:
81104
mount_data_line: List[Union[CSVLine, CSVLineRepeating]] = [
82105
CSVLine("TEST_CURRENTS", [str, str, str, str, str])
83106
]
84-
for setting in FORCE_TEST_SETTINGS:
107+
for setting in FORCE_TEST_LEFT_SETTINGS:
85108
mount_data_line.append(
86109
CSVLine(
87110
_get_test_tag(setting["CURRENT"]),
@@ -98,8 +121,18 @@ def _build_csv_report() -> CSVReport:
98121
test_name="z-stage-test-qc-ot3",
99122
sections=[
100123
CSVSection(
101-
title="TEST_PARAMETERS",
102-
lines=[CSVLine(parameter, [int]) for parameter in TEST_PARAMETERS],
124+
title="TEST_LEFT_PARAMETERS",
125+
lines=[
126+
CSVLine(parameter, [int, CSVResult])
127+
for parameter in TEST_LEFT_PARAMETERS
128+
],
129+
),
130+
CSVSection(
131+
title="TEST_RIGHT_PARAMETERS",
132+
lines=[
133+
CSVLine(parameter, [int, CSVResult])
134+
for parameter in TEST_RIGHT_PARAMETERS
135+
],
103136
),
104137
CSVSection(
105138
title=OT3Mount.LEFT.name,
@@ -190,7 +223,6 @@ def check_force(
190223
qc_pass = True
191224
else:
192225
qc_pass = False
193-
194226
_tag = _get_test_tag(current)
195227
report(
196228
mount.name,
@@ -203,12 +235,15 @@ def check_force(
203235
CSVResult.from_bool(qc_pass),
204236
],
205237
)
206-
207238
return qc_pass
208239

209240

210241
async def _force_gauge(
211-
api: OT3API, mount: OT3Mount, report: CSVReport, simulate: bool
242+
api: OT3API,
243+
mount: OT3Mount,
244+
report: CSVReport,
245+
simulate: bool,
246+
arguments: argparse.Namespace,
212247
) -> bool:
213248
"""Apply force to the gague and log."""
214249
global thread_sensor
@@ -235,11 +270,40 @@ async def _force_gauge(
235270
["MAX", "MAX_RANGE", "AVERAGE", "AVERAGE_RANGE", "RESULT"],
236271
)
237272
# Test each current setting
238-
for test in FORCE_TEST_SETTINGS:
273+
if mount == OT3Mount.LEFT:
274+
force_test_setting = FORCE_TEST_LEFT_SETTINGS
275+
else:
276+
force_test_setting = FORCE_TEST_RIGHT_SETTINGS
277+
for test in force_test_setting:
239278
# Test each current setting several times and average the results
240279
max_results = []
241280
avg_results = []
242281
test_current = test["CURRENT"]
282+
if arguments.user_current == "None":
283+
pass
284+
else:
285+
if test_current != float(arguments.user_current):
286+
continue
287+
else:
288+
for i in range(100):
289+
await api.move_to(mount=mount, abs_position=pre_test_pos)
290+
ui.print_header(f"Cycle {i+1}: Testing Current = {test_current}")
291+
try:
292+
async with api._backend.motor_current():
293+
await api._backend.set_active_current({z_ax: test_current})
294+
await api.move_to(
295+
mount=mount,
296+
abs_position=press_pos,
297+
speed=FORCE_SPEED,
298+
expect_stalls=True,
299+
)
300+
finally:
301+
pass
302+
await api._update_position_estimation([Axis.by_mount(mount)])
303+
await api.refresh_positions()
304+
305+
await api.move_to(mount=mount, abs_position=pre_test_pos)
306+
243307
for i in range(CYCLES_CURRENT):
244308
# Move to just above force gauge
245309
await api.move_to(mount=mount, abs_position=pre_test_pos)
@@ -272,10 +336,12 @@ async def _force_gauge(
272336
avg_results.append(round(analyzed_avg, 1))
273337
else:
274338
ui.print_error(
275-
"DATA INVALID - z-stage did not contact or guage not zeroed"
339+
"DATA INVALID - z-stage did not contact or guage not zeroed \n"
340+
f"Mount {mount.name} fail !"
276341
)
342+
valid_fail.append(mount.name)
277343
qc_pass = False
278-
break
344+
return False
279345

280346
# we expect a stall has happened during pick up, so we want to
281347
# update the motor estimation
@@ -296,7 +362,12 @@ async def _force_gauge(
296362
ui.print_header(f"CURRENT: {test_current} - PASS")
297363
else:
298364
ui.print_header(f"CURRENT: {test_current} - FAIL")
299-
qc_pass = qc_pass and res
365+
# only calculate 0.2 & 0.5
366+
if ONLY_COUNT_USING_CURRENT_YIELD:
367+
if test_current == 0.2 or test_current == 0.5:
368+
qc_pass = qc_pass and res
369+
else:
370+
qc_pass = qc_pass and res
300371

301372
return qc_pass
302373

@@ -306,11 +377,15 @@ async def _run(api: OT3API, arguments: argparse.Namespace, report: CSVReport) ->
306377
qc_pass = True
307378

308379
if not arguments.skip_left:
309-
res = await _force_gauge(api, OT3Mount.LEFT, report, arguments.simulate)
380+
res = await _force_gauge(
381+
api, OT3Mount.LEFT, report, arguments.simulate, arguments
382+
)
310383
qc_pass = res and qc_pass
311384

312385
if not arguments.skip_right:
313-
res = await _force_gauge(api, OT3Mount.RIGHT, report, arguments.simulate)
386+
res = await _force_gauge(
387+
api, OT3Mount.RIGHT, report, arguments.simulate, arguments
388+
)
314389
qc_pass = res and qc_pass
315390

316391
return qc_pass
@@ -327,8 +402,12 @@ async def _main(arguments: argparse.Namespace) -> None:
327402
dut = helpers_ot3.DeviceUnderTest.OTHER
328403
helpers_ot3.set_csv_report_meta_data_ot3(api, report, dut=dut)
329404

330-
for k, v in TEST_PARAMETERS.items():
331-
report("TEST_PARAMETERS", k, [v])
405+
# NOTE: We submit an automatic "PASS" result for these parameter lists.
406+
# They do not test any logic but only add the list of parameters used to the CSV
407+
for k, v in TEST_LEFT_PARAMETERS.items():
408+
report("TEST_LEFT_PARAMETERS", k, [v, CSVResult.PASS])
409+
for k, v in TEST_RIGHT_PARAMETERS.items():
410+
report("TEST_RIGHT_PARAMETERS", k, [v, CSVResult.PASS])
332411

333412
# Attempt to home if first homing fails because of OT-3 in box Y axis issue
334413
try:
@@ -354,6 +433,14 @@ async def _main(arguments: argparse.Namespace) -> None:
354433

355434
try:
356435
qc_pass = await _run(api, arguments, report)
436+
await api.home(
437+
[
438+
Axis.X,
439+
Axis.Y,
440+
Axis.by_mount(OT3Mount.LEFT),
441+
Axis.by_mount(OT3Mount.RIGHT),
442+
]
443+
)
357444
except KeyboardInterrupt:
358445
print("Cancelled")
359446
except Exception as e:
@@ -364,6 +451,12 @@ async def _main(arguments: argparse.Namespace) -> None:
364451
ui.print_title("Test Done - PASSED")
365452
else:
366453
ui.print_title("Test Done - FAILED")
454+
if len(valid_fail) == 0:
455+
pass
456+
else:
457+
print("Data Invalid, Please Re-Test This Unit (数据验证失败, 请复测当前Z轴) !")
458+
for item in valid_fail:
459+
print(f"Mount {item} Fail")
367460
report.save_to_disk()
368461
report.print_results()
369462

@@ -373,6 +466,7 @@ async def _main(arguments: argparse.Namespace) -> None:
373466
arg_parser.add_argument("--simulate", action="store_true")
374467
arg_parser.add_argument("--skip_left", action="store_true")
375468
arg_parser.add_argument("--skip_right", action="store_true")
469+
arg_parser.add_argument("--user_current", type=str, default="None")
376470
old_stall_setting = get_adv_setting("disableStallDetection", RobotTypeEnum.FLEX)
377471
try:
378472
asyncio.run(set_adv_setting("disableStallDetection", True))

0 commit comments

Comments
 (0)