Skip to content

feat(robot-server, api): Implement RobotServerPyroResource providing proxies of server assets#21240

Open
CaseyBatten wants to merge 10 commits intoedgefrom
EXEC_2195_robot_server_pyro_hookup
Open

feat(robot-server, api): Implement RobotServerPyroResource providing proxies of server assets#21240
CaseyBatten wants to merge 10 commits intoedgefrom
EXEC_2195_robot_server_pyro_hookup

Conversation

@CaseyBatten
Copy link
Copy Markdown
Contributor

@CaseyBatten CaseyBatten commented Apr 9, 2026

Overview

Covers EXEC-2195

The bulk of this PRs logic gets us up to the point where if the hardware subprocess feature flag is enabled and a hardware subprocess is running, a run can "start" (without the protocol subprocess involved). This does not include the callback proxy for the doorwatcher needed after beginning a run (that will happen elsewhere).

TODO:

  • Clean up the use of feature flag logic
  • Reinforce and test the feature flags
  • Theres a host of todo's regarding consolidating code and/or setting up systemD service messages, either address or expand these in future PRs
  • Unit tests to enforce that the several assets provided by the RobotServerPyroResource are available as Async-able and Cast-able proxies (this has only been manually tested)
  • Good lord so much linting to be done

Test Plan and Hands on Testing

  • Unit tests enforcing Proxy returns from RobotServerPyroResource
  • Test on robot with hardware subprocess flag on
  • Test on robot with hardware subprocess flag off

Changelog

Introduced bifurcated logic for initializing the hardware API depending on if the system is in "subprocess" mode
Introduced the RobotServerPyroResource which is spun up on app start.

Review requests

The use of feature flags is happening in a lot of places, should this be consolidated now or later?

Risk assessment

Medium - this is a pretty big one. It doesn't have to be "forever" code, especially the feature flag logic, but the core of the RobotServerPyroResource architecture will certainly remain. How do we feel about it?

@CaseyBatten CaseyBatten requested review from jbleon95 and sfoster1 April 9, 2026 20:43
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 70.74830% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.56%. Comparing base (480a6bf) to head (8b22953).
⚠️ Report is 79 commits behind head on edge.

Files with missing lines Patch % Lines
...r/robot_server/service/pyro_utils/pyro_resource.py 72.54% 14 Missing ⚠️
robot-server/robot_server/hardware.py 71.87% 9 Missing ⚠️
...ot_server/service/pyro_utils/resource_utilities.py 66.66% 8 Missing ⚠️
...er/robot_server/service/legacy/routers/settings.py 40.00% 6 Missing ⚠️
...rver/robot_server/maintenance_runs/dependencies.py 50.00% 2 Missing ⚠️
robot-server/robot_server/runs/dependencies.py 50.00% 2 Missing ⚠️
robot-server/robot_server/subsystems/router.py 33.33% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #21240      +/-   ##
==========================================
- Coverage   56.97%   56.56%   -0.42%     
==========================================
  Files        3978     3996      +18     
  Lines      326009   327714    +1705     
  Branches    46358    46690     +332     
==========================================
- Hits       185744   185362     -382     
- Misses     140046   142133    +2087     
  Partials      219      219              
