Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hardware-testing): finalize the LLD script #15569

Merged
merged 6 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions hardware-testing/hardware_testing/liquid_sense/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
get_git_description,
get_testing_data_directory,
)
from opentrons_hardware.hardware_control.motion_planning import move_utils

from opentrons.protocol_api import InstrumentContext, ProtocolContext
from opentrons.protocol_engine.types import LabwareOffset
Expand Down Expand Up @@ -111,12 +112,11 @@ class RunArgs:
ctx: ProtocolContext
protocol_cfg: Any
test_report: CSVReport
probe_seconds_before_contact: float
aspirate: bool
dial_indicator: Optional[mitutoyo_digimatic_indicator.Mitutoyo_Digimatic_Indicator]
plunger_speed: float
trials_before_jog: int
multi_passes: int
no_multi_pass: int
test_well: str

@classmethod
Expand Down Expand Up @@ -226,7 +226,6 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs":
args.liquid,
protocol_cfg.LABWARE_ON_SCALE, # type: ignore[union-attr]
args.z_speed,
args.probe_seconds_before_contact,
)
return RunArgs(
tip_volumes=tip_volumes,
Expand All @@ -245,31 +244,31 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs":
ctx=_ctx,
protocol_cfg=protocol_cfg,
test_report=report,
probe_seconds_before_contact=args.probe_seconds_before_contact,
aspirate=args.aspirate,
dial_indicator=dial,
plunger_speed=args.plunger_speed,
trials_before_jog=args.trials_before_jog,
multi_passes=args.multi_passes,
no_multi_pass=args.no_multi_pass,
test_well=args.test_well,
)


if __name__ == "__main__":
move_utils.MINIMUM_DISPLACEMENT = 0.01

parser = argparse.ArgumentParser("Pipette Testing")
parser.add_argument("--simulate", action="store_true")
parser.add_argument("--pipette", type=int, choices=[50, 1000], required=True)
parser.add_argument("--mount", type=str, choices=["left", "right"], default="left")
parser.add_argument("--channels", type=int, choices=[1, 8, 96], default=1)
parser.add_argument("--tip", type=int, choices=[0, 50, 200, 1000], default=0)
parser.add_argument("--probe-seconds-before-contact", type=float, default=1.0)
parser.add_argument("--return-tip", action="store_true")
parser.add_argument("--trials", type=int, default=7)
parser.add_argument("--trials-before-jog", type=int, default=7)
parser.add_argument("--z-speed", type=float, default=1)
parser.add_argument("--aspirate", action="store_true")
parser.add_argument("--plunger-speed", type=float, default=-1.0)
parser.add_argument("--multi-passes", type=int, default=1)
parser.add_argument("--no-multi-pass", action="store_true")
parser.add_argument("--starting-tip", type=str, default="A1")
parser.add_argument("--test-well", type=str, default="A1")
parser.add_argument("--google-sheet-name", type=str, default="LLD-Shared-Data")
Expand All @@ -281,9 +280,6 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs":

args = parser.parse_args()

assert (
0.0 < args.probe_seconds_before_contact <= MAX_PROBE_SECONDS
), f"'--probe-seconds-before-contact' must be between 0.0-{MAX_PROBE_SECONDS}"
run_args = RunArgs.build_run_args(args)
exit_error = 0
serial_logger: Optional[subprocess.Popen] = None
Expand Down Expand Up @@ -357,7 +353,7 @@ def build_run_args(cls, args: argparse.Namespace) -> "RunArgs":
run_args.run_id,
sheet_id,
new_folder_name,
make_graph=True,
make_graph=False,
)
# Log to Google Sheet
if args.aspirate is False:
Expand Down
98 changes: 43 additions & 55 deletions hardware-testing/hardware_testing/liquid_sense/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@
pass


PROBE_MAX_TIME: Dict[int, float] = {
1: 2.75,
8: 1.75,
96: 0.85,
}


def _load_tipracks(
ctx: ProtocolContext, pipette_channels: int, protocol_cfg: Any, tip: int
) -> List[Labware]:
Expand Down Expand Up @@ -263,20 +256,10 @@ def _get_target_height() -> None:
ui.print_info(f"Picking up {tip}ul tip")
run_args.pipette.pick_up_tip(tips[0])
del tips[: run_args.pipette_channels]
# operator defines num of seconds btwn start of the probe movement
# and meniscus contact calculating ideal starting position is then
# easy bc no acceleration is involved during probe
starting_mm_above_liquid = (
run_args.probe_seconds_before_contact * run_args.z_speed
)
starting_mount_height = (
test_well.bottom(z=liquid_height).point.z + starting_mm_above_liquid
)
run_args.pipette.move_to(
test_well.bottom(z=(liquid_height + starting_mm_above_liquid))
)

run_args.pipette.move_to(test_well.top())
start_pos = hw_api.current_position_ot3(OT3Mount.LEFT)
height = _run_trial(run_args, tip, test_well, trial, starting_mount_height)
height = _run_trial(run_args, tip, test_well, trial, start_pos)
end_pos = hw_api.current_position_ot3(OT3Mount.LEFT)
run_args.pipette.blow_out()
tip_length_offset = 0.0
Expand Down Expand Up @@ -340,10 +323,7 @@ def get_plunger_travel(run_args: RunArgs) -> float:


