Skip to content

abr protocols for liquid-classes and LLD in v8.4.0 #17997

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

Draft
wants to merge 36 commits into
base: chore_release-8.4.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a7164df
adds files
andySigler Apr 7, 2025
1f75029
passes linting
andySigler Apr 7, 2025
d9b3167
update de-static labware def to match physical dimensions
andySigler Apr 7, 2025
52614ea
mark SW issues with FIXME
andySigler Apr 7, 2025
73112cf
wip
andySigler Apr 8, 2025
7692221
add --rp argument to execute and simulate
andySigler Apr 8, 2025
b9b7dc0
cleanup args to iq/oq protocol
andySigler Apr 8, 2025
df7ad1a
works during simulate
andySigler Apr 8, 2025
b4b08ba
makefile tests working
andySigler Apr 8, 2025
eb8476d
remove redundant transfer liquid method
andySigler Apr 8, 2025
89c1b2e
renames are to --parameters
andySigler Apr 8, 2025
73363b5
96ch tip-racks and modes hooked up
andySigler Apr 8, 2025
ee8b694
formatting
andySigler Apr 8, 2025
c3bd6e5
update makefile with new directory name
andySigler Apr 8, 2025
d7cba05
add note that not LLD'ing during simulating is expected
andySigler Apr 8, 2025
dd7befd
wip
andySigler Apr 9, 2025
16f4ecc
fixes simulation mistakes w/ 96ch
andySigler Apr 9, 2025
0474115
adds all liquid types to make tests
andySigler Apr 9, 2025
f7f960b
wip
andySigler Apr 10, 2025
ee8eed7
adds button to static-bar FW
andySigler Apr 15, 2025
6996476
renames to de_static_fixture; adjust fixture def to avoid collisions
andySigler Apr 15, 2025
a3e3011
simulates
andySigler Apr 16, 2025
c0f6ee3
adds waste chute
andySigler Apr 16, 2025
1f7868b
waste chute does not take an arg
andySigler Apr 16, 2025
91dde4c
go to correct destination wells
andySigler Apr 17, 2025
0c3f556
contact dispense for all classes
andySigler Apr 17, 2025
52b4639
wip
andySigler Apr 17, 2025
8d8c59b
adds waste-chute and swaps tip-racks out when they are empty
andySigler Apr 18, 2025
bc74858
first pass at new profiler log
ryanthecoder Mar 14, 2025
935bec4
simply logs a bit
andySigler Mar 19, 2025
de5b0a5
also simply ot3api logs
andySigler Mar 19, 2025
64989a5
raise error if multiple plates are needed
andySigler Apr 22, 2025
c5df3be
fix unpack mistake
andySigler Apr 22, 2025
9e4f52f
fix bug where pipette minimum volume was in default mode when calcula…
andySigler Apr 22, 2025
46848b4
comments fixme
andySigler Apr 24, 2025
32146f1
use new method name in low-volume protocol
andySigler Apr 24, 2025
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
9 changes: 9 additions & 0 deletions api/src/opentrons/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ class ConfigElement(NamedTuple):
" absolute path, it will be used directly. If it is a "
"relative path it will be relative to log_dir",
),
ConfigElement(
"move_profile_log_file",
"Move Profile File",
Path("logs") / "profile.log",
ConfigElementType.FILE,
"The location of the file to save move profile logs to. If this is an"
" absolute path, it will be used directly. If it is a "
"relative path it will be relative to log_dir",
),
ConfigElement(
"serial_log_file",
"Serial Log File",
Expand Down
51 changes: 51 additions & 0 deletions api/src/opentrons/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Optional,
TextIO,
Union,
Any,
)

from opentrons_shared_data.labware.labware_definition import (
Expand Down Expand Up @@ -67,6 +68,9 @@
create_protocol_engine,
)
from opentrons.protocol_engine.types import PostRunHardwareState
from opentrons.protocol_engine.types.run_time_parameters import (
PrimitiveRunTimeParamValuesType,
)

from opentrons.protocol_reader import ProtocolSource

Expand Down Expand Up @@ -230,6 +234,11 @@ def get_arguments(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"this option and should be configured in the config file. If "
"'none', do not show logs",
)

