Skip to content

Commit e9362b9

Browse files
authored
chore(hardware-testing): Validate belt-calibration using production script or Protocol run (#15552)
…n simply test the current settings <!-- Thanks for taking the time to open a pull request! Please make sure you've read the "Opening Pull Requests" section of our Contributing Guide: https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests To ensure your code is reviewed quickly and thoroughly, please fill out the sections below to the best of your ability! --> # Overview This PR makes it easier to confirm belt calibration is working in two ways: 1. add a `--skip-calibration` argument to the belt-calibration production script, so we can simply evaluate how accurate the current belt-calibration is without overwriting it 2. add a new Protocol file which pauses over wells in opposite corners of the machine, so a person can use their eye to determine if belt-calibration is working <!-- Use this section to describe your pull-request at a high level. If the PR addresses any open issues, please tag the issues here. --> # Test Plan <!-- Use this section to describe the steps that you took to test your Pull Request. If you did not perform any testing provide justification why. OT-3 Developers: You should default to testing on actual physical hardware. Once again, if you did not perform testing against hardware, justify why. Note: It can be helpful to write a test plan before doing development Example Test Plan (HTTP API Change) - Verified that new optional argument `dance-party` causes the robot to flash its lights, move the pipettes, then home. - Verified that when you omit the `dance-party` option the robot homes normally - Added protocol that uses `dance-party` argument to G-Code Testing Suite - Ran protocol that did not use `dance-party` argument and everything was successful - Added unit tests to validate that changes to pydantic model are correct --> # Changelog <!-- List out the changes to the code in this PR. Please try your best to categorize your changes and describe what has changed and why. Example changelog: - Fixed app crash when trying to calibrate an illegal pipette - Added state to API to track pipette usage - Updated API docs to mention only two pipettes are supported IMPORTANT: MAKE SURE ANY BREAKING CHANGES ARE PROPERLY COMMUNICATED --> # Review requests <!-- Describe any requests for your reviewers here. --> # Risk assessment <!-- Carefully go over your pull request and look at the other parts of the codebase it may affect. Look for the possibility, even if you think it's small, that your change may affect some other part of the system - for instance, changing return tip behavior in protocol may also change the behavior of labware calibration. Identify the other parts of the system your codebase may affect, so that in addition to your own review and testing, other people who may not have the system internalized as much as you can focus their attention and testing there. -->
1 parent b053d6e commit e9362b9

File tree

3 files changed

+67
-36
lines changed

3 files changed

+67
-36
lines changed

hardware-testing/hardware_testing/production_qc/belt_calibration_ot3.py

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ async def _calibrate_belts(
124124

125125

126126
async def run_belt_calibration(
127-
api: OT3API, mount: types.OT3Mount, test: bool
127+
api: OT3API, mount: types.OT3Mount, calibrate: bool, test: bool
128128
) -> Tuple[
129129
Optional[_TestBeltCalibrationData],
130-
AttitudeMatrix,
131-
Dict[str, Any],
130+
Optional[AttitudeMatrix],
131+
Optional[Dict[str, Any]],
132132
Optional[_TestBeltCalibrationData],
133133
]:
134134
"""Run belt calibration."""
@@ -149,14 +149,16 @@ async def run_belt_calibration(
149149
if not api.is_simulator:
150150
ui.get_user_ready("ATTACH a probe to pipette")
151151

152-
without_data = None
153-
with_data = None
154-
152+
without_data: Optional[_TestBeltCalibrationData] = None
153+
with_data: Optional[_TestBeltCalibrationData] = None
154+
attitude: Optional[AttitudeMatrix] = None
155+
details: Optional[Dict[str, Any]] = None
155156
try:
156157
# calibrate belts
157-
ui.print_header("CALIBRATE BELTS")
158-
await api.reset_instrument_offset(mount)
159-
attitude, details = await _calibrate_belts(api, mount)
158+
if calibrate:
159+
ui.print_header("CALIBRATE BELTS")
160+
await api.reset_instrument_offset(mount)
161+
attitude, details = await _calibrate_belts(api, mount)
160162

161163
# test after
162164
if test:
@@ -236,7 +238,7 @@ def _create_csv_report() -> CSVReport:
236238
)
237239

238240

239-
async def run(is_simulating: bool, skip_test: bool) -> None:
241+
async def run(is_simulating: bool, skip_calibration: bool, skip_test: bool) -> None:
240242
"""Run."""
241243
ui.print_header("BELT CALIBRATION")
242244

@@ -252,9 +254,13 @@ async def run(is_simulating: bool, skip_test: bool) -> None:
252254
helpers_ot3.set_csv_report_meta_data_ot3(api, report)
253255

254256
# RUN TEST
257+
before: Optional[_TestBeltCalibrationData] = None
258+
after: Optional[_TestBeltCalibrationData] = None
259+
attitude: Optional[AttitudeMatrix] = None
260+
details: Optional[Dict[str, Any]] = None
255261
try:
256262
before, attitude, details, after = await run_belt_calibration(
257-
api, types.OT3Mount.LEFT, test=not skip_test
263+
api, types.OT3Mount.LEFT, calibrate=not skip_calibration, test=not skip_test
258264
)
259265
except (
260266
EarlyCapacitiveSenseTrigger,
@@ -263,7 +269,6 @@ async def run(is_simulating: bool, skip_test: bool) -> None:
263269
MisalignedGantryError,
264270
) as e:
265271
ui.print_error(str(e))
266-
return
267272

268273
if api.is_simulator:
269274
nom_front_left = helpers_ot3.get_slot_calibration_square_position_ot3(
@@ -283,32 +288,34 @@ async def run(is_simulating: bool, skip_test: bool) -> None:
283288
details = sim_cal_data.build_details()
284289

285290
# STORE ATTITUDE
286-
report("ATTITUDE", "attitude-x", attitude[0])
287-
report("ATTITUDE", "attitude-y", attitude[1])
288-
report("ATTITUDE", "attitude-z", attitude[2])
291+
if attitude:
292+
report("ATTITUDE", "attitude-x", attitude[0])
293+
report("ATTITUDE", "attitude-y", attitude[1])
294+
report("ATTITUDE", "attitude-z", attitude[2])
289295

290296
# STORE DETAILS
291-
report(
292-
"BELT-CALIBRATION-POSITIONS",
293-
"slot-front-left",
294-
list(details["slots"]["front_left"]),
295-
)
296-
report(
297-
"BELT-CALIBRATION-POSITIONS",
298-
"slot-front-right",
299-
list(details["slots"]["front_right"]),
300-
)
301-
report(
302-
"BELT-CALIBRATION-POSITIONS",
303-
"slot-rear-left",
304-
list(details["slots"]["rear_left"]),
305-
)
306-
for align_shift in AlignmentShift:
297+
if details:
298+
report(
299+
"BELT-CALIBRATION-POSITIONS",
300+
"slot-front-left",
301+
list(details["slots"]["front_left"]),
302+
)
303+
report(
304+
"BELT-CALIBRATION-POSITIONS",
305+
"slot-front-right",
306+
list(details["slots"]["front_right"]),
307+
)
307308
report(
308-
"BELT-CALIBRATION-SHIFTS",
309-
align_shift.value,
310-
[details[align_shift.value]["shift"]],
309+
"BELT-CALIBRATION-POSITIONS",
310+
"slot-rear-left",
311+
list(details["slots"]["rear_left"]),
311312
)
313+
for align_shift in AlignmentShift:
314+
report(
315+
"BELT-CALIBRATION-SHIFTS",
316+
align_shift.value,
317+
[details[align_shift.value]["shift"]],
318+
)
312319

313320
if before and after:
314321
# STORE PIPETTE-OFFSET CALIBRATIONS
@@ -344,6 +351,7 @@ async def run(is_simulating: bool, skip_test: bool) -> None:
344351
if __name__ == "__main__":
345352
parser = argparse.ArgumentParser()
346353
parser.add_argument("--simulate", action="store_true")
354+
parser.add_argument("--skip-calibration", action="store_true")
347355
parser.add_argument("--skip-test", action="store_true")
348356
args = parser.parse_args()
349-
asyncio.run(run(args.simulate, args.skip_test))
357+
asyncio.run(run(args.simulate, args.skip_calibration, args.skip_test))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Flex IQ: Belt Calibration."""
2+
from opentrons.protocol_api import ProtocolContext
3+
4+
5+
metadata = {"protocolName": "Flex IQ: Belt Calibration"}
6+
requirements = {"robotType": "Flex", "apiLevel": "2.18"}
7+
8+
9+
def run(ctx: ProtocolContext) -> None:
10+
"""Run."""
11+
plate_a1 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A1")
12+
plate_d3 = ctx.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "D3")
13+
14+
tips = ctx.load_labware("opentrons_flex_96_tiprack_50uL", "C2")
15+
pipette = ctx.load_instrument("flex_1channel_1000", "right", tip_racks=[tips])
16+
17+
pipette.pick_up_tip()
18+
pipette.move_to(plate_a1["A1"].top())
19+
ctx.pause()
20+
pipette.move_to(plate_d3["H12"].top())
21+
ctx.pause()
22+
pipette.return_tip()

hardware-testing/hardware_testing/scripts/belt_calibration_ot3.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
if __name__ == "__main__":
99
parser = argparse.ArgumentParser()
1010
parser.add_argument("--simulate", action="store_true")
11+
parser.add_argument("--skip-calibration", action="store_true")
1112
parser.add_argument("--skip-test", action="store_true")
1213
args = parser.parse_args()
13-
asyncio.run(run(args.simulate, args.skip_test))
14+
asyncio.run(run(args.simulate, args.skip_calibration, args.skip_test))

0 commit comments

Comments
 (0)