Robotics StackExchange | Archived questions

Equivalents of "if" and "unless" ROS1 XML for ROS2 python launch files

Hi,

I migrate some packages to ROS2 (humble) and I don't find an equivalent of the if and unless arguments I had in ROS1 (noetic) XML launch files for python ROS2 launchers. I know I could achieve that with XML in ROS2 too, but I was curious to make it in python since it seems to be a more "ROSish" way to do it, nowadays.

A very simple example ROS1 snippet XML

 <launch>
   <arg name="viz" default="false"/>
   <node if="$(arg viz)" pkg="rviz" name="rviz" type="rviz"/>
 </launch>

Now In ROS2, I have the following python file, but the rviz node run is not conditioned to the value of viz anymore.

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import TextSubstitution
from launch_ros.actions import Node

def generate_launch_description():
  launch_description = LaunchDescription()

  viz_arg = DeclareLaunchArgument("viz", default_value=TextSubstitution(text="false"))  
  launch_description.add_action(viz_arg)

  rviz_node = Node(
    package='rviz2',
    executable='rviz2',
    name='rviz2',
  )
  # I'd like condition the following line with an if, I guess
  launch_description.add_action(rviz_node)

  return launch_description

Thanks for your help.

Asked by aplat on 2022-12-05 05:42:13 UTC

Comments

Answers

This is answered is previous questions. For instance, see: https://answers.ros.org/question/382000/ros2-makes-launch-files-crazy-too-soon-to-be-migrating/?answer=382141#post-id-382141

In short, you should be able to do:

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import TextSubstitution
from launch_ros.actions import Node

def generate_launch_description():
  launch_description = LaunchDescription()

  viz_arg = DeclareLaunchArgument("viz", default_value=TextSubstitution(text="false"))  
  launch_description.add_action(viz_arg)

  rviz_node = Node(
    package='rviz2',
    executable='rviz2',
    name='rviz2',
    condition=IfCondition(LaunchConfiguration('viz')),
  )
  launch_description.add_action(rviz_node)

  return launch_description

Asked by christophebedard on 2022-12-05 16:30:02 UTC

Comments

Thanks for your answer and the link. I now understand that it has to be done through the condition mechanism. My question was broader than the example I posted though, because the if and unless of ROS1 are usable in more than <node> tag, for example also in <param> and in <include>.

Asked by aplat on 2022-12-08 17:14:33 UTC

The condition parameter applies to all Actions: https://github.com/ros2/launch/blob/7694235bbf00f19dafd7251f6954f7f14606ab4c/launch/launch/action.py#L40. Node, IncludeLaunchDescription, SetParameter, etc. are Actions, so they all (as well as their corresponding YAML/XML versions) support the condition parameter.

Asked by christophebedard on 2022-12-08 17:20:35 UTC

Thanks, that was the missing part of the puzzle. The naming is not obvious though, I did not get that all those classes inherited from Action. Now it makes perfect sense. Thanks again!

Asked by aplat on 2022-12-08 18:00:09 UTC

Yeah, documentation could definitely be helpful here.

Asked by christophebedard on 2022-12-10 12:50:33 UTC

To mimic a if and unless present into an <include> tag, you should use conditions such as pointed by Christophe in another answer.

Note that several types of conditions exists, such as IfCondition, UnlessCondition, LaunchConfigurationEquals and LaunchConfigurationNotEquals

For example, to migrate the following ROS1 XML code

<include file="$(find pkg_1)/launch/my_file.launch" if="$(eval points != '/points')">

In python ROS2

from ament_index_python import get_package_share_directory
from launch import LaunchDescription
from launch.actions.include_launch_description import IncludeLaunchDescription
from launch.conditions import LaunchConfigurationNotEquals
from launch.launch_description_sources
import PythonLaunchDescriptionSource
import os

launch_description = LaunchDescription() 

pkg_1_include = IncludeLaunchDescription(
  PythonLaunchDescriptionSource(
    os.path.join(get_package_share_directory('pkg_1'), 'launch/my_file.launch')),
  condition=LaunchConfigurationNotEquals('points', "/points"),
)
launch_description.add_action(pkg_1_include)

Asked by aplat on 2022-12-08 17:53:27 UTC

Comments

Hi,

If you use the simple_launch package*, conditions boil down to a with block as follows:

from simple_launch import SimpleLauncher

def generate_launch_description():
    sl = SimpleLauncher()

    sl.declare_arg('viz', default_value=False, description='Run RViz')

    with sl.group(if_arg='viz'):
        sl.node('rviz2', 'rviz2')

return sl.launch_description()

More complex conditions can be combined, and the opaque_function approach can always be taken when actual Python (not launch) logic is needed.

  • disclaimer: I am the author of this package

Asked by Olivier Kermorgant on 2022-12-12 06:20:08 UTC

Comments

Looks great, I'll definitely give it a try. To be honest, the number of line inflation from a one liner XML snippet to a 15+ lines python equivalent is a bit hard to swallow, and your package looks very appealing.

Asked by aplat on 2022-12-16 06:15:46 UTC

I could not agree more, that is why simple_launch was created in the first place. The initial idea was to easily teach the principles of launch files without the syntax bloat but at the end of the day this package is useful for many cases.

Asked by Olivier Kermorgant on 2022-12-16 09:14:40 UTC

If you want to use XML, then why don't you create an XML ROS 2 launch file?

Asked by christophebedard on 2022-12-17 13:52:34 UTC