ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question
2

How to access the runtime value of a LaunchConfiguration instance within custom launch code (injected via an OpaqueFunction) in ROS2?

asked 2022-03-05 13:50:01 -0500

updated 2022-03-05 15:11:57 -0500

I'm trying to add some dynamic behavior into my python launch file(s) that depends on the runtime value of a/few LaunchConfiguration object(s).

For example, I have a yaml_file_path argument:

def generate_launch_description():
   yaml_file_path = LaunchConfiguration('yaml_file_path')
   yaml_file_path_arg = DeclareLaunchArgument('yaml_file_path')
   ...

The yaml file contains a list of entities that I need to generate IncludeLaunchDescription instances for, which will be returned by my generate_launch_description() api. For example:

#
# Only required for launch processing. These are not node params.
#
module_a:
   prefix: <some folder path>
   ...
module_b:
   prefix: <some other folder path>
   ...

I understand I need to define and return an OpaqueFunction within my generate_launch_description() method, as below:

def generate_launch_description():
   yaml_file_path = LaunchConfiguration('yaml_file_path')
   yaml_file_path_arg = DeclareLaunchArgument('yaml_file_path')

   ...
   variable_includer = OpaqueFunction(generate_variable_include_list, ...<???>...)     # <<<<<<< ??? <<<<<<<<

   return [
       yaml_file_path_arg,
       variable_includer,
   ]

The generate_variable_include_list() method injected above generates a module_includes containing the IncludeLaunchDescription instances for each item in yaml file:

def generate_variable_include_list(context: LaunchContext, yaml_file_path: str) -> List[LaunchDescriptionEntity]:
   settings: Dict[str, Any] = parse_yaml_file_to_dict(yaml_file_path)

   module_includes: List[IncludeLaunchDescription] = []
   for (key,value) in settings.items():
      module_includes.append(
         IncludeLaunchDescription(
            PythonLaunchDescriptionSource(
               PathJoinSubstitution([settings[key]['prefix'], "module.launch.py"])
            ),
            launch_arguments=[...]
         )
      )
      module_includes.append(
         LogInfo(msg=(["\tIncluding file: ", PathJoinSubstitution([settings[key]['prefix'], "module.launch.py"])]))
      )
   return module_includes

... but, I can't seem to find documentation about the following:

  • how to accept the yaml_file_path into this generate_variable_include_list() function above? (Specifically shown with the <???> comment). I mean, what type is the argument supposed to be? That is, should I be passing in the LaunchConfiguration object as-is, i.e, yaml_file_path, or some post-processed version of it?
  • how to use that yaml_file_path value in that function? That is, since it is an asyncio future, how do I access the resolved (runtime) outcome of that LaunchConfiguration instance within my generate_variable_include_list() method? Should I invoke the perform() API on the object, or would that cause redundant evaluation of the associated future? Is there any cache that I can lookup instead, at runtime, for the resolved value of this entity?
  • And, in a general sense, what if the entity in question was any LaunchDescriptionEntity object, not just a LaunchConfiguration, and I wanted to obtain its resolved/future value in my OpaqueFunction above?

Some sample code, wherever applicable, would be greatly appreciated.

edit retag flag offensive close merge delete

2 Answers

Sort by ยป oldest newest most voted
3

answered 2022-03-10 18:30:00 -0500

updated 2023-05-12 04:40:46 -0500

130s gravatar image

When using OpaqueFunction you can define the LaunchConfiguration variable inside it and use its .perform(context) method. You can check it out how I did it here So, in short

def launch_setup(context, *args, **kwargs):
    some_var = LaunchConfiguration('some_variable')
    some_var_runtime_value = some_var.perform(context)

I hope this helps


UPDATE by @130s: Because relying on the content in the external link is fragile as it may become unavailable at anytime, I copy-pasted the essense below.

def launch_setup(context, *args, **kwargs):

    robot_model = LaunchConfiguration("robot_model")
    :

def generate_launch_description():
    return LaunchDescription(
        [
            DeclareLaunchArgument(
                "robot_model",
                default_value=TextSubstitution(text=""),
                description=(
                    "model type of the Interbotix Arm such as 'wx200' or 'rx150'"
                ),
            ),
            DeclareLaunchArgument(
        :

            OpaqueFunction(function=launch_setup),
        ]
    )
edit flag offensive delete link more

Comments

Thanks @Serafadam. The example link you included above takes me to duckduckgo.com. Perhaps you meant to include a github link instead?

alikureishy gravatar image alikureishy  ( 2022-03-10 20:41:05 -0500 )edit

Ah, sorry, it's fixed now

Serafadam gravatar image Serafadam  ( 2022-03-11 00:55:15 -0500 )edit

Thanks @Serafadam! The example link is very helpful.

alikureishy gravatar image alikureishy  ( 2022-03-11 23:50:35 -0500 )edit

+1. I've edited your answer to include the entire info that's needed to make it more self-contained answer.

130s gravatar image 130s  ( 2023-05-12 04:52:47 -0500 )edit
1

answered 2023-05-14 14:05:58 -0500

danzimmerman gravatar image

updated 2023-05-14 14:06:12 -0500

I discovered recently that the context object also has a context.launch_configurations attribute that is a dict of the names and values of the declared LaunchConfiguration variables.

See here

https://github.com/danzimmerman/dz_la...

for argname, argval in context.launch_configurations.items():
    arg_message = f"'{argname}' has a value of {argval}"
    info_log_items.append(launch.actions.LogInfo(msg=arg_message))
edit flag offensive delete link more

Question Tools

3 followers

Stats

Asked: 2022-03-05 13:50:01 -0500

Seen: 1,491 times

Last updated: May 14 '23