0
$\begingroup$

Rosanswers logo

using ROS2 Foxy

I have really been struggling how to do 2 seemly simply thing with a launch file. By launch file I mean launch.py not xml.

The 2 things I am trying to do are: 1 pass a command line argument the controls whether to launch a node or not. Recently I found 2 ways to do that see below. 2 pass in the name of a specific config file or world (the file name only not the full path) and have the launch file append the current package config or world folder and pass it on to a node as a parameter or argument

Maybe I am missing something and to help someone else I am including my understand of a launch.py. For ROS1 there were clear tutorials for this but I could find any for ROS2.

My understanding of how this works

First imports

import os ---import this so you can use os.path.join( function

from launch.actions import IncludeLaunchDescription --- import this if you want to include another launch.py file using IncludeLaunchDescription( function

from launch.conditions import IfCondition -- import this to conditional launch a node (only discovered this yesterday and answers my #1)) use on this function condition=IfCondition(LaunchConfiguration('use_rviz')) on a Node( statement

from ament_index_python.packages import get_package_share_directory -- allows pkg_gazebo_ros = get_package_share_directory('gazebo_ros') to get share folder so that you could then use os.path.join to point to the config or world folder

from launch import LaunchDescription -- required

from launch.actions import DeclareLaunchArgument --- needed to declare command line arguments

from launch.substitutions import LaunchConfiguration --- I think this is a way to access the argument??

from launch_ros.actions import Node -- required to use Node statement

Second functions

After you have the imports you need this line def generate_launch_description():

You also need return LaunchDescription([ alistofnodes ])

How to declare arguments

in order for commandline argument to work you need to use DeclareLaunchArgument.

DeclareLaunchArgument( 'use_rviz', default_value='false', description='Use rviz if true'),

Does this have to happen inside the LaunchDescription( function? If so that seem too late. I would think these are like variable declarations and you would want them well before the LaunchDescription( which is typically the last section.

How to access those values I think you use LaunchConfiguration use_rviz = LaunchConfiguration('use_rviz') But this actual returns a LaunchConfiguration object not the value see below

How to launch a node

apparently there are several options you can use Node()

Node( package='rviz2', executable='rviz2', name='rviz2', argument=[], paramters=[] output='screen')

there is ExecuteProcess( also

OpaqueFunction

This is a function that can bee called inside LaunchDescription( that calls a function you write. It can access argument by using LaunchConfiguration('use_rviz').perform(context). You can then use an if statement to build nodes and then return those nodes

Is my understanding correct? Does anyone see anything wrong with my statements above?

Now for the 2 things I am trying to do.

Conditionally create nodes based on command line argument. I figures out 2 ways to do this.

I have this command line ros2 launch bigbotdescript bigbotdescript.launch.py use_sim_time:=true use_rviz:=true

and I want to launch rviz if use_rviz = true and launch spawn my robot urdf in gazebo if use_sim_time=true

OpaqueFunction option This option works

# normal imports
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
from launch.actions import IncludeLaunchDescription
from ament_index_python.packages import get_package_share_directory
# OpaqueFunction import
from launch.actions import OpaqueFunction


# I declare a function
# it must have context, *args, **kwargs even though I can not seem to access contect or args or kwargs
# the name of the function is used in the call to OpaqueFunction see below
def evaluate_spawn(context, *args, **kwargs):

    use_sim_time = LaunchConfiguration('use_sim_time').perform(context)
    print("use_sim_time",use_sim_time)
    if use_sim_time == "true":
        print("We should spawn")
        #print(robot_desc)
        # am having issues with spawnrobot2 but that is another issue
        spawnnode = Node(package='spawnrobot', executable='spawnrobot2', arguments=[{'robot_desc' : robot_desc},{'x' : 0.01},{'y':0.02},{'z' : 0.03} ], output='screen')

    use_rviz = LaunchConfiguration('use_rviz').perform(context)
    if use_rviz == "true":
        print("use_rviz " ,use_rviz)
        rviz_config_dir = os.path.join(
            get_package_share_directory('bigbotdescript'),
            'rviz',
            'odom.rviz')
        print("rviz_config_dir ", rviz_config_dir)

        rviznode =    Node(
            package='rviz2',
            executable='rviz2',
            name='rviz2',
            arguments=['-d', rviz_config_dir],
            output='screen')
        print("after rviznode ")
    if use_sim_time == "true" and use_rviz == "true":
        return [spawnnode ,rviznode]
    else:
        if use_sim_time == "true" and use_rviz == "false":
            return [spawnnode]
        else:
            if use_sim_time == "false" and use_rviz == "true":
                return [rviznode]

-------------
  return LaunchDescription([

    DeclareLaunchArgument(
            'use_sim_time',
            default_value='false',
            description='Use simulation (Gazebo) clock if true'),
    DeclareLaunchArgument(
            'use_rviz',
            default_value='false',
            description='Use rviz if true'),

    OpaqueFunction(function=evaluate_spawn )  
])

This works and the correct nodes get launched. I found examples of this a couple of weeks ago it took we a couple of days to work out the kink.

option IfCondition

Same commandline ros2 launch bigbotdescript bigbotdescript.launch.py use_sim_time:=true use_rviz:=true here is some code that I put together for this question that does launch the correct based on the parameter passed.

#normal imports
import os
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
from launch.actions import IncludeLaunchDescription
from ament_index_python.packages import get_package_share_directory
# if import
from launch.conditions import IfCondition 

def generate_launch_description():

    # why does these work here when argument decalred below???
    use_sim_time = LaunchConfiguration('use_sim_time', default='false')
    use_rviz = LaunchConfiguration('use_rviz', default='true')

    #build rviz config
    rviz_config_dir = os.path.join(
            get_package_share_directory('bigbotdescript'),
            'rviz',
            'odom.rviz')

   
    return LaunchDescription([

    DeclareLaunchArgument(
            'use_sim_time',
            default_value='false',
            description='Use simulation (Gazebo) clock if true'),
    DeclareLaunchArgument(
            'use_rviz',
            default_value='false',
            description='Use rviz if true'),

    Node(
            package='rviz2',
            executable='rviz2',
            name='rviz2',
            arguments=['-d', rviz_config_dir],
            output='screen',
            condition=IfCondition(use_rviz )
            ), 
    Node(
        package='spawnrobot', 
        executable='spawnrobot2', 
        arguments=[{'robot_desc' : robot_desc},{'x' : 0.01},{'y':0.02},{'z' : 0.03} ], 
        output='screen',
        condition=IfCondition(use_sim_time)
        )
     ])

Now onto question number 2

How to pass just the file name for a config file or a world file. I am using the rviz config for this example but I am really wanting to do this for the world for gazebo and config files for navigation.

So from the above example (option IfCondition) I want to be able to pass in an additional parameter for the rviz config call rvizconfig. The new command line is ros2 launch bigbotdescript bigbotdescript.launch.py use_sim_time:=true use_rviz:=true rvizconfig:=odom.rviz

So I need to add to declare it

DeclareLaunchArgument( 'rvizconfig', default_value='odom.rviz', description='which rviz config file in rviz folder'),

so where I had this

#build rviz config rviz_config_dir = os.path.join( get_package_share_directory('bigbotdescript'), 'rviz', 'odom.rviz')

I have changed this to

   rviz_config = LaunchConfiguration('rvizconfig', default='odom.rviz')

    #build rviz config
    rviz_config_dir = os.path.join(
            get_package_share_directory('motorcontrol'),
            'rviz',
            rviz_config)

and left this the same

 Node(
            package='rviz2',
            executable='rviz2',
            name='rviz2',
            arguments=['-d', rviz_config_dir],
            output='screen',
            condition=IfCondition(use_rviz )
            ), 

This does not work I am getting join() argument must be str, bytes, or os.PathLike object, not 'LaunchConfiguration' so rviz_config = LaunchConfiguration('rvizconfig', default='odom.rviz') result in rviz_config being a LaunchConfiguration not the string odom.rviz passed on the command.

how do I get the actual string

If I do print(rviz_config.values)
I get 'LaunchConfiguration' object has no attribute 'values' even though Visual code should it having that property If I do print(rviz_config.variable_name) I get [<launch.substitutions.text_substitution.TextSubstitution object at 0x7f41cd3f1370>] rviz_config.parse,rviz_config.describe, do not return anything useful

rviz_config.perform requires some kind of context see the definition of evaluate_spawn above

how do I get the actual string ?


Originally posted by ed on ROS Answers with karma: 91 on 2022-01-23

Post score: 0

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

Based on reviewing some other questions/messages I am using the opaquefunction.


Originally posted by ed with karma: 91 on 2022-01-30

This answer was ACCEPTED on the original site

Post score: 0


Original comments

Comment by ed on 2022-02-04:
I just saw this package simple_lunch https://index.ros.org/p/simple_launch/ which seems to handle some of the things I was looking to solve. If I get some time I will investigate it

$\endgroup$

Your Answer

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