Flag Coverage Δ
app 44.91% <ø> (-0.21%) ⬇️
protocol-designer 19.93% <ø> (+<0.01%) ⬆️
step-generation 5.79% <ø> (+0.10%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
robot-server/robot_server/app_setup.py 92.59% <100.00%> (+0.28%) ⬆️
...server/robot_server/camera/fastapi_dependencies.py 100.00% <100.00%> (ø)
...server/robot_server/data_files/data_files_store.py 27.04% <ø> (-31.71%) ⬇️
...robot_server/file_provider/fastapi_dependencies.py 100.00% <100.00%> (ø)
...tenance_runs/maintenance_run_orchestrator_store.py 55.55% <100.00%> (-19.73%) ⬇️
...-server/robot_server/robot/control/dependencies.py 76.19% <100.00%> (+14.28%) ⬆️
...server/robot_server/runs/run_orchestrator_store.py 61.88% <100.00%> (-3.89%) ⬇️
...server/service/pyro_utils/serpent_type_registry.py 100.00% <100.00%> (ø)
...a/python/opentrons_shared_data/data_files/types.py 0.00% <ø> (ø)
...rver/robot_server/maintenance_runs/dependencies.py 86.95% <50.00%> (-13.05%) ⬇️
... and 6 more

... and 172 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@CaseyBatten CaseyBatten marked this pull request as ready for review April 10, 2026 19:58
@CaseyBatten CaseyBatten requested a review from a team as a code owner April 10, 2026 19:58
Copy link
Copy Markdown
Member

@sfoster1 sfoster1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me when tested! Got some inline things about style and spelling but fundamentally looks good!

@@ -32,10 +33,12 @@ class DataFileSource(Enum):
GENERATED = "generated"


@dataclass(frozen=True)
class DataFileInfo:
#@dataclass(frozen=True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove comment

@@ -116,6 +124,9 @@ async def clean_up_hardware(app_state: AppState) -> None:
_init_task_accessor.set_on(app_state, None)
_postinit_task_accessor.set_on(app_state, None)
_hw_api_accessor.set_on(app_state, None)
# NOTE: we erase the subprocess state, but do not run clean_up() on the subprocess.
# This is the responsibility of it's executing service.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# This is the responsibility of it's executing service.
# This is the responsibility of its executing service.

@@ -121,7 +121,8 @@ def __init__(
self._hardware_api = hardware_api
self._robot_type = robot_type
self._deck_type = deck_type
hardware_api.register_callback(_get_estop_listener(self))
if not feature_flags.hardware_subprocess_enabled():
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is one of the todos, right? maybe a good place for a todo comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a todo. This is just the preservation of the original callback registry assuming we are not in subprocess mode, for both runs and maintenance runs. I don't think this will ever go away unless we completely remove the old single-process software someday and do away with feature flags.

@@ -193,7 +195,8 @@ def __init__(
self._run_process: Optional[subprocess.Popen[bytes]] = None
if feature_flags.protocol_subprocess_enabled():
register_process_types()
hardware_api.register_callback(_get_hardware_listener(self))
if not feature_flags.hardware_subprocess_enabled():
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a todo perhaps

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here what I said in the reply above.

@@ -95,6 +106,29 @@ async def _set_oem_mode_request(enable: bool, authorization_header: str | None)
return resp.status


async def _hardware_subprocess_transition(enable: bool, app_state: AppState) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoa, this is fancy. i'd be okay just having this require a restart but it is pretty cool to have it be live

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feasibility of this one will depend on the the systemd implementation for the OT3API. Once that work follows this up I'll be able to test linking the two, if it proves non-viable we'll go through with the restart approach.



class RobotServerPyroResource:
"""Class to represent the resources the robot-server hosts which can be provided over Pryo5.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Class to represent the resources the robot-server hosts which can be provided over Pryo5.
"""Class to represent the resources the robot-server hosts which can be provided over Pyro5.

with pyro.locate_ns() as ns:
while time.monotonic() - start_time < 60:
if RS_PYRONAME in ns.list():
robot_server_proxy = pyro.Proxy(ns.list()[RS_PYRONAME]) # type: ignore[no-untyped-call]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe let's get a time.sleep(0.01) in here or something

self._camera_provider: Optional[CameraProvider] = None
self._file_provider: Optional[FileProvider] = None

def _set_run_orchestorator_store(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _set_run_orchestorator_store(
def _set_run_orchestrator_store(

"""Set a provided RunOrchestratorStore as the active store to be used by the Robot Server's Pyro Resource."""
robot_server_pyro_resource = robot_server_pyro_resource_accessor.get_from(app_state)
if robot_server_pyro_resource is not None:
robot_server_pyro_resource._set_run_orchestorator_store(run_orchestrator_store)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't love calling underscore methods from over here

@@ -81,6 +83,7 @@ class FlexHardwareControlInterface(
with some additional functionality and parameterization not supported on the OT-2.
"""

@pyro_behavior(specialty_func=convert_type_to_instance, apply_local=False)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer pulling the implementation out of this function if possible so this typing protocol remains free to import without involving more dependencies and import-time work

Copy link
Copy Markdown
Contributor Author

@CaseyBatten CaseyBatten Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a good point. I'll see if theres a good place to create a pyro friendly alternative to somewhere else for the get_robot_type method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants