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