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

refactor(api, robot-server): redirect router level calls to PE and protocol_runners via run orchestrator #15257

Merged
merged 41 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
667147d
WIP deafult orchestrator
TamarZanzouri May 22, 2024
47e5f5d
fixed get_default_orchstrator
TamarZanzouri May 23, 2024
e88982c
remove runner and engine props from EngineStore WIP
TamarZanzouri May 23, 2024
34b4391
play and finish and progress replacing
TamarZanzouri May 23, 2024
7bd9f5f
run_data_manager via orchestrator
TamarZanzouri May 23, 2024
7b29b9a
run_data_manger remove direct access to orchestrator
TamarZanzouri May 23, 2024
29813d8
run_data_manager.py via orchestrator
TamarZanzouri May 28, 2024
c8f1f4b
test run_data_manager
TamarZanzouri May 28, 2024
9d9979f
run_controller.py
TamarZanzouri May 28, 2024
13a0cb8
light_control_task.py and dependencies.py
TamarZanzouri May 28, 2024
43d82e8
labware_router.py
TamarZanzouri May 29, 2024
31e0051
add command and wait
TamarZanzouri May 29, 2024
9531aa7
all implemented engine store and above
TamarZanzouri May 30, 2024
8e06362
await add and wait for command
TamarZanzouri May 30, 2024
967f78e
fixed failing tests in the commands router
TamarZanzouri May 30, 2024
2e00e7d
run_orchestrator prop
TamarZanzouri May 30, 2024
4b27221
WIP removed props for engine and runner
TamarZanzouri May 30, 2024
bc8237a
orchestrator implementations + engine_store redirect
TamarZanzouri May 31, 2024
bcdfcea
same
TamarZanzouri May 31, 2024
a079763
get default orchestrator WIP
TamarZanzouri May 31, 2024
98bc42e
get default orchestrator
TamarZanzouri May 31, 2024
9b9f11f
fixed tests and removed props from engine_store
TamarZanzouri May 31, 2024
2ffc0a8
implementation for orchestrator -> engine_store
TamarZanzouri Jun 3, 2024
aa156bb
no linting errors!!!
TamarZanzouri Jun 3, 2024
c598bfa
engine_store all tests passing
TamarZanzouri Jun 3, 2024
9e8775a
orchestrator linting
TamarZanzouri Jun 4, 2024
3fb5cba
fixing tests
TamarZanzouri Jun 4, 2024
a5ddce7
added tests
TamarZanzouri Jun 5, 2024
815c7d9
added tests WIP
TamarZanzouri Jun 5, 2024
83e484b
added tests
TamarZanzouri Jun 5, 2024
1602623
added tests and fixed bugs
TamarZanzouri Jun 6, 2024
c18bf98
fixed bug with maintenance runs
TamarZanzouri Jun 6, 2024
04c0e1d
bug fixes and linting
TamarZanzouri Jun 7, 2024
20202f1
fixed but with protocol live runner
TamarZanzouri Jun 7, 2024
8dcc13f
added tests and specific error
TamarZanzouri Jun 10, 2024
2ef261c
Update robot-server/robot_server/runs/engine_store.py
TamarZanzouri Jun 10, 2024
42dbedd
fixed docsrtings
TamarZanzouri Jun 10, 2024
ee5e5a3
docstrings
TamarZanzouri Jun 10, 2024
836c3f6
prepare on init. reverted tests commented out. docstrings
TamarZanzouri Jun 10, 2024
77bf864
get_robot_type
TamarZanzouri Jun 11, 2024
a1121d1
robot type import
TamarZanzouri Jun 11, 2024
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
1 change: 1 addition & 0 deletions api/src/opentrons/protocol_runner/protocol_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ def __init__(

self._hardware_api.should_taskify_movement_execution(taskify=False)

# TODO(tz, 6-10-2024): explore moving this method into the constructor.
def prepare(self) -> None:
"""Set the task queue to wait until all commands are executed."""
self._task_queue.set_run_func(func=self._protocol_engine.wait_until_complete)
Expand Down
251 changes: 238 additions & 13 deletions api/src/opentrons/protocol_runner/run_orchestrator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
"""Engine/Runner provider."""
from __future__ import annotations
from typing import Optional, Union
from typing import Optional, Union, List, Dict

from . import protocol_runner, AnyRunner
from anyio import move_on_after

from opentrons_shared_data.labware.dev_types import LabwareUri
from opentrons_shared_data.labware.labware_definition import LabwareDefinition
from opentrons_shared_data.errors import GeneralError

from . import protocol_runner, RunResult, JsonRunner, PythonAndLegacyRunner
from ..hardware_control import HardwareControlAPI
from ..protocol_engine import ProtocolEngine
from ..protocol_engine.types import PostRunHardwareState
from ..protocol_reader import JsonProtocolConfig, PythonProtocolConfig
from ..hardware_control.modules import AbstractModule as HardwareModuleAPI
from ..protocol_engine import (
ProtocolEngine,
CommandCreate,
Command,
StateSummary,
CommandPointer,
CommandSlice,
)
from ..protocol_engine.types import (
PostRunHardwareState,
EngineStatus,
LabwareOffsetCreate,
LabwareOffset,
DeckConfigurationType,
RunTimeParameter,
RunTimeParamValuesType,
)
from ..protocol_reader import JsonProtocolConfig, PythonProtocolConfig, ProtocolSource
from ..protocols.parse import PythonParseMode


class NoProtocolRunAvailable(RuntimeError):
"""An error raised if there is no protocol run available."""


class RunNotFound(GeneralError):
"""An error raised if there is no run associated."""


class RunOrchestrator:
SyntaxColoring marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -20,6 +51,7 @@ class RunOrchestrator:
]
_setup_runner: protocol_runner.LiveRunner
_fixit_runner: protocol_runner.LiveRunner
_protocol_live_runner: protocol_runner.LiveRunner
_hardware_api: HardwareControlAPI
_protocol_engine: ProtocolEngine

