Executing and configuring multiple ROS 1 nodes at once
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.
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.
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:
We got three nodes running with a single line!
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:
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:
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:
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
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!