parser.add_argument(
"--parameters", nargs="+", help="my_str=help my_bool=true my_int=1 my_float=1.0"
)

parser.add_argument(
"-L",
"--custom-labware-path",
Expand Down Expand Up @@ -293,6 +302,7 @@ def get_arguments(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
def execute(
protocol_file: Union[BinaryIO, TextIO],
protocol_name: str,
run_time_param_values: Optional[PrimitiveRunTimeParamValuesType] = None,
propagate_logs: bool = False,
log_level: str = "warning",
emit_runlog: Optional[_EmitRunlogCallable] = None,
Expand All @@ -317,6 +327,7 @@ def execute(
:param protocol_name: The name of the protocol file. This is required
internally, but it may not be a thing we can get
from the ``protocol_file`` argument.
:param run_time_param_values: Runtime parameter values
:param propagate_logs: Whether this function should allow logs from the
Opentrons stack to propagate up to the root handler.
This can be useful if you're integrating this
Expand Down Expand Up @@ -430,6 +441,7 @@ def execute(
protocol_file.seek(0)
_run_file_pe(
protocol=protocol,
run_time_param_values=run_time_param_values,
hardware_api=_get_global_hardware_controller(_get_robot_type()),
emit_runlog=emit_runlog,
)
Expand All @@ -455,6 +467,42 @@ def _print_runlog(command: command_types.CommandMessage) -> None:
return _print_runlog


def _convert_runtime_param_value(val: str) -> Any:
if val.lower() == "true":
return True
elif val.lower() == "false":
return False
if "." in val: # NOTE: arg must explicitly define a float vs int with "."
try:
return float(val)
except ValueError:
pass
else:
try:
return int(val)
except ValueError:
pass
return val


def _parse_parameters(
name_value_list: List[str],
) -> PrimitiveRunTimeParamValuesType:
if not name_value_list:
return {}
result: Dict[str, Any] = {}
for name_value in name_value_list:
if "=" in name_value:
name, value = name_value.split("=", 1)
if name and value:
result[name] = _convert_runtime_param_value(value)
continue
raise argparse.ArgumentTypeError(
f"Invalid argument format: '{name_value}' (expected name=value)"
)
return result


def main() -> int:
"""Handler for command line invocation to run a protocol.

Expand Down Expand Up @@ -492,6 +540,7 @@ def main() -> int:
execute(
protocol_file=args.protocol,
protocol_name=args.protocol.name,
run_time_param_values=_parse_parameters(args.parameters),
custom_labware_paths=args.custom_labware_path,
custom_data_paths=(args.custom_data_path + args.custom_data_file),
log_level=log_level,
Expand Down Expand Up @@ -623,6 +672,7 @@ def _run_file_non_pe(

def _run_file_pe(
protocol: Protocol,
run_time_param_values: Optional[PrimitiveRunTimeParamValuesType],
hardware_api: ThreadManagedHardware,
emit_runlog: Optional[_EmitRunlogCallable],
) -> None:
Expand Down Expand Up @@ -668,6 +718,7 @@ async def run(protocol_source: ProtocolSource) -> None:
result = await orchestrator.run(
deck_configuration=entrypoint_util.get_deck_configuration(),
protocol_source=protocol_source,
run_time_param_values=run_time_param_values,
)
finally:
unsubscribe()
Expand Down
13 changes: 13 additions & 0 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from functools import partial, lru_cache, wraps
from dataclasses import replace
import logging
import time
from collections import OrderedDict
from typing import (
AsyncIterator,
Expand All @@ -27,6 +28,8 @@
)


from opentrons_hardware.hardware_control import MOVE_PROFILE_LOG_NAME

from opentrons_shared_data.pipette.types import (
PipetteName,
)
Expand Down Expand Up @@ -148,6 +151,7 @@
from .backends.errors import SubsystemUpdating

mod_log = logging.getLogger(__name__)
PROFILE_LOG = logging.getLogger(MOVE_PROFILE_LOG_NAME)

AXES_IN_HOMING_ORDER: Tuple[Axis, Axis, Axis, Axis, Axis, Axis, Axis, Axis, Axis] = (
*Axis.ot3_mount_axes(),
Expand Down Expand Up @@ -1462,6 +1466,8 @@ async def _move(
expect_stalls: bool = False,
) -> None:
"""Worker function to apply robot motion."""
start = time.time()
PROFILE_LOG.info(f"OT3API._move\tstart\t{start}")
machine_pos = machine_from_deck(
deck_pos=target_position,
attitude=self._robot_calibration.deck_calibration.attitude,
Expand Down Expand Up @@ -1494,11 +1500,18 @@ async def _move(
except Exception:
self._log.exception("Move failed")
self._current_position.clear()
end = time.time()
PROFILE_LOG.info(f"OT3API._move\tfailed\t{end}")
PROFILE_LOG.info(f"OT3API._move\tduration\t{end - start}")
raise
else:
await self._cache_current_position()
await self._cache_encoder_position()

end = time.time()
PROFILE_LOG.info(f"OT3API._move\tend\t{end}")
PROFILE_LOG.info(f"OT3API._move\tduration\t{end - start}")

async def _set_plunger_current_and_home(
self,
axis: Axis,
Expand Down
49 changes: 49 additions & 0 deletions api/src/opentrons/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
from opentrons.protocol_engine.state.config import Config
from opentrons.protocol_engine.types import DeckType, EngineStatus, PostRunHardwareState
from opentrons.protocol_reader.protocol_source import ProtocolSource
from opentrons.protocol_engine.types.run_time_parameters import (
PrimitiveRunTimeParamValuesType,
)
from opentrons.protocol_runner.protocol_runner import create_protocol_runner, LiveRunner
from opentrons.protocol_runner import RunOrchestrator
from opentrons.protocols.duration import DurationEstimator
Expand Down Expand Up @@ -435,6 +438,7 @@ def bundle_from_sim(
def simulate(
protocol_file: Union[BinaryIO, TextIO],
file_name: Optional[str] = None,
run_time_param_values: Optional[PrimitiveRunTimeParamValuesType] = None,
custom_labware_paths: Optional[List[str]] = None,
custom_data_paths: Optional[List[str]] = None,
propagate_logs: bool = False,
Expand Down Expand Up @@ -483,6 +487,7 @@ def simulate(

:param protocol_file: The protocol file to simulate.
:param file_name: The name of the file
:param run_time_param_values: Runtime parameter values
:param custom_labware_paths: A list of directories to search for custom labware.
Loads valid labware from these paths and makes them available
to the protocol context. If this is ``None`` (the default), and
Expand Down Expand Up @@ -581,6 +586,7 @@ def simulate(
protocol_file.seek(0)
return _run_file_pe(
protocol=protocol,
run_time_param_values=run_time_param_values,
robot_type=protocol.robot_type,
hardware_api=hardware_simulator,
stack_logger=stack_logger,
Expand Down Expand Up @@ -659,6 +665,10 @@ def get_arguments(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
'Log levels below warning can be chatty. If "none", do not show logs',
)

parser.add_argument(
"--parameters", nargs="+", help="my_str=help my_bool=true my_int=1 my_float=1.0"
)

parser.add_argument(
"-L",
"--custom-labware-path",
Expand Down Expand Up @@ -911,6 +921,7 @@ def _run_file_non_pe(

def _run_file_pe(
protocol: Protocol,
run_time_param_values: Optional[PrimitiveRunTimeParamValuesType],
robot_type: RobotType,
hardware_api: ThreadManagedHardware,
stack_logger: logging.Logger,
Expand Down Expand Up @@ -965,6 +976,7 @@ async def run(protocol_source: ProtocolSource) -> _SimulateResult:
# the Protocol Engine config specifies use_simulated_deck_config=True.
deck_configuration=[],
protocol_source=protocol_source,
run_time_param_values=run_time_param_values,
)

if result.state_summary.status != EngineStatus.SUCCEEDED:
Expand Down Expand Up @@ -1004,6 +1016,42 @@ def _clear_live_protocol_engine_contexts() -> None:
_LIVE_PROTOCOL_ENGINE_CONTEXTS.close()


def _convert_runtime_param_value(val: str) -> Any:
if val.lower() == "true":
return True
elif val.lower() == "false":
return False
if "." in val: # NOTE: arg must explicitly define a float vs int with "."
try:
return float(val)
except ValueError:
pass
else:
try:
return int(val)
except ValueError:
pass
return val


def _parse_parameters(
name_value_list: List[str],
) -> PrimitiveRunTimeParamValuesType:
if not name_value_list:
return {}
result: Dict[str, Any] = {}
for name_value in name_value_list:
if "=" in name_value:
name, value = name_value.split("=", 1)
if name and value:
result[name] = _convert_runtime_param_value(value)
continue
raise argparse.ArgumentTypeError(
f"Invalid argument format: '{name_value}' (expected name=value)"
)
return result


# Note - this script is also set up as a setuptools entrypoint and thus does
# an absolute minimum of work since setuptools does something odd generating
# the scripts
Expand All @@ -1024,6 +1072,7 @@ def main() -> int:
runlog, maybe_bundle = simulate(
protocol_file=args.protocol,
file_name=args.protocol.name,
run_time_param_values=_parse_parameters(args.parameters),
custom_labware_paths=args.custom_labware_path,
custom_data_paths=(args.custom_data_path + args.custom_data_file),
duration_estimator=duration_estimator,
Expand Down
30 changes: 30 additions & 0 deletions api/src/opentrons/util/logging_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

if ARCHITECTURE is SystemArchitecture.YOCTO:
from opentrons_hardware.sensors import SENSOR_LOG_NAME
from opentrons_hardware.hardware_control import MOVE_PROFILE_LOG_NAME
else:
# we don't use the sensor log on ot2 or host
SENSOR_LOG_NAME = "unused"
MOVE_PROFILE_LOG_NAME = "unused"


# We want this big enough to smooth over any temporary stalls in journald's ability
Expand Down Expand Up @@ -39,6 +41,7 @@ def _config_for_host(level_value: int) -> None:
serial_log_filename = CONFIG["serial_log_file"]
api_log_filename = CONFIG["api_log_file"]
sensor_log_filename = CONFIG["sensor_log_file"]
move_profile_log_filename = CONFIG["move_profile_log_file"]
config = {
"version": 1,
"disable_existing_loggers": False,
Expand Down Expand Up @@ -79,6 +82,14 @@ def _config_for_host(level_value: int) -> None:
"level": logging.DEBUG,
"backupCount": 5,
},
"move_profile": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "basic",
"filename": move_profile_log_filename,
"maxBytes": 1000000,
"level": logging.DEBUG,
"backupCount": 5,
},
},
"loggers": {
"opentrons": {
Expand Down Expand Up @@ -109,6 +120,11 @@ def _config_for_host(level_value: int) -> None:
"level": logging.DEBUG,
"propagate": False,
},
MOVE_PROFILE_LOG_NAME: {
"handlers": ["move_profile"],
"level": logging.DEBUG,
"propagate": False,
},
"__main__": {"handlers": ["api"], "level": level_value},
},
}
Expand All @@ -123,6 +139,7 @@ def _config_for_robot(level_value: int) -> None:
from systemd.journal import JournalHandler # type: ignore

sensor_log_filename = CONFIG["sensor_log_file"]
move_profile_log_filename = CONFIG["move_profile_log_file"]

sensor_log_queue = Queue[logging.LogRecord](maxsize=_LOG_QUEUE_SIZE)

Expand Down Expand Up @@ -167,6 +184,14 @@ def _config_for_robot(level_value: int) -> None:
"formatter": "message_only",
"queue": sensor_log_queue,
},
"move_profile": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "message_only",
"filename": move_profile_log_filename,
"maxBytes": 1000000,
"level": logging.DEBUG,
"backupCount": 3,
},
},
"loggers": {
"opentrons.drivers.asyncio.communication.serial_connection": {
Expand Down Expand Up @@ -197,6 +222,11 @@ def _config_for_robot(level_value: int) -> None:
"level": logging.DEBUG,
"propagate": False,
},
MOVE_PROFILE_LOG_NAME: {
"handlers": ["move_profile"],
"level": logging.DEBUG,
"propagate": False,
},
"__main__": {"handlers": ["api"], "level": level_value},
},
}
Expand Down
Loading
Loading