def find_max_z_distances(
run_args: RunArgs,
well: Well,
p_speed: float,
starting_mount_height: float,
run_args: RunArgs, well: Well, p_speed: float, tip: float
) -> List[float]:
"""Returns a list of max z distances for each probe.

Expand All @@ -353,15 +333,21 @@ def find_max_z_distances(
if the distance would exceed the well depth then the number is
truncated to avoid collisions.
"""
hw_mount = OT3Mount.LEFT if run_args.pipette.mount == "left" else OT3Mount.RIGHT
hw_api = get_sync_hw_api(run_args.ctx)
lld_settings = hw_api._pipette_handler.get_pipette(hw_mount).lld_settings

z_speed = run_args.z_speed
max_z_distance = starting_mount_height - well.bottom().point.z
max_z_distance = (
well.top().point.z
- well.bottom().point.z
- lld_settings[f"t{int(tip)}"]["minHeight"]
)
plunger_travel = get_plunger_travel(run_args)
if p_speed == 0:
p_travel_time = PROBE_MAX_TIME[run_args.pipette_channels]
p_travel_time = 10.0
else:
p_travel_time = min(
plunger_travel / p_speed, PROBE_MAX_TIME[run_args.pipette_channels]
)
p_travel_time = plunger_travel / p_speed

z_travels: List[float] = []
while max_z_distance > 0:
Expand All @@ -372,7 +358,11 @@ def find_max_z_distances(


def _run_trial(
run_args: RunArgs, tip: int, well: Well, trial: int, starting_mount_height: float
run_args: RunArgs,
tip: int,
well: Well,
trial: int,
start_pos: Dict[Axis, float],
) -> float:
hw_api = get_sync_hw_api(run_args.ctx)
lqid_cfg: Dict[str, int] = LIQUID_PROBE_SETTINGS[run_args.pipette_volume][
Expand All @@ -397,34 +387,32 @@ def _run_trial(
else run_args.plunger_speed
)

start_height = starting_mount_height
start_height = start_pos[Axis.Z_L]
height = 2 * start_height
z_distances: List[float] = find_max_z_distances(
run_args, well, plunger_speed, starting_mount_height
z_distances: List[float] = find_max_z_distances(run_args, well, plunger_speed, tip)
if run_args.no_multi_pass:
z_distance = z_distances[0]
else:
z_distance = sum(z_distances)

lps = LiquidProbeSettings(
mount_speed=run_args.z_speed,
plunger_speed=plunger_speed,
plunger_impulse_time=0.2,
sensor_threshold_pascals=lqid_cfg["sensor_threshold_pascals"],
output_option=OutputOptions.sync_buffer_to_csv,
aspirate_while_sensing=run_args.aspirate,
data_files=data_files,
)
z_distances = z_distances[: run_args.multi_passes]
for z_dist in z_distances:
lps = LiquidProbeSettings(
mount_speed=run_args.z_speed,
plunger_speed=plunger_speed,
plunger_impulse_time=0.2,
sensor_threshold_pascals=lqid_cfg["sensor_threshold_pascals"],
output_option=OutputOptions.sync_buffer_to_csv,
aspirate_while_sensing=run_args.aspirate,
data_files=data_files,
)

hw_mount = OT3Mount.LEFT if run_args.pipette.mount == "left" else OT3Mount.RIGHT
run_args.recorder.set_sample_tag(f"trial-{trial}-{tip}ul")
# TODO add in stuff for secondary probe
try:
height = hw_api.liquid_probe(hw_mount, z_dist, lps, probe_target)
except PipetteLiquidNotFoundError as lnf:
ui.print_info(f"Liquid not found current position {lnf.detail}")
start_height -= z_dist
else:
break
run_args.recorder.clear_sample_tag()
hw_mount = OT3Mount.LEFT if run_args.pipette.mount == "left" else OT3Mount.RIGHT
run_args.recorder.set_sample_tag(f"trial-{trial}-{tip}ul")
# TODO add in stuff for secondary probe
try:
height = hw_api.liquid_probe(hw_mount, z_distance, lps, probe_target)
except PipetteLiquidNotFoundError as lnf:
ui.print_info(f"Liquid not found current position {lnf.detail}")
run_args.recorder.clear_sample_tag()

ui.print_info(f"Trial {trial} complete")
return height
3 changes: 0 additions & 3 deletions hardware-testing/hardware_testing/liquid_sense/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def build_config_section() -> CSVSection:
CSVLine("liquid", [str]),
CSVLine("labware_type", [str]),
CSVLine("speed", [str]),
CSVLine("probe_seconds_before_contact", [str]),
],
)

Expand Down Expand Up @@ -151,7 +150,6 @@ def store_config(
liquid: str,
labware_type: str,
speed: str,
probe_seconds_before_contact: str,
) -> None:
"""Report config."""
report("CONFIG", "protocol_name", [protocol_name])
Expand All @@ -166,7 +164,6 @@ def store_config(
report("CONFIG", "liquid", [liquid])
report("CONFIG", "labware_type", [labware_type])
report("CONFIG", "speed", [speed])
report("CONFIG", "probe_seconds_before_contact", [probe_seconds_before_contact])


def store_baseline_trial(
Expand Down
Loading