0
$\begingroup$

Rosanswers logo

My first experience with ROS was implementing motor and sensor nodes in Kinetic for the Raspberry Pi/GoPiGo3 robot a couple years ago.

I finished all the ROS2 tutorials successfully, so this month I decided to strengthen my weak ROS2 skills by migrating the examples in the ROS (Kinetic/Melodic) book "Hands-On ROS for Robotics Programming" to ROS2 Foxy.

Chapter 3 ROS Basics (topic pub/sub) went quite smooth, only a few changes from the pub/sub ROS2 tutorial.

Chapter 4 Rviiz Basics (urdf, launch files, launch arguments, parameters, joint_state_publisher(_gui), and rviz2) has made me think ROS2 might just be over the "bleeding edge" at this point.

  • The roughly 10 line launch.xml became a 58 line mess that can't handle arguments or parameters easily

  • joint_state_publisher in the xml launch took a parameter /use_gui and apparently now needs two launch files - one for no gui, and a second for with gui (joint_state_publisher_gui)

  • I found a very applicable example that used a launch condition feature but apparently that got deprecated already.

  • ROS1 launch could pass in the urdf file name, but in ROS2 I have to read the file and I can't figure out how to read the file if the name comes in a launch argument - at least not with the patterns I learned in the ROS2 launch and ROS2 URDF tutorials.

  • I tried the ros2-launch-file-migrator but it just put None for the robot_description and the use_gui arg

Is ROS2 launch going to settle and be easier in a year or so?

Is it too soon to be migrating (as a way to really learn ROS2)?

For reference - this is the launch.xml file I have been working with:

 <launch>
   <!-- values passed by command line input -->     
   <arg name="model" default="gopigoMinimal" />
   <arg name="gui" default="False" />

   <!-- set these parameters on Parameter Server -->
   <param name="robot_description" textfile="$(find rviz_basics)/urdf/$(arg model).urdf" />

   <!-- Start 3 nodes: joint_state_publisher, robot_state_publisher and rviz -->

   <!-- Send joint values -->
   <node pkg="joint_state_publisher" type="joint_state_publisher" name="joint_state_publisher">
     <param name="/use_gui" value="$(arg gui)"/>
   </node>
   <!-- Combine joint values to TF-->
   <node name="robot_state_publisher" pkg="robot_state_publisher" type="state_publisher"/>

   <node name="rviz" pkg="rviz" type="rviz" args="-d $(find rviz_basics)/rviz/$(arg model).rviz" required="true" />
   <!-- (required = "true") if rviz dies, entire roslaunch will be killed -->
</launch>

This is what I have come up with: https://github.com/slowrunner/handsonros2/tree/main/src/chap4_rviz_basics/launch


Originally posted by RobotDreams on ROS Answers with karma: 327 on 2021-07-07

Post score: 3


Original comments

Comment by djchopp on 2021-07-09:
Do you have a source for the comment about launch conditions being deprecated? I do not see any deprecation warnings when using conditions. Thanks

Comment by kscottz on 2021-07-09:
FWIW I've heard repeated requests for improved documentation for ROS 2 launch files. I'll see what we can do to improve the situation.

Comment by RobotDreams on 2021-07-10:
This is the comment that I was referring to: https://answers.ros.org/question/322636/ros2-access-current-launchconfiguration/#:~:text=This%20is%20not,Mar%2023%20%2721%20)

I misunderstood this to be broader than intended.

$\endgroup$

2 Answers 2

0
$\begingroup$

Rosanswers logo

FWIW, I think the ROS2 launch system is in a decent place and I have started porting a lot to it. I think it mostly suffers from poor documentation which means you have to look at the source code. The implementation is somewhat complex which makes the source code hard to digest. There are still some edge cases that are not supported, but I have been able to find equivalent functionality for most things I want to do.

You are right that it generally takes more lines to do the same thing. Note however if you were to condense down your Node definitions to a single line like they are in an XML, it would be shorter and more comparable.

You can use a PathJoinSubstitution to evaluate paths to files at launch time (IE using launch arguments). The big change in ROS2 was that robot_state_publisher no longer accepts a file name, but needs the URDF contents as text. That is why you are required to load it before hand. I tend to use xacro so the following example works for me. Note that to use ParameterValue you probably need to be on Galactic as I don't think that has been back ported yet (one of the edge cases I was talking about earlier).

