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
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
@@ -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",
51 changes: 51 additions & 0 deletions api/src/opentrons/execute.py
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
Optional,
TextIO,
Union,
Any,
)

from opentrons_shared_data.labware.labware_definition import (
@@ -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

@@ -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",
@@ -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,
@@ -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
@@ -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,
)
@@ -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.

@@ -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,
@@ -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:
@@ -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()
13 changes: 13 additions & 0 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
@@ -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,
@@ -27,6 +28,8 @@
)


from opentrons_hardware.hardware_control import MOVE_PROFILE_LOG_NAME

from opentrons_shared_data.pipette.types import (
PipetteName,
)
@@ -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(),
@@ -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,
@@ -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,
49 changes: 49 additions & 0 deletions api/src/opentrons/simulate.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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,
@@ -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
@@ -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,
@@ -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",
@@ -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,
@@ -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:
@@ -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
@@ -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,
30 changes: 30 additions & 0 deletions api/src/opentrons/util/logging_config.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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,
@@ -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": {
@@ -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},
},
}
@@ -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)

@@ -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": {
@@ -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},
},
}
Loading

Unchanged files with check annotations Beta

export const appRestart = (message: string): AppRestartAction => ({
type: APP_RESTART,
payload: {
message: message,

Check warning on line 360 in app-shell-odd/src/actions.ts

GitHub Actions / js checks

Expected property shorthand
},
meta: { shell: true },
})
export const reloadUi = (message: string): ReloadUiAction => ({
type: RELOAD_UI,
payload: {
message: message,

Check warning on line 368 in app-shell-odd/src/actions.ts

GitHub Actions / js checks

Expected property shorthand
},
meta: { shell: true },
})
export const sendLog = (message: string): SendLogAction => ({
type: SEND_LOG,
payload: {
message: message,

Check warning on line 376 in app-shell-odd/src/actions.ts

GitHub Actions / js checks

Expected property shorthand
},
meta: { shell: true },
})
export const updateBrightness = (message: string): UpdateBrightnessAction => ({
type: UPDATE_BRIGHTNESS,
payload: {
message: message,

Check warning on line 384 in app-shell-odd/src/actions.ts

GitHub Actions / js checks

Expected property shorthand
},
meta: { shell: true },
})
export function showOpenDirectoryDialog(
browserWindow: BrowserWindow,
options: Partial<BaseDialogOptions> = {}
): Promise<String[]> {

Check warning on line 27 in app-shell-odd/src/dialogs/index.ts

GitHub Actions / js checks

Don't use `String` as a type. Use string instead
let openDialogOpts: OpenDialogOptions = BASE_DIRECTORY_OPTS
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
return dialog
.showOpenDialog(browserWindow, openDialogOpts)
.then((result: OpenDialogReturnValue) => {
return result.canceled ? [] : (result.filePaths as string[])

Check warning on line 38 in app-shell-odd/src/dialogs/index.ts

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
})
}
return dialog
.showOpenDialog(browserWindow, openDialogOpts)
.then((result: OpenDialogReturnValue) => {
return result.canceled ? [] : (result.filePaths as string[])

Check warning on line 61 in app-shell-odd/src/dialogs/index.ts

GitHub Actions / js checks

This assertion is unnecessary since it does not change the type of the expression
})
}
): Promise<string> {
return fetch(input, { signal: options?.signal }).then(response => {
let downloaded = 0
const size = Number(response.headers.get('Content-Length')) || null

Check warning on line 75 in app-shell-odd/src/http.ts

GitHub Actions / js checks

Unexpected number value in conditional. An explicit zero/NaN check is required
// with node-fetch, response.body will be a Node.js readable stream
// rather than a browser-land ReadableStream
const handleError = (problem: Error): void => {
// if we error out, delete the temp dir to clean up
log.error(`Aborting fetchToFile: ${problem.name}: ${problem.message}`)
remove(destination).then(() => {

Check warning on line 102 in app-shell-odd/src/http.ts

GitHub Actions / js checks

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
reject(error)
})
}
source: string
): Promise<Response> {
return new Promise<Response>((resolve, reject) => {
createReadStream(source, reject).then(readStream =>

Check warning on line 131 in app-shell-odd/src/http.ts

GitHub Actions / js checks

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
new Promise<Response>(resolve => {
const body = new FormData()
body.append(name, readStream)