tutorial
ROS

How to Use ROS 1 Launch Files

Executing and configuring multiple ROS 1 nodes at once

tutorial
ROS

In previous posts, we’ve seen how ROS 1 nodes communicate using topics, services and actions. We’ve also talked about how we can configure nodes using parameters. In these tutorials, we have been running nodes one by one.

Rather than launching one node at a time, we can leverage launch files to execute and configure multiple nodes with a single command. You can even pull in nodes from other packages to run different processes.

In this tutorial, we will cover how to use a launch file to run multiple nodes, configure them, and group them into meaningful namespaces.

Why ROS 1 launch files?

Running many ROS 1 nodes takes a lot of time and many terminal windows. Even small projects or robots can have many nodes running simultaneously.

Imagine a robot following the "sense-think-act" model that runs one node for each step. A sensor_node is in charge of reading distance data from a sensor, a compute_node receives this data and sends a command to the wheels, and finally a motor_node receives the command and outputs the needed voltage to the motors.

Instead of running each of these nodes in a separate terminal window each time we startup the robot, we can use a launch file to execute them all at once – with a single command, in a single terminal window.

Creating a launch file

Start by creating a new package named launch_pkg in your ROS 1 workspace. In the src folder, create the following files for each of your nodes:

In the root directory of your package, create a launch folder with a launch_example.launch.py file – start by adding the xml version and launch tag:

<?xml version="1.0"?>
<launch>

language-xml

Next, use the node tag to declare each of your nodes and close the launch tag:

<!-- Three nodes -->
<node pkg="launch_pkg" type="sensor_node" name="sensor_node" output="screen"/>
<node pkg="launch_pkg" type="compute_node" name="compute_node" output="screen"/>
<node pkg="launch_pkg" type="motor_node" name="motor_node" output="screen"/>
</launch>

language-xml

Executing the launch file will parse this file and run the list of nodes. Everything else happens behind the curtains of ROS 1.

Before launching our new file, let’s compile the executables by editing your CMakeLists.txt:

# find dependencies
find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
std_msgs
)
# ...
catkin_package()
# ...
add_executable(motor_node src/motor.cpp)
add_executable(sensor_node src/sensor.cpp)
add_executable(compute_node src/compute.cpp)

target_link_libraries(motor_node
${catkin_LIBRARIES}
)
target_link_libraries(sensor_node
${catkin_LIBRARIES}
)
target_link_libraries(compute_node
${catkin_LIBRARIES}
)

language-txt

Compile your workspace with catkin_make and source your workspace before you run your launch file:

$ roslaunch launch_pkg launch_example.launch.py
... logging to /home/jose/.ros/log/6ceb21b0-56e5-11ed-8d02-ede36847100a/roslaunch-jose-hp-10609.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://jose-hp:42669/

SUMMARY
========

PARAMETERS
* /rosdistro: noetic
* /rosversion: 1.15.14

NODES
/
compute_node (launch_pkg/compute_node)
motor_node (launch_pkg/motor_node)
sensor_node (launch_pkg/sensor_node)

auto-starting new master
process[master]: started with pid [10625]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to 6ceb21b0-56e5-11ed-8d02-ede36847100a
process[rosout-1]: started with pid [10642]
started core service [/rosout]
process[sensor_node-2]: started with pid [10645]
process[compute_node-3]: started with pid [10646]
process[motor_node-4]: started with pid [10651]
[ INFO] [1666977890.709897549]: Read value 17767
[ INFO] [1666977890.712234400]: Received value 17767, sending 1.00
[ INFO] [1666977890.715850085]: Received value 1.00
[ INFO] [1666977890.717371423]: Moving motors forward
[ INFO] [1666977891.709779304]: Read value 9158
[ INFO] [1666977891.710294371]: Received value 9158, sending 1.00
[ INFO] [1666977891.710665976]: Received value 1.00
[ INFO] [1666977891.710717793]: Moving motors forward
[ INFO] [1666977892.709717886]: Read value -26519
[ INFO] [1666977892.710128518]: Received value -26519, sending -1.00
[ INFO] [1666977892.710384523]: Received value -1.00
[ INFO] [1666977892.710431862]: Moving motors backwards
[ INFO] [1666977893.709804282]: Read value 18547
[ INFO] [1666977893.710253914]: Received value 18547, sending 1.00
[ INFO] [1666977893.710570992]: Received value 1.00
[ INFO] [1666977893.710637276]: Moving motors forward

language-bash

Check that your nodes are running by opening a new terminal window with ROS 2 sourced and adding Foxglove's Topic Graph panel to your layout:

Topic graph

We got three nodes running with a single line!

Adding namespaces

Launch files can also group your nodes into families, or namespaces. This makes it easier for you to keep track of and monitor your nodes' behavior.