My suggestion would be to switch your URDF to xacro and use the method below. If you are set on using the urdf you could try something like 'robot_description': ParameterValue(Command(['cat ',model_path,]), value_type=str) with the downside being cat is a linux command and probably won't work on windows. Ideally it would have a OS agnostic command to get the contents of a file... Maybe with H-turtle we will see it

Also shown in the example is the use of conditionals for choosing the gui vs non-gui joint_state_publisher

With all that being said, the XML launch system is still supported and could be used with minimal porting.

Hope this helps!

#!/usr/bin/env python3

from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, Command, PathJoinSubstitution
from launch.conditions import IfCondition, UnlessCondition
from launch_ros.actions import Node
from launch_ros.descriptions import ParameterValue # Need master or Galactic branch for this feature
from launch_ros.substitutions import FindPackageShare

def generate_launch_description():
  this_pkg = FindPackageShare('wheeled_platform_tutorial')

  model_path = LaunchConfiguration('model_path')
  params_path = LaunchConfiguration('params_path')
  use_sim_time = LaunchConfiguration('use_sim_time')
  use_gui = LaunchConfiguration('use_gui')

  return LaunchDescription([
    DeclareLaunchArgument(
      'model_path',
      default_value=PathJoinSubstitution([this_pkg,'xacro','test_rig_model_top.gazebo']),
      description='Full path to model xacro file including filename and extension'
    ),

    DeclareLaunchArgument(
      'params_path',
      default_value=PathJoinSubstitution([this_pkg,'params','test_rig_model.yaml']),
      description='Full path to parameter yaml file including filename and extension'
    ),

    DeclareLaunchArgument(
      'use_sim_time',
      default_value='false',
      description='Use sim (Gazebo) clock when True'
    ),

    DeclareLaunchArgument(
      'use_gui',
      default_value='True',
      description='True causes joint_state_publisher to launch joint_state_publisher_gui'
    ),

    Node(
      package='robot_state_publisher',
      executable='robot_state_publisher',
      name='robot_state_publisher',
      output='screen',
      parameters=[{
        'robot_description': ParameterValue(Command(['xacro ',model_path,' params_path:=',params_path]), value_type=str)
      }],
    ),

    Node(
      package='joint_state_publisher_gui',
      executable='joint_state_publisher_gui',
      name='joint_state_publisher',
      output='screen',
      condition=IfCondition(use_gui)
    ),

    Node(
      package='joint_state_publisher',
      executable='joint_state_publisher',
      name='joint_state_publisher',
      output='screen',
      condition=UnlessCondition(use_gui)
    )

  ])

Originally posted by djchopp with karma: 328 on 2021-07-09

This answer was ACCEPTED on the original site

Post score: 3


Original comments

Comment by Darkproduct on 2021-08-18:
Do you know if and possibly when ParameterValue will be backported to foxy? And second question: What does use_sim_time in you example do? The param is not used in any Node. And how would you use this to set a Node param to this value?

Comment by djchopp on 2021-08-20:
For the if/when question, the short answer is I don't know. I was kinda hoping the original author might get to it. Unfortunately I think there is a web of features that have to be backported in order for ParameterValue to work in foxy. If I get some time I may look into doing it myself.

In the example use_sim_time does nothing and is left over from my original port of my code to python launch.

If you wanted to use a launch argument as a parameter in a node then you would do something like this:

Node(
  package='my_package',
  executable='my_node',
  name='node001',
  output='screen',
  parameters=[{
    'my_parameter': model_path
  }])

or if you don't want to use the variables and just reference by the argument name:

Node(
  package='my_package',
  executable='my_node',
  name='node001',
  output='screen',
  parameters=[{
    'my_parameter': LaunchConfiguration('model_path')
  }])

Comment by Darkproduct on 2021-08-20:
Ok, thanks a lot. Currently, my team an I are really struggeling with the robot_description param. There we use xacro and they have comments, and if you use a ':' anywhere in those comments then you'll get a yaml parser error, because the parser thinks that the urdf is a yaml. I would think ParameterValue would solve this problem.

