Skip to content

Commit 1c8b9cb

Browse files
authored
chore(hardware-testing): Flex Stacker DVT Diagnostic Script Suite (#17583)
1 parent 7f28026 commit 1c8b9cb

18 files changed

+1394
-1
lines changed

api/src/opentrons/drivers/flex_stacker/driver.py

+1
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ async def create(
307307
loop=loop,
308308
error_keyword=FS_ERROR_KEYWORD,
309309
async_error_ack=FS_ASYNC_ERROR_ACK,
310+
reset_buffer_before_write=True,
310311
error_codes=StackerErrorCodes,
311312
)
312313
return cls(connection)

api/src/opentrons/drivers/flex_stacker/simulator.py

+19
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ async def set_serial_number(self, sn: str) -> bool:
8383
self._sn = sn
8484
return True
8585

86+
@ensure_yield
8687
async def enable_motors(self, axis: List[StackerAxis]) -> bool:
8788
"""Enables the axis motor if present, disables it otherwise."""
8889
return True
@@ -92,26 +93,31 @@ async def stop_motors(self) -> bool:
9293
"""Stop all motor movement."""
9394
return True
9495

96+
@ensure_yield
9597
async def set_run_current(self, axis: StackerAxis, current: float) -> bool:
9698
"""Set axis peak run current in amps."""
9799

98100
return True
99101

102+
@ensure_yield
100103
async def set_ihold_current(self, axis: StackerAxis, current: float) -> bool:
101104
"""Set axis hold current in amps."""
102105
return True
103106

107+
@ensure_yield
104108
async def set_stallguard_threshold(
105109
self, axis: StackerAxis, enable: bool, threshold: int
106110
) -> bool:
107111
"""Enables and sets the stallguard threshold for the given axis motor."""
108112
self._stallgard_threshold[axis] = StallGuardParams(axis, enable, threshold)
109113
return True
110114

115+
@ensure_yield
111116
async def enable_tof_sensor(self, sensor: TOFSensor, enable: bool) -> bool:
112117
"""Enable or disable the TOF sensor."""
113118
return True
114119

120+
@ensure_yield
115121
async def manage_tof_measurement(
116122
self,
117123
sensor: TOFSensor,
@@ -129,6 +135,7 @@ async def manage_tof_measurement(
129135
total_bytes=3840 if start else 0,
130136
)
131137

138+
@ensure_yield
132139
async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:
133140
"""Get the full histogram measurement from the TOF sensor."""
134141
return TOFMeasurementResult(
@@ -137,28 +144,33 @@ async def get_tof_histogram(self, sensor: TOFSensor) -> TOFMeasurementResult:
137144
bins={c: [b for b in range(NUMBER_OF_BINS)] for c in range(10)},
138145
)
139146

147+
@ensure_yield
140148
async def set_motor_driver_register(
141149
self, axis: StackerAxis, reg: int, value: int
142150
) -> bool:
143151
"""Set the register of the given motor axis driver to the given value."""
144152
self._motor_registers[axis].update({reg: value})
145153
return True
146154

155+
@ensure_yield
147156
async def get_motor_driver_register(self, axis: StackerAxis, reg: int) -> int:
148157
"""Gets the register value of the given motor axis driver."""
149158
return self._motor_registers[axis].get(reg, 0)
150159

160+
@ensure_yield
151161
async def set_tof_driver_register(
152162
self, sensor: TOFSensor, reg: int, value: int
153163
) -> bool:
154164
"""Set the register of the given tof sensor driver to the given value."""
155165
self._tof_registers[sensor].update({reg: value})
156166
return True
157167

168+
@ensure_yield
158169
async def get_tof_driver_register(self, sensor: TOFSensor, reg: int) -> int:
159170
"""Gets the register value of the given tof sensor driver."""
160171
return self._tof_registers[sensor].get(reg, 0)
161172

173+
@ensure_yield
162174
async def get_tof_sensor_status(self, sensor: TOFSensor) -> TOFSensorStatus:
163175
"""Get the status of the tof sensor."""
164176
return TOFSensorStatus(
@@ -168,10 +180,12 @@ async def get_tof_sensor_status(self, sensor: TOFSensor) -> TOFSensorStatus:
168180
ok=True,
169181
)
170182

183+
@ensure_yield
171184
async def get_motion_params(self, axis: StackerAxis) -> MoveParams:
172185
"""Get the motion parameters used by the given axis motor."""
173186
return MoveParams(axis, 1, 1, 1)
174187

188+
@ensure_yield
175189
async def get_stallguard_threshold(self, axis: StackerAxis) -> StallGuardParams:
176190
"""Get the stallguard parameters by the given axis motor."""
177191
return self._stallgard_threshold[axis]
@@ -189,13 +203,15 @@ async def get_limit_switches_status(self) -> LimitSwitchStatus:
189203
"""Get limit switch statuses for all axes."""
190204
return self._limit_switch_status
191205

206+
@ensure_yield
192207
async def get_platform_sensor(self, direction: Direction) -> bool:
193208
"""Get platform sensor status.
194209
195210
:return: True if platform is present, False otherwise
196211
"""
197212
return self._platform_sensor_status.get(direction)
198213

214+
@ensure_yield
199215
async def get_platform_status(self) -> PlatformStatus:
200216
"""Get platform status."""
201217
return self._platform_sensor_status
@@ -222,10 +238,12 @@ async def move_to_limit_switch(
222238
"""Move until limit switch is triggered."""
223239
return MoveResult.NO_ERROR
224240

241+
@ensure_yield
225242
async def home_axis(self, axis: StackerAxis, direction: Direction) -> MoveResult:
226243
"""Home axis."""
227244
return MoveResult.NO_ERROR
228245

246+
@ensure_yield
229247
async def set_led(
230248
self,
231249
power: float,
@@ -238,6 +256,7 @@ async def set_led(
238256
"""Set LED Status bar color and pattern."""
239257
return True
240258

259+
@ensure_yield
241260
async def enter_programming_mode(self) -> None:
242261
"""Reboot into programming mode"""
243262
pass

hardware-testing/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ test-integration: test-production-qc test-examples test-scripts test-gravimetric
170170

171171
.PHONY: test-stacker
172172
test-stacker:
173-
$(python) -m hardware_testing.modules.flex_stacker_evt_qc --simulate
173+
$(python) -m hardware_testing.modules.flex_stacker_dvt_qc --simulate
174174

175175
.PHONY: lint
176176
lint:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""FLEX Stacker QC scripts for DVT."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""FLEX Stacker DVT QC."""
2+
from os import environ
3+
4+
# NOTE: this is required to get WIFI test to work
5+
if "OT_SYSTEM_VERSION" not in environ:
6+
environ["OT_SYSTEM_VERSION"] = "0.0.0"
7+
8+
import argparse
9+
import asyncio
10+
from pathlib import Path
11+
from typing import Tuple
12+
13+
from hardware_testing.data import ui
14+
from hardware_testing.data.csv_report import CSVReport
15+
16+
from .config import TestSection, TestConfig, build_report, TESTS
17+
from .driver import FlexStackerInterface, PlatformState
18+
19+
20+
async def build_stacker_report(
21+
is_simulating: bool,
22+
) -> Tuple[CSVReport, FlexStackerInterface]:
23+
"""Report setup for FLEX Stacker qc script."""
24+
test_name = Path(__file__).parent.name.replace("_", "-")
25+
ui.print_title(test_name.upper())
26+
27+
stacker = (
28+
await FlexStackerInterface.build_simulator()
29+
if is_simulating
30+
else await FlexStackerInterface.build()
31+
)
32+
33+
report = build_report(test_name)
34+
report.set_operator(
35+
"simulating" if is_simulating else input("enter OPERATOR name: ")
36+
)
37+
return report, stacker
38+
39+
40+
async def _main(cfg: TestConfig) -> None:
41+
# BUILD REPORT
42+
report, stacker = await build_stacker_report(cfg.simulate)
43+
44+
if not cfg.simulate:
45+
# Perform initial checks before starting tests
46+
# 1. estop should not be pressed
47+
# 2. platform should be removed
48+
if await stacker.get_estop():
49+
ui.print_error("ESTOP is pressed, please release it before starting")
50+
ui.get_user_ready("Release ESTOP")
51+
if stacker.get_estop():
52+
ui.print_error("ESTOP is still pressed, cannot start tests")
53+
return
54+
55+
platform_state = await stacker.get_platform_state()
56+
if platform_state is not PlatformState.UNKNOWN:
57+
ui.print_error("Platform must be removed from the carrier before starting")
58+
ui.get_user_ready("Remove platform from {platform_state.value}")
59+
if await stacker.get_platform_state() is not PlatformState.UNKNOWN:
60+
ui.print_error("Platform is still detected, cannot start tests")
61+
return
62+
63+
device_info = await stacker._driver.get_device_info()
64+
report.set_tag(device_info.sn if device_info.sn else "UNKNOWN")
65+
66+
# RUN TESTS
67+
try:
68+
for section, test_run in cfg.tests.items():
69+
ui.print_title(section.value)
70+
await test_run(stacker, report, section.value)
71+
except Exception as e:
72+
ui.print_error(f"An error occurred: {e}")
73+
74+
# SAVE REPORT
75+
ui.print_title("DONE")
76+
report.save_to_disk()
77+
report.print_results()
78+
79+
80+
if __name__ == "__main__":
81+
parser = argparse.ArgumentParser()
82+
parser.add_argument("--simulate", action="store_true")
83+
# add each test-section as a skippable argument (eg: --skip-connectivity)
84+
for s in TestSection:
85+
parser.add_argument(f"--skip-{s.value.lower()}", action="store_true")
86+
parser.add_argument(f"--only-{s.value.lower()}", action="store_true")
87+
args = parser.parse_args()
88+
_t_sections = {s: f for s, f in TESTS if getattr(args, f"only_{s.value.lower()}")}
89+
if _t_sections:
90+
assert (
91+
len(list(_t_sections.keys())) < 2
92+
), 'use "--only" for just one test, not multiple tests'
93+
else:
94+
_t_sections = {
95+
s: f for s, f in TESTS if not getattr(args, f"skip_{s.value.lower()}")
96+
}
97+
_config = TestConfig(simulate=args.simulate, tests=_t_sections)
98+
asyncio.run(_main(_config))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Config."""
2+
from dataclasses import dataclass
3+
import enum
4+
from typing import Dict, Callable
5+
6+
from hardware_testing.data.csv_report import CSVReport, CSVSection
7+
8+
from . import (
9+
test_connectivity,
10+
test_z_axis_basic,
11+
test_x_axis_basic,
12+
test_l_axis_basic,
13+
test_z_axis_current_speed,
14+
test_x_axis_current_speed,
15+
test_door_switch,
16+
test_estop,
17+
test_ui_leds,
18+
test_uv_lockout_switch,
19+
)
20+
21+
22+
class TestSection(enum.Enum):
23+
"""Test Section."""
24+
25+
CONNECTIVITY = "CONNECTIVITY"
26+
DOOR_SWITCH = "DOOR_SWITCH"
27+
ESTOP = "ESTOP"
28+
Z_AXIS_BASIC = "Z_AXIS_BASIC"
29+
L_AXIS_BASIC = "L_AXIS_BASIC"
30+
X_AXIS_BASIC = "X_AXIS_BASIC"
31+
UI_LEDS = "UI_LEDS"
32+
UV_LOCKOUT_SWITCH = "UV_LOCKOUT_SWITCH"
33+
Z_AXIS_CURRENT_SPEED = "Z_AXIS_CURRENT_SPEED"
34+
X_AXIS_CURRENT_SPEED = "X_AXIS_CURRENT_SPEED"
35+
36+
37+
@dataclass
38+
class TestConfig:
39+
"""Test Config."""
40+
41+
simulate: bool
42+
tests: Dict[TestSection, Callable]
43+
44+
45+
TESTS = [
46+
(
47+
TestSection.CONNECTIVITY,
48+
test_connectivity.run,
49+
),
50+
(
51+
TestSection.Z_AXIS_BASIC,
52+
test_z_axis_basic.run,
53+
),
54+
(
55+
TestSection.X_AXIS_BASIC,
56+
test_x_axis_basic.run,
57+
),
58+
(
59+
TestSection.L_AXIS_BASIC,
60+
test_l_axis_basic.run,
61+
),
62+
(
63+
TestSection.ESTOP,
64+
test_estop.run,
65+
),
66+
(
67+
TestSection.DOOR_SWITCH,
68+
test_door_switch.run,
69+
),
70+
(
71+
TestSection.UI_LEDS,
72+
test_ui_leds.run,
73+
),
74+
(
75+
TestSection.UV_LOCKOUT_SWITCH,
76+
test_uv_lockout_switch.run,
77+
),
78+
(
79+
TestSection.Z_AXIS_CURRENT_SPEED,
80+
test_z_axis_current_speed.run,
81+
),
82+
(
83+
TestSection.X_AXIS_CURRENT_SPEED,
84+
test_x_axis_current_speed.run,
85+
),
86+
]
87+
88+
89+
def build_report(test_name: str) -> CSVReport:
90+
"""Build report."""
91+
return CSVReport(
92+
test_name=test_name,
93+
sections=[
94+
CSVSection(
95+
title=TestSection.CONNECTIVITY.value,
96+
lines=test_connectivity.build_csv_lines(),
97+
),
98+
CSVSection(
99+
title=TestSection.ESTOP.value,
100+
lines=test_estop.build_csv_lines(),
101+
),
102+
CSVSection(
103+
title=TestSection.DOOR_SWITCH.value,
104+
lines=test_door_switch.build_csv_lines(),
105+
),
106+
CSVSection(
107+
title=TestSection.Z_AXIS_BASIC.value,
108+
lines=test_z_axis_basic.build_csv_lines(),
109+
),
110+
CSVSection(
111+
title=TestSection.X_AXIS_BASIC.value,
112+
lines=test_x_axis_basic.build_csv_lines(),
113+
),
114+
CSVSection(
115+
title=TestSection.L_AXIS_BASIC.value,
116+
lines=test_l_axis_basic.build_csv_lines(),
117+
),
118+
CSVSection(
119+
title=TestSection.UI_LEDS.value,
120+
lines=test_ui_leds.build_csv_lines(),
121+
),
122+
CSVSection(
123+
title=TestSection.UV_LOCKOUT_SWITCH.value,
124+
lines=test_uv_lockout_switch.build_csv_lines(),
125+
),
126+
CSVSection(
127+
title=TestSection.Z_AXIS_CURRENT_SPEED.value,
128+
lines=test_z_axis_current_speed.build_csv_lines(),
129+
),
130+
CSVSection(
131+
title=TestSection.X_AXIS_CURRENT_SPEED.value,
132+
lines=test_x_axis_current_speed.build_csv_lines(),
133+
),
134+
],
135+
)

0 commit comments

Comments
 (0)