Expand All @@ -29,6 +61,7 @@ def __init__(
hardware_api: HardwareControlAPI,
fixit_runner: protocol_runner.LiveRunner,
setup_runner: protocol_runner.LiveRunner,
protocol_live_runner: protocol_runner.LiveRunner,
json_or_python_protocol_runner: Optional[
Union[protocol_runner.PythonAndLegacyRunner, protocol_runner.JsonRunner]
] = None,
Expand All @@ -41,25 +74,27 @@ def __init__(
hardware_api: Hardware control API instance.
fixit_runner: LiveRunner for fixit commands.
setup_runner: LiveRunner for setup commands.
protocol_live_runner: LiveRunner for protocol commands.
json_or_python_protocol_runner: JsonRunner/PythonAndLegacyRunner for protocol commands.
run_id: run id if any, associated to the runner/engine.
"""
self.run_id = run_id
self._run_id = run_id
self._protocol_engine = protocol_engine
self._hardware_api = hardware_api
self._protocol_runner = json_or_python_protocol_runner
self._setup_runner = setup_runner
self._fixit_runner = fixit_runner
self._protocol_live_runner = protocol_live_runner

@property
def engine(self) -> ProtocolEngine:
"""Get the "current" persisted ProtocolEngine."""
return self._protocol_engine
self._fixit_runner.prepare()
self._setup_runner.prepare()

@property
def runner(self) -> AnyRunner:
"""Get the "current" persisted ProtocolRunner."""
return self._protocol_runner or self._setup_runner
def run_id(self) -> str:
"""Get the "current" persisted run_id."""
if not self._run_id:
raise RunNotFound()
return self._run_id

@classmethod
def build_orchestrator(
Expand All @@ -82,6 +117,10 @@ def build_orchestrator(
protocol_engine=protocol_engine,
hardware_api=hardware_api,
)
protocol_live_runner = protocol_runner.LiveRunner(
protocol_engine=protocol_engine,
hardware_api=hardware_api,
)
json_or_python_runner = None
if protocol_config:
json_or_python_runner = protocol_runner.create_protocol_runner(
Expand All @@ -98,4 +137,190 @@ def build_orchestrator(
fixit_runner=fixit_runner,
hardware_api=hardware_api,
protocol_engine=protocol_engine,
protocol_live_runner=protocol_live_runner,
)

def play(self, deck_configuration: Optional[DeckConfigurationType] = None) -> None:
"""Start or resume the run."""
self._protocol_engine.play(deck_configuration=deck_configuration)

async def run(self, deck_configuration: DeckConfigurationType) -> RunResult:
TamarZanzouri marked this conversation as resolved.
Show resolved Hide resolved
"""Start the run."""
if self._protocol_runner:
return await self._protocol_runner.run(
deck_configuration=deck_configuration
)
elif self._protocol_live_runner:
return await self._protocol_live_runner.run(
deck_configuration=deck_configuration
)
else:
return await self._setup_runner.run(deck_configuration=deck_configuration)

def pause(self) -> None:
"""Pause the run."""
self._protocol_engine.request_pause()

async def stop(self) -> None:
"""Stop the run."""
if self.run_has_started():
await self._protocol_engine.request_stop()
else:
await self.finish(
drop_tips_after_run=False,
set_run_status=False,
post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE,
)

def resume_from_recovery(self) -> None:
"""Resume the run from recovery."""
self._protocol_engine.resume_from_recovery()

async def finish(
self,
error: Optional[Exception] = None,
drop_tips_after_run: bool = True,
set_run_status: bool = True,
post_run_hardware_state: PostRunHardwareState = PostRunHardwareState.HOME_AND_STAY_ENGAGED,
) -> None:
"""Finish the run."""
await self._protocol_engine.finish(
error=error,
drop_tips_after_run=drop_tips_after_run,
set_run_status=set_run_status,
post_run_hardware_state=post_run_hardware_state,
)

def get_state_summary(self) -> StateSummary:
"""Get protocol run data."""
return self._protocol_engine.state_view.get_summary()

def get_loaded_labware_definitions(self) -> List[LabwareDefinition]:
"""Get loaded labware definitions."""
return self._protocol_engine.state_view.labware.get_loaded_labware_definitions()

def get_run_time_parameters(self) -> List[RunTimeParameter]:
"""Parameter definitions defined by protocol, if any. Will always be empty before execution."""
return (
[]
if self._protocol_runner is None
else self._protocol_runner.run_time_parameters
)

def get_current_command(self) -> Optional[CommandPointer]:
"""Get the current running command."""
return self._protocol_engine.state_view.commands.get_current()

def get_command_slice(
self,
cursor: Optional[int],
length: int,
) -> CommandSlice:
"""Get a slice of run commands.

Args:
cursor: Requested index of first command in the returned slice.
length: Length of slice to return.
"""
return self._protocol_engine.state_view.commands.get_slice(
cursor=cursor, length=length
)

def get_command_recovery_target(self) -> Optional[CommandPointer]:
"""Get the current error recovery target."""
return self._protocol_engine.state_view.commands.get_recovery_target()

def get_command(self, command_id: str) -> Command:
"""Get a run's command by ID."""
return self._protocol_engine.state_view.commands.get(command_id=command_id)

def get_all_commands(self) -> List[Command]:
"""Get all run commands."""
return self._protocol_engine.state_view.commands.get_all()

def get_run_status(self) -> EngineStatus:
"""Get the current execution status of the engine."""
return self._protocol_engine.state_view.commands.get_status()

def get_is_run_terminal(self) -> bool:
"""Get whether engine is in a terminal state."""
return self._protocol_engine.state_view.commands.get_is_terminal()

def run_has_started(self) -> bool:
"""Get whether the run has started."""
return self._protocol_engine.state_view.commands.has_been_played()

def run_has_stopped(self) -> bool:
"""Get whether the run has stopped."""
return self._protocol_engine.state_view.commands.get_is_stopped()

def add_labware_offset(self, request: LabwareOffsetCreate) -> LabwareOffset:
"""Add a new labware offset to state."""
return self._protocol_engine.add_labware_offset(request)

def add_labware_definition(self, definition: LabwareDefinition) -> LabwareUri:
"""Add a new labware definition to state."""
return self._protocol_engine.add_labware_definition(definition)

async def add_command_and_wait_for_interval(
self,
command: CommandCreate,
wait_until_complete: bool = False,
timeout: Optional[int] = None,
failed_command_id: Optional[str] = None,
) -> Command:
"""Add a new command to execute and wait for it to complete if needed."""
added_command = self._protocol_engine.add_command(
request=command, failed_command_id=failed_command_id
)
if wait_until_complete:
timeout_sec = None if timeout is None else timeout / 1000.0
with move_on_after(timeout_sec):
await self._protocol_engine.wait_for_command(added_command.id)
return added_command

def estop(self) -> None:
"""Handle an E-stop event from the hardware API."""
return self._protocol_engine.estop()

async def use_attached_modules(
self, modules_by_id: Dict[str, HardwareModuleAPI]
) -> None:
"""Load attached modules directly into state, without locations."""
await self._protocol_engine.use_attached_modules(modules_by_id=modules_by_id)

def get_protocol_runner(self) -> Optional[Union[JsonRunner, PythonAndLegacyRunner]]:
"""Get run's protocol runner if any, if not return None."""
return self._protocol_runner

async def load_json(
self,
protocol_source: ProtocolSource,
) -> None:
"""Load a json protocol."""
assert self._protocol_runner is not None
assert isinstance(self._protocol_runner, JsonRunner)
await self._protocol_runner.load(protocol_source=protocol_source)

async def load_python(
self,
protocol_source: ProtocolSource,
python_parse_mode: PythonParseMode,
run_time_param_values: Optional[RunTimeParamValuesType],
) -> None:
"""Load a python protocol."""
assert self._protocol_runner is not None
assert isinstance(self._protocol_runner, PythonAndLegacyRunner)
await self._protocol_runner.load(
protocol_source=protocol_source,
python_parse_mode=python_parse_mode,
run_time_param_values=run_time_param_values,
)

def get_is_okay_to_clear(self) -> bool:
"""Get whether the engine is stopped or sitting idly, so it could be removed."""
return self._protocol_engine.state_view.commands.get_is_okay_to_clear()

def prepare(self) -> None:
"""Prepare live runner for a run."""
self._protocol_live_runner.prepare()
Loading
Loading