Comment by djchopp on 2021-08-20:
Indeed the parser sees that colon and gets greedy. Pretty much the exact issue that drove me to the solution in the answer. My current solution for foxy is to have the galactic branches of launch_ros and launch in my workspaces' src folder:

git clone -b galactic https://github.com/ros2/launch_ros.git

git clone -b galactic https://github.com/ros2/launch_ros.git

Not ideal, but it works.

$\endgroup$
0
$\begingroup$

Rosanswers logo

Just thought I'd share a workaround that works in Foxy now, without the need to checkout galactic/rolling versions of packages, using OpaqueFunction and the xacro python library:

#!/usr/bin/env python3

import os

from ament_index_python.packages import get_package_share_directory

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

# add these imports:
import xacro
from launch.actions import OpaqueFunction


# evaluates LaunchConfigurations in context for use with xacro.process_file(). Returns a list of launch actions to be included in launch description
def evaluate_xacro(context, *args, **kwargs):
    use_sim_time = LaunchConfiguration('use_sim_time').perform(context)
    model_path = LaunchConfiguration('model_path').perform(context)
    xacro_prefix = LaunchConfiguration('xacro_prefix').perform(context)
  
    robot_state_publisher_node = Node(
      package='robot_state_publisher',
      executable='robot_state_publisher',
      name='robot_state_publisher',
      output='screen',
      parameters=[{
        'use_sim_time': use_sim_time,
        'robot_description': xacro.process_file(model_path, mappings={'prefix': xacro_prefix}).toxml()
      }])

    return [robot_state_publisher_node]

def generate_launch_description():
  return LaunchDescription([

    DeclareLaunchArgument(
      'use_sim_time',
      default_value="false",
      description='Flag to use simulation time'),   

    DeclareLaunchArgument(
      'model_path',
      default_value=os.path.join(get_package_share_directory('my_package'),'urdf','test.urdf'),
      description='path to urdf'),

    DeclareLaunchArgument(
      'xacro_prefix',
      default_value="my_robot_",
      description='prefix argument input to xacro file'),   

    # add OpaqueFunction to evaluate xacro file in context and pass to any nodes that need it
    OpaqueFunction(function=evaluate_xacro)  
  ])

While it is admittedly a bit harder to read than a typical launch file, it checks all the boxes for my use cases; namely it:

  • supports xacro argument inputs when parsing a file
  • supports dynamically set paths using LaunchConfigurations
  • supports URDF containing special characters (though I haven't tested this exhaustively)

Originally posted by shonigmann with karma: 1567 on 2021-09-08

This answer was NOT ACCEPTED on the original site

Post score: 3


Original comments

Comment by AndyZe on 2022-03-15:
Can I ask what's going on with the context argument here? I have almost the same code (ROS2 Rolling, Ubuntu 20) but this error:

Caught exception when trying to load file of format [py]: load_file() missing 1 required positional argument: 'context'

My test code:

def load_file(context, *args, **kwargs):
    package_path = get_package_share_directory(LaunchConfiguration('moveit_config_package').perform(context))
    return None

def generate_launch_description():
    moveit_config_package = LaunchConfiguration("moveit_config_package")
    declared_arguments = []
    declared_arguments.append(
        DeclareLaunchArgument(
            "moveit_config_package",
            description="Name of the support package",
            choices=["abb_irb1200_5_90_moveit_config"],
        )
    )
    ---
    robot_description_semantic_config = OpaqueFunction(function=load_file())

@William

Comment by shonigmann on 2022-03-16:
I think your problem is that you are calling load_file() and passing the return value to function rather than passing in the function handle (load_file without parenthesis). As such, the load_file() opaque function is getting called without the required input (context); context is satisfied by the OpaqueFunction implementation when it uses the handle, as it makes the current launch context (values of different arguments, launch configurations, etc) available to your load_file() function.

Comment by AndyZe on 2022-03-16:
OK, that makes sense. Thanks. The new error is: Caught exception when trying to load file of format [py]: Unexpected type for parameter value <launch.actions.opaque_function.OpaqueFunction object at 0x7fedb6734820>

Comment by shonigmann on 2022-03-16:
haven't seen that one in particular, but i noticed your load_file() function returns None instead of the generated path. definitely a place to start

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.