In this post, I’m going to provide some straight-forward pointers for getting started using ROS 2.
There’s a minimal repository with the code from this post here.
I recently started a new job which involves me working on the Stretch robot from Hello Robots. Since my background is principally in machine learning and not robotics, this is my first time using ROS. However, when I searched for “ROS 2 API” or “How to use ROS 2” or “Getting Started with ROS 2” most of the sites I found looked like they had been insanely SEO’d to the point of being unusable. To wit, I figured I would document my progress as I am learning so that others who may be making the same transition might benefit as well.
In this blog post I hope to do the following:
If I miss anything, please leave a comment (note that the commenting tool requires a Github account).
In this post I won’t be covering:
Throughout this post, I will be using RoboStack, which is a way of installing ROS as a Conda bundle. This makes it easier to manage multiple versions of ROS and install new packages.
# Robostack's ROS 2 Humble channel only works with Python 3.9.
# Galactic can work with either version.
conda create --name ros-blog-post python=3.9
conda activate ros-blog-post
conda install -c conda-forge mamba
Mamba is a drop-in replacement for the Conda CLI which is a lot faster and makes working with Conda packages a lot easier.
If you want to avoid adding specific Conda channels, such as
-c conda-forge
, you can add them to your~/.condarc
file usingconda config --add channels conda-forge
. Then you can just do something likeconda install mamba
and it will look in theconda-forge
channel automatically. In particular for this post I suggest adding therobostack-humble
channel if you plan on using ROS 2 Humble.
Following the installation instructions here:
# Install Humble distro.
mamba install \
-c robostack-humble \
-c conda-forge \
spdlog=1.9.2 \
foonathan-memory=0.7.2 \
ros-humble-desktop
# These are the instructions to install the Galactic
# distro, which worked fine for me on an Ubuntu machine
# but failed on my M1 Mac.
mamba install \
-c robostack-experimental \
ros-galactic-desktop
Note that installing ROS 2 this way adds some scripts to the directory in ${CONDA_PREFIX}/etc/conda/activate.d/
. These set some environment variables which are important. In order to get these scripts to run, you have to restart your Conda environment, like so:
conda deactivate
conda activate ros-blog-post
ROS 2 packages are built using colcon, so you will need to install it as well:
mamba install \
-c conda-forge \
colcon-core \
colcon-common-extensions
You can double-check that the installation was successful by running the dummy programs. In one terminal session, run:
ros2 run demo_nodes_cpp talker
If everything was installed correctly, you should see something like this:
[INFO] [1667791882.591136612] [talker]: Publishing: 'Hello World: 1'
[INFO] [1667791883.591488073] [talker]: Publishing: 'Hello World: 2'
[INFO] [1667791884.595503927] [talker]: Publishing: 'Hello World: 3'
[INFO] [1667791885.591661410] [talker]: Publishing: 'Hello World: 4'
[INFO] [1667791886.593317378] [talker]: Publishing: 'Hello World: 5'
In another terminal session, run:
ros2 run demo_nodes_cpp listener
If everything worked as expected, you should see something like this:
[INFO] [1667791947.122910896] [listener]: I heard: [Hello World: 13]
[INFO] [1667791948.122430228] [listener]: I heard: [Hello World: 14]
[INFO] [1667791949.123189150] [listener]: I heard: [Hello World: 15]
[INFO] [1667791950.119661010] [listener]: I heard: [Hello World: 16]
[INFO] [1667791951.121229768] [listener]: I heard: [Hello World: 17]
The introductory ROS node is Turtlesim. To get started, simply run
ros2 run turtlesim turtlesim_node
If you are running this on your local machine, it should pop up a window that looks like this:
The command above starts a new node which manages the window. In another terminal, start another node which takes keyboard inputs and communicates with the associated topics and actions on the first node:
ros2 run turtlesim turtle_teleop_key
You can also directly call a topic by using the command below:
ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 1.0}, angular: {z: -3.1415926535}}"
If everything is working as expected, this should make the turtle spin around in circles on your screen.
Let’s write a new node to interact with the Turtlesim node. First, let’s create a new workspace somewhere:
mkdir ros-blog-post
cd ros-blog-post
git init .
Next, create a new package using the command:
ros2 pkg create --build-type ament_python custom_turtlesim
If you run the tree
command (if it’s not installed, see these instructions), you should get a directory structure that looks like this:
$ tree
.
└── custom_turtlesim
├── custom_turtlesim
│ └── __init__.py
├── package.xml
├── resource
│ └── custom_turtlesim
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
Next, create a new file in custom_turtlesim/custom_turtlesim/controller.py
and add the following:
import math
from typing import List, Optional
import rclpy
from geometry_msgs.msg import Twist
from rclpy.node import Node
class TurtlesimController(Node):
def __init__(self, run_every_n_seconds: float = 0.5, queue_size: int = 10) -> None:
"""Defines a simple controller for Turtlesim.
Args:
run_every_n_seconds: Run the publisher every N seconds
queue_size: The number of messages to queue up if the subscriber
is not receiving them fast enough; this is a quality-of-service
setting in ROS
"""
super().__init__(node_name="turtle_sim_controller")
self.turtle_pub = self.create_publisher(Twist, "/turtle1/cmd_vel", qos_profile=queue_size)
self.timer = self.create_timer(run_every_n_seconds, self.timer_callback)
def timer_callback(self) -> None:
"""Defines a callback which is called every time the timer spins."""
msg = Twist()
msg.linear.x = 1.0
msg.angular.z = math.pi
self.turtle_pub.publish(msg)
self.get_logger().info("Published a message")
def run_turtlesim_controller(args: Optional[List[str]] = None) -> None:
rclpy.init(args=args)
controller = TurtlesimController()
try:
rclpy.spin(controller)
finally:
controller.destroy_node()
rclpy.shutdown()
if __name__ == "__main__":
run_turtlesim_controller()
Next, let’s run the node that we’ve written.
There are two ways to run this. The simplest is to simply run the Python script directly:
# Run the script.
python custom_turtlesim/custom_turtlesim/controller.py
# Run as a module.
cd custom_turtlesim/
python -m custom_turtlesim.controller
This way you can do quick debugging without having to rebuild your entire package
When you’re done debugging your controller, you can build the whole package and add it to your environment.
Add the run_turtlesim_controller
function as an entry point in the file custom_turtlesim/setup.py
:
entry_points={
"console_scripts": [
"controller = custom_turtlesim.controller:run_turtlesim_controller",
],
},
From your root ros-blog-post/
directory, build the package by running:
colcon build
Now your directory tree should look something like this:
$ tree -L 1
.
├── build
├── custom_turtlesim
├── install
└── log
Install the newly-built package:
. install/setup.bash # If using bash
. install/setup.zsh # If using zsh
Finally, you can run your service using:
ros2 run custom_turtlesim controller
You can run the auto-generated tests using
colcon test
This part will require having two machines, which I’ll call the host and client. The host is going to be running the main Turtlesim node, while the client is going to run our custom controller. On the host, run
ros2 run turtlesim turtlesim_node
You should be able to see the standard view of the turtle. Next, in a different terminal, get the host hostname:
ROS_HOSTNAME=$(hostname)
$ hostname
benjamins-mini.lan
Next, from the client, make sure you can ping the host hostname (note that this requires being on the same network):
export ROS_HOSTNAME=<hostname-from-host>
ping $ROS_HOSTNAME
$ export ROS_HOSTNAME=ben-computer.lan
$ ping $ROS_HOSTNAME
PING host-hostname (192.168.0.1): 56 data bytes
64 bytes from 192.168.0.1: icmp_seq=0 ttl=64 time=136.229 ms
64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=17.090 ms
64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=18.387 ms
64 bytes from 192.168.0.1: icmp_seq=3 ttl=64 time=17.272 ms
If you were able to ping from the client to the host successfully, then after setting the $ROS_HOSTNAME
environment variable, you can just run the custom node:
python -m custom_turtlesim.controller
A package in ROS is a collection of code, much like a Python or C++ package.
To create a new Python package:
ros2 pkg create --build-type ament_python <package-name>
To create a new C++ package:
ros2 pkg create --build-type ament_cmake <package-name>
To prepopulate with a starter node, you can add the command line argument --node-name <node-name>
To list all currently-installed packages, run:
ros2 pkg list
package.xml
All ROS packages include a package.xml
file which contains meta information about the package. However, when using RoboStack as your dependency manager, you can more or less ignore it.
A node in ROS is a process that performs some computation - basically an executable. They communicate with each other using topics, services and actions. You can list running nodes using
ros2 node list
You can print out info about a particular running node using
ros2 node info <node-name>
turtlesim_node
$ ros2 node info /turtlesim
/turtlesim
Subscribers:
/parameter_events: rcl_interfaces/msg/ParameterEvent
/turtle1/cmd_vel: geometry_msgs/msg/Twist
Publishers:
/parameter_events: rcl_interfaces/msg/ParameterEvent
/rosout: rcl_interfaces/msg/Log
/turtle1/color_sensor: turtlesim/msg/Color
/turtle1/pose: turtlesim/msg/Pose
Service Servers:
/clear: std_srvs/srv/Empty
/kill: turtlesim/srv/Kill
/reset: std_srvs/srv/Empty
/spawn: turtlesim/srv/Spawn
/turtle1/set_pen: turtlesim/srv/SetPen
/turtle1/teleport_absolute: turtlesim/srv/TeleportAbsolute
/turtle1/teleport_relative: turtlesim/srv/TeleportRelative
/turtlesim/describe_parameters: rcl_interfaces/srv/DescribeParameters
/turtlesim/get_parameter_types: rcl_interfaces/srv/GetParameterTypes
/turtlesim/get_parameters: rcl_interfaces/srv/GetParameters
/turtlesim/list_parameters: rcl_interfaces/srv/ListParameters
/turtlesim/set_parameters: rcl_interfaces/srv/SetParameters
/turtlesim/set_parameters_atomically: rcl_interfaces/srv/SetParametersAtomically
Service Clients:
Action Servers:
/turtle1/rotate_absolute: turtlesim/action/RotateAbsolute
Action Clients:
The official documentation here covers this pretty well. Essentially:
rclcpp
and rclpy
A topic in ROS is a way for nodes to send messages to each other using publish/subscribe semantics. A node will publish a topic, which can then be subscribed to by other nodes. You can list available topics using:
ros2 topic list
To show the topic type, you can use:
ros2 topic list --show-types
ros2 topic list -t # Shorthand
turtlesim_node
$ ros2 topic list --show-types
/parameter_events [rcl_interfaces/msg/ParameterEvent]
/rosout [rcl_interfaces/msg/Log]
/turtle1/cmd_vel [geometry_msgs/msg/Twist]
/turtle1/color_sensor [turtlesim/msg/Color]
/turtle1/pose [turtlesim/msg/Pose]
To send a message to a topic from the command line, you can use the command:
ros2 topic pub <topic-name> <message-type>
Some useful command-line flags:
-r/--rate <rate>
publishes this many times per second-t/--times <times>
only runs this many timesturtlesim_node
ros2 topic pub -r 0.5 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -3.1415926535}}"
A message is just a type of topic, which tells ROS what kind of data is being passed around. This is so that data can be encoded and decoded correctly.
A service in ROS is a way for nodes to communicate with each other using call-and-response semantics. A node will send a request to another node, and receive a response in return. You can list available services using:
ros2 service list
turtlesim_node
$ ros2 service list
/clear
/kill
/reset
/spawn
/turtle1/set_pen
/turtle1/teleport_absolute
/turtle1/teleport_relative
/turtlesim/describe_parameters
/turtlesim/get_parameter_types
/turtlesim/get_parameters
/turtlesim/list_parameters
/turtlesim/set_parameters
/turtlesim/set_parameters_atomically
A bag in ROS is just a recording of a session.
A action in ROS has three parts:
Actions are essentially services which can be pre-empted. You can list available actions using:
ros2 action list
turtlesim_node
$ ros2 action list
/turtle1/rotate_absolute
run
ros2 run
is used to run a single executable (simply a node). This can be invoked using:
ros2 run <package-name> <node-name>
For example, to run Turtlesim, use:
ros2 run turtlesim turtlesim_node
This will then print the logged output from the running process.
Sometimes you might get an error message like this:
Package '<package-name>' not found
In this case, you can check if your package is installed by using:
ros2 pkg list | grep <package-name>
launch
ros2 launch
is used to run multiple nodes at once, as defined by a launch file. A launch file can be written in Python, XML or YAML. For example, you can create a launch file in Python that looks like this:
import launch_ros.actions
from launch import LaunchDescription
def generate_launch_description() -> LaunchDescription:
return LaunchDescription(
[
launch_ros.actions.Node(
namespace="turtlesim1",
package="turtlesim",
executable="turtlesim_node",
output="screen",
),
launch_ros.actions.Node(
namespace="turtlesim2",
package="turtlesim",
executable="turtlesim_node",
output="screen",
),
]
)
If you save this somewhere (for example, launch_turtlesim.py
) you can then run it using:
ros2 launch launch_turtlesim.py
In another terminal, you can then see two running Turtlesim nodes, each in their own respective namespace:
$ ros2 node list
/turtlesim1/turtlesim
/turtlesim2/turtlesim
colcon
Colcon is the command line tool that ROS uses for building and testing. To build a Colcon project, you can run:
colcon build
This looks at each subdirectory in whichever directory you’re in, checks if it is a ROS directory, and if so builds it. There are additional command-line options for choosing which subdirectories to include and ignore, such as:
--packages-select <pkg-1> (<pkg-2> ...)
Include these packages--package-skip <pkg-1> (<pkg-2> ...)
Skip these packagesTo run Colcon tests, you can use:
colcon test
After finishing, you can see any failures by running:
colcon test-result # To show the test result summary files
colcon test-result --verbose # To show all errors
ament
/ catkin
Ament and Catkin are build tools for ROS. Catkin was used for ROS 1, Ament has been used for ROS 2. Both have been superseded by Colcon (see here and here).
Gazebo is a simulator package which interacts nicely with ROS.
Here’s a reference ROS 2 command cheat sheet.
Base Command | Command | Description |
---|---|---|
action |
info |
Output information about an action |
list |
Output a list of action names | |
list -t |
List actions with types | |
send_goal |
Send an action goal | |
bag |
info |
Output information of a bag |
play |
Play a bag | |
record |
Record a bag | |
launch |
Run a launch file | |
node |
info |
Output information about a node |
list |
Output a list of available nodes | |
pkg |
create |
Create a new package |
executables |
Output a list of executables in a package | |
list |
Output a list of available packages | |
run |
Run an executable in a given package | |
service |
call |
Call a service manually |
find |
Output a list of services of a given type | |
list |
Output a list of service names | |
type |
Output a service’s type | |
test |
Run launch test | |
topic |
bw |
Show bandwidth usage for a topic |
delay |
Display delay of topic from timestamp in header | |
echo |
Output messages of a given topic | |
find |
Find topics of a given type | |
hz |
Get publishing rate of a topic | |
info |
Output information about a topic | |
list |
Output list of active topics | |
pub |
Publish data to a topic | |
type |
Output type of a topic |
Some other resources that I found helpful are listed below.