A node has only one name, but can belong to multiple levels of namespaces. These namespaces can be joined with a forward slash (/) – all nodes without a namespace will always have a single / before their names (e.g. /sensor_node). All topics without a namespace specified during declaration will inherit the node’s namespace, as seen in the previous image.

Let’s add our three nodes to a "sense_think_act" namespace in our launch file:

<node ns="sense_think_act" pkg="launch_pkg" type="sensor_node" name="sensor_node" output="screen"/>
<node ns="sense_think_act" pkg="launch_pkg" type="compute_node" name="compute_node" output="screen"/>
<node ns="sense_think_act" pkg="launch_pkg" type="motor_node" name="motor_node" output="screen"/>

language-xml

Run the launch file again, and check the new results with the Topic Graph panel – you'll see that the nodes and the topics have the namespace specified in the launch file:

Nodes with namespaces

Including nodes from other packages

Another great feature of launch files is the possibility to include nodes from another package. Let's pull the robot_node node created in our ROS 1 parameters tutorial into our launch file and configure it with some parameters:

<!-- Adding node from another package with parameters -->
<rosparam command="load" file="$(find params_pkg)/params/warehouseA_core.yaml" />
<node ns="core" pkg="params_pkg" type="robot_node" name="robot_node" output="screen"/>

language-xml

If you have a different workspace, you have to source as well. Remember that you can source as many workspaces as you want. Run your launch file again:

$ roslaunch launch_pkg launch_example.launch.py
... logging to /home/jose/.ros/log/f1802984-56e5-11ed-8d02-ede36847100a/roslaunch-jose-hp-10913.log
Checking log directory for disk usage. This may take a while.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://jose-hp:45537/

SUMMARY
========

PARAMETERS
* /core/robot_node/max_speed: 1.4
* /core/robot_node/robot_name: RobotA
* /core/robot_node/waypoints: ['Home', 'Corrido...
* /rosdistro: noetic
* /rosversion: 1.15.14

NODES
/core/
robot_node (params_pkg/robot_node)
/sense_think_act/
compute_node (launch_pkg/compute_node)
motor_node (launch_pkg/motor_node)
sensor_node (launch_pkg/sensor_node)

auto-starting new master
process[master]: started with pid [10928]
ROS_MASTER_URI=http://localhost:11311

setting /run_id to f1802984-56e5-11ed-8d02-ede36847100a
process[rosout-1]: started with pid [10946]
started core service [/rosout]
process[sense_think_act/sensor_node-2]: started with pid [10949]
process[sense_think_act/compute_node-3]: started with pid [10950]
process[sense_think_act/motor_node-4]: started with pid [10955]
process[core/robot_node-5]: started with pid [10957]
[ INFO] [1666978112.180216384]: Hi! I'm 'RobotA'
[ INFO] [1666978112.181028565]: My max speed is 1.4
[ INFO] [1666978112.181063065]: I will follow the waypoints:
[ INFO] [1666978112.181083044]: 1) Home
[ INFO] [1666978112.181104531]: 2) Corridor
[ INFO] [1666978112.181124876]: 3) Home
[ INFO] [1666978113.161815262]: Read value 17767
[ INFO] [1666978113.163937606]: Received value 17767, sending 1.00
[ INFO] [1666978113.167211139]: Received value 1.00
[ INFO] [1666978113.168465613]: Moving motors forward
[ INFO] [1666978114.161610631]: Read value 9158
[ INFO] [1666978114.161951609]: Received value 9158, sending 1.00
[ INFO] [1666978114.162253739]: Received value 1.00
[ INFO] [1666978114.162292001]: Moving motors forward
[ INFO] [1666978115.161723949]: Read value -26519
[ INFO] [1666978115.162189397]: Received value -26519, sending -1.00
[ INFO] [1666978115.162619920]: Received value -1.00
[ INFO] [1666978115.162685572]: Moving motors backwards

language-bash

And check your nodes again with Foxglove:

All nodes in the Topic Graph panel

Using Foxglove

Now that we’ve created launch files that execute multiple nodes, let’s include Foxglove in the development process.

In this particular Foxglove layout, we are using Foxglove to monitor the sensor and command values:

Foxglove layout

L to R, top to bottom: ROS computational graph in a Topic Graph panel, raw sensor data and cmd_vel values in two Raw Messages panels, sensor data plotted with a Plot panel, cmd_vel.linear.x monitored with a Gauge panel

Summary

ROS 1 launch files can dramatically streamline your robotics development, by making it possible to execute multiple nodes with a single command. We hope you found this tutorial useful, and learned how to streamline your workflows in the future!

For a reference to all the code covered in this post, check out our foxglove/tutorials GitHub repo.

As always, feel free to reach out to the Foxglove team in our Discord community to ask questions, give us feedback, and request a topic for the next tutorial!

Read more

Start building with Foxglove.

Get started for free