Skip to content

Commit

Permalink
Merge branch 'edge' into EXEC-643-add-WellVolumeOffset
Browse files Browse the repository at this point in the history
  • Loading branch information
pmoegenburg committed Oct 2, 2024
2 parents a671aad + 44cc303 commit 726ffc1
Show file tree
Hide file tree
Showing 122 changed files with 4,245 additions and 1,137 deletions.
76 changes: 69 additions & 7 deletions api/src/opentrons/cli/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@
)
import logging
import sys
import json

from opentrons.protocol_engine.types import RunTimeParameter, EngineStatus
from opentrons.protocol_engine.types import (
RunTimeParameter,
CSVRuntimeParamPaths,
PrimitiveRunTimeParamValuesType,
EngineStatus,
)
from opentrons.protocols.api_support.types import APIVersion
from opentrons.protocol_reader import (
ProtocolReader,
Expand Down Expand Up @@ -104,8 +110,22 @@ class _Output:
type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False),
default="WARNING",
)
@click.option(
"--rtp-values",
help="Serialized JSON of runtime parameter variable names to values.",
default="{}",
type=str,
)
@click.option(
"--rtp-files",
help="Serialized JSON of runtime parameter variable names to file paths.",
default="{}",
type=str,
)
def analyze(
files: Sequence[Path],
rtp_values: str,
rtp_files: str,
json_output: Optional[IO[bytes]],
human_json_output: Optional[IO[bytes]],
log_output: str,
Expand All @@ -125,7 +145,7 @@ def analyze(

try:
with _capture_logs(log_output, log_level):
sys.exit(run(_analyze, files, outputs, check))
sys.exit(run(_analyze, files, rtp_values, rtp_files, outputs, check))
except click.ClickException:
raise
except Exception as e:
Expand Down Expand Up @@ -194,6 +214,37 @@ def _get_input_files(files_and_dirs: Sequence[Path]) -> List[Path]:
return results


def _get_runtime_parameter_values(
serialized_rtp_values: str,
) -> PrimitiveRunTimeParamValuesType:
rtp_values = {}
try:
for variable_name, value in json.loads(serialized_rtp_values).items():
if not isinstance(value, (bool, int, float, str)):
raise click.BadParameter(
f"Runtime parameter '{value}' is not of allowed type boolean, integer, float or string",
param_hint="--rtp-values",
)
rtp_values[variable_name] = value
except json.JSONDecodeError as error:
raise click.BadParameter(
f"JSON decode error: {error}", param_hint="--rtp-values"
)
return rtp_values


def _get_runtime_parameter_paths(serialized_rtp_files: str) -> CSVRuntimeParamPaths:
try:
return {
variable_name: Path(path_string)
for variable_name, path_string in json.loads(serialized_rtp_files).items()
}
except json.JSONDecodeError as error:
raise click.BadParameter(
f"JSON decode error: {error}", param_hint="--rtp-files"
)


R = TypeVar("R")


Expand Down Expand Up @@ -238,7 +289,11 @@ def _convert_exc() -> Iterator[EnumeratedError]:
)


async def _do_analyze(protocol_source: ProtocolSource) -> RunResult:
async def _do_analyze(
protocol_source: ProtocolSource,
rtp_values: PrimitiveRunTimeParamValuesType,
rtp_paths: CSVRuntimeParamPaths,
) -> RunResult:

orchestrator = await create_simulating_orchestrator(
robot_type=protocol_source.robot_type, protocol_config=protocol_source.config
Expand All @@ -247,8 +302,8 @@ async def _do_analyze(protocol_source: ProtocolSource) -> RunResult:
await orchestrator.load(
protocol_source=protocol_source,
parse_mode=ParseMode.NORMAL,
run_time_param_values=None,
run_time_param_paths=None,
run_time_param_values=rtp_values,
run_time_param_paths=rtp_paths,
)
except Exception as error:
err_id = "analysis-setup-error"
Expand Down Expand Up @@ -285,9 +340,16 @@ async def _do_analyze(protocol_source: ProtocolSource) -> RunResult:


async def _analyze(
files_and_dirs: Sequence[Path], outputs: Sequence[_Output], check: bool
files_and_dirs: Sequence[Path],
rtp_values: str,
rtp_files: str,
outputs: Sequence[_Output],
check: bool,
) -> int:
input_files = _get_input_files(files_and_dirs)
parsed_rtp_values = _get_runtime_parameter_values(rtp_values)
rtp_paths = _get_runtime_parameter_paths(rtp_files)

try:
protocol_source = await ProtocolReader().read_saved(
files=input_files,
Expand All @@ -296,7 +358,7 @@ async def _analyze(
except ProtocolFilesInvalidError as error:
raise click.ClickException(str(error))

analysis = await _do_analyze(protocol_source)
analysis = await _do_analyze(protocol_source, parsed_rtp_values, rtp_paths)
return_code = _get_return_code(analysis)

if not outputs:
Expand Down
4 changes: 4 additions & 0 deletions api/src/opentrons/hardware_control/backends/flex_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ async def get_serial_number(self) -> Optional[str]:
def restore_system_constraints(self) -> AsyncIterator[None]:
...

@asynccontextmanager
def grab_pressure(self, channels: int, mount: OT3Mount) -> AsyncIterator[None]:
...

def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None:
...

Expand Down
9 changes: 9 additions & 0 deletions api/src/opentrons/hardware_control/backends/ot3controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
capacitive_pass,
liquid_probe,
check_overpressure,
grab_pressure,
)
from opentrons_hardware.hardware_control.rear_panel_settings import (
get_door_state,
Expand Down Expand Up @@ -369,6 +370,14 @@ async def restore_system_constraints(self) -> AsyncIterator[None]:
self._move_manager.update_constraints(old_system_constraints)
log.debug(f"Restore previous system constraints: {old_system_constraints}")

@asynccontextmanager
async def grab_pressure(
self, channels: int, mount: OT3Mount
) -> AsyncIterator[None]:
tool = axis_to_node(Axis.of_main_tool_actuator(mount))
async with grab_pressure(channels, tool, self._messenger):
yield

def update_constraints_for_calibration_with_gantry_load(
self,
gantry_load: GantryLoad,
Expand Down
6 changes: 6 additions & 0 deletions api/src/opentrons/hardware_control/ot3api.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,12 @@ async def restore_system_constrants(self) -> AsyncIterator[None]:
async with self._backend.restore_system_constraints():
yield

@contextlib.asynccontextmanager
async def grab_pressure(self, mount: OT3Mount) -> AsyncIterator[None]:
instrument = self._pipette_handler.get_pipette(mount)
async with self._backend.grab_pressure(instrument.channels, mount):
yield

def _update_door_state(self, door_state: DoorState) -> None:
mod_log.info(f"Updating the window switch status: {door_state}")
self.door_state = door_state
Expand Down
11 changes: 10 additions & 1 deletion api/src/opentrons/protocol_api/_parameter_context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Parameter context for python protocols."""
import uuid
from typing import List, Optional, Union, Dict

from opentrons.protocols.api_support.types import APIVersion
Expand Down Expand Up @@ -251,8 +252,16 @@ def initialize_csv_files(
f" but '{variable_name}' is not a CSV parameter."
)

# The parent folder in the path will be the file ID, so we can use that to resolve that here
# TODO(jbl 2024-09-30) Refactor this so file ID is passed as its own argument and not assumed from the path
# If this is running on a robot, the parent folder in the path will be the file ID
# If it is running locally, most likely the parent folder will not be a UUID, so instead we will change
# this to be an empty string
file_id = file_path.parent.name
try:
uuid.UUID(file_id, version=4)
except ValueError:
file_id = ""

file_name = file_path.name

with file_path.open("rb") as fh:
Expand Down
28 changes: 22 additions & 6 deletions api/src/opentrons/protocol_api/instrument_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,12 +540,12 @@ def mix(
),
):
self.aspirate(volume, location, rate)
while repetitions - 1 > 0:
self.dispense(volume, rate=rate, **dispense_kwargs)
self.aspirate(volume, rate=rate)
repetitions -= 1
self.dispense(volume, rate=rate)

with AutoProbeDisable(self):
while repetitions - 1 > 0:
self.dispense(volume, rate=rate, **dispense_kwargs)
self.aspirate(volume, rate=rate)
repetitions -= 1
self.dispense(volume, rate=rate)
return self

@requires_version(2, 0)
Expand Down Expand Up @@ -2192,6 +2192,22 @@ def _raise_if_configuration_not_supported_by_pipette(
# SINGLE, QUADRANT and ALL are supported by all pipettes


class AutoProbeDisable:
"""Use this class to temporarily disable automatic liquid presence detection."""

def __init__(self, instrument: InstrumentContext):
self.instrument = instrument

def __enter__(self) -> None:
if self.instrument.api_version >= APIVersion(2, 21):
self.auto_presence = self.instrument.liquid_presence_detection
self.instrument.liquid_presence_detection = False

def __exit__(self, *args: Any, **kwargs: Any) -> None:
if self.instrument.api_version >= APIVersion(2, 21):
self.instrument.liquid_presence_detection = self.auto_presence


def _raise_if_has_end_or_front_right_or_back_left(
style: NozzleLayout,
end: Optional[str],
Expand Down
Loading

0 comments on commit 726ffc1

Please sign in to comment.