Skip to content

Conversation

@mamueluth
Copy link
Member

@mamueluth mamueluth commented Nov 24, 2025

Replaces: #2349
There are cases in which you want to spawn controllers running on a remote machine (robot) without the controllers.yaml beeing available on the local machine. This PR extends the spawner with the option to activate a controller on the remote machine without the need of having controllers.yaml on your local machine.

Example:

ros2 run controller_manager spawner fts_broadcaster:force_torque_sensor_broadcaster/ForceTorqueSensorBroadcaster --param-file /path/to/controllers.yaml --param-file-remote-only

Open questions

  1. How to pass controller type? In the spawner we need to know at least the controller name and the controller type. Usually we would parse the type from the controller.yaml but since the file is not present on the local machine we need to pass the type. I choose to do: controller:type as can be seen in the example. Is this good?

  2. How should we go about testing? In tests the file will always be present. I would just define the .yaml in code, don't have the file present and test like this or are there any other suggestions?

Copy link
Member

@saikishor saikishor left a comment

Choose a reason for hiding this comment

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

If i understood correctly it expects that the file exist on the same PC as the controller manager. I thought this PR is for cases when you want to load files of PC launching the command, but not on the PC the controller_manager is running

@mamueluth
Copy link
Member Author

If i understood correctly it expects that the file exist on the same PC as the controller manager. I thought this PR is for cases when you want to load files of PC launching the command, but not on the PC the controller_manager is running

No its inverted its for cases where you don't have the .yaml file local but it exists on the remote machine (robot).

I wanted to add the inverse as well if we want this.

@mamueluth mamueluth changed the title Pass yaml load controller Spwaner: Load controller with remote only .yaml Nov 27, 2025
@codecov
Copy link

codecov bot commented Nov 28, 2025

Codecov Report

❌ Patch coverage is 36.53846% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.47%. Comparing base (03372e0) to head (d12be59).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
controller_manager/controller_manager/spawner.py 35.13% 19 Missing and 5 partials ⚠️
.../controller_manager/controller_manager_services.py 40.00% 6 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2853      +/-   ##
==========================================
- Coverage   89.57%   89.47%   -0.10%     
==========================================
  Files         152      152              
  Lines       18087    17732     -355     
  Branches     1470     1442      -28     
==========================================
- Hits        16201    15866     -335     
+ Misses       1297     1287      -10     
+ Partials      589      579      -10     
Flag Coverage Δ
unittests 89.47% <36.53%> (-0.10%) ⬇️

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

Files with missing lines Coverage Δ
.../controller_manager/controller_manager_services.py 77.59% <40.00%> (-3.55%) ⬇️
controller_manager/controller_manager/spawner.py 62.62% <35.13%> (-8.89%) ⬇️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@christophfroehlich christophfroehlich changed the title Spwaner: Load controller with remote only .yaml Spawner: Load controller with remote-only .yaml Nov 30, 2025
Copy link
Member

@saikishor saikishor left a comment

Choose a reason for hiding this comment

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

Let me know what you think

Comment on lines +129 to +135
parser.add_argument(
"-n",
"--namespace",
help="DEPRECATED Namespace for the controller_manager and the controller(s)",
default=None,
required=False,
)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
parser.add_argument(
"-n",
"--namespace",
help="DEPRECATED Namespace for the controller_manager and the controller(s)",
default=None,
required=False,
)

This is no more needed from rolling on

Comment on lines +75 to +96
def parse_type_from_controllers(controller_names: list[str]) -> dict[str, str]:
controller_to_type : dict[str, str] = dict()
for name in controller_names:
# We expect controller:some/type
# -> split[0]=controller AND split[1]=some/type
split = name.split(":")
if len(split) != 2 or not split[0] or not split[1]:
raise ValueError(
f"Invalid format '{name}'. Expected format is 'controller_name:some/controller_type' if '--param-file-remote-only' flag is used."
)
controller = split[0]
controller_type = split[1]

if controller in controller_to_type:
raise ValueError(
f"Controller names must be unique. Got multiple occurrences of {controller}"
)
else:
controller_to_type[controller] = controller_type
return controller_to_type


Copy link
Member

Choose a reason for hiding this comment

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

For the type, maybe it makes sense to have the --type arg ? Instead of doing it this way?

Comment on lines +122 to +128
parser.add_argument(
"--param-file-remote-only",
help="Set this to load the param file only remotely. Param file is not needed to be present locally only remotely.",
default=False,
action="store_true",
required=False,
)
Copy link
Member

Choose a reason for hiding this comment

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

I've been thinking about the arg, should we use something like --skip--param-file-parsing ? Because what you are doing within the spawner's changes is simply avoiding parsing the files and setting them as the parameters

Comment on lines +236 to +260
if node.get_namespace() != "/" and args.namespace:
raise RuntimeError(
f"Setting namespace through both '--namespace {args.namespace}' arg and the ROS 2 standard way "
f"'--ros-args -r __ns:={node.get_namespace()}' is not allowed!"
)

if args.namespace:
warnings.filterwarnings("always")
warnings.warn(
"The '--namespace' argument is deprecated and will be removed in future releases."
" Use the ROS 2 standard way of setting the node namespacing using --ros-args -r __ns:=<namespace>",
DeprecationWarning,
)

spawner_namespace = args.namespace if args.namespace else node.get_namespace()

if not spawner_namespace.startswith("/"):
spawner_namespace = f"/{spawner_namespace}"

if not controller_manager_name.startswith("/"):
if spawner_namespace and spawner_namespace != "/":
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
else:
controller_manager_name = f"/{controller_manager_name}"

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if node.get_namespace() != "/" and args.namespace:
raise RuntimeError(
f"Setting namespace through both '--namespace {args.namespace}' arg and the ROS 2 standard way "
f"'--ros-args -r __ns:={node.get_namespace()}' is not allowed!"
)
if args.namespace:
warnings.filterwarnings("always")
warnings.warn(
"The '--namespace' argument is deprecated and will be removed in future releases."
" Use the ROS 2 standard way of setting the node namespacing using --ros-args -r __ns:=<namespace>",
DeprecationWarning,
)
spawner_namespace = args.namespace if args.namespace else node.get_namespace()
if not spawner_namespace.startswith("/"):
spawner_namespace = f"/{spawner_namespace}"
if not controller_manager_name.startswith("/"):
if spawner_namespace and spawner_namespace != "/":
controller_manager_name = f"{spawner_namespace}/{controller_manager_name}"
else:
controller_manager_name = f"/{controller_manager_name}"

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.

3 participants