Detailed content
🧠 What ROS 2 is and why to use it
Up through Learning Path 3, the robot was one or two programs that you wrote in full. Once you add LiDAR, a map, a camera, and navigation, a single program becomes impossible to maintain. The ROS 2 (Robot Operating System 2) solves this: each task becomes a small program, and they all exchange data by name, without knowing who is on the other end or which computer they’re using. Despite its name, it isn’t an operating system: it runs on top of Linux as a set of libraries and tools.
🌱 New here?
Middleware is the software “in the middle”: it sits between your programs and the network and handles message delivery. In ROS 2, that role is handled by DDS (Data Distribution Service), an industry standard that automatically discovers who publishes and subscribes to each piece of data. A A package is a unit of code that you install or write: a LiDAR driver, Nav2, or your robot.
How to read this map: each amber box is an independent program (a node). None of them talks directly to another: they all connect to the cyan band. Navigation requests "/scan" and receives data from the LiDAR driver without knowing who it is. The dashed box is on another computer on the network and participates in the same way.
✓ Use ROS 2 when
- ✓Your robot has LiDAR, a map, or navigation.
- ✓More than one computer (Pi, PC, ESP32) is working together.
- ✓You want to simulate before building.
✗ You don’t need ROS 2 for
- ✗A line follower or obstacle-avoiding rover (Tracks 1 and 2).
- ✗An ESP32 on its own, without a nearby computer.
- ✗A weekend prototype with just one sensor.
Why learn this
ROS 2 is the common language of research robotics and much of the industry. Once you learn the basics, you can use sensor drivers, slam_toolbox for mapping, and Nav2 for navigation for free, instead of writing everything from scratch.
Key concepts
📅 Which distro: Lyrical or Jazzy
ROS 2 is released in distributions (distros), named after turtles, one per year. Even-numbered releases, launched in May, are LTS (long-term support, five years) and pair with an LTS version of Ubuntu. In October 2026, there are two good choices: Lyrical Luth, released on 22/05/2026 for Ubuntu 26.04 and supported until May 2031, and Jazzy Jalisco, the previous LTS, for Ubuntu 24.04, supported until 2029 and with more tutorials and ready-to-use packages.
How to read this map: each bar shows the period when the distro receives fixes. The cyan line marks today. Humble (dashed gray) ends in less than a year: don’t start with it. Jazzy and Lyrical will serve you for years; each comes with the matching Ubuntu and Gazebo versions.
| If you... | Choose |
|---|---|
| are starting from scratch today on a new PC | Lyrical (Ubuntu 26.04, Gazebo Jetty) |
| are following a tutorial, book, or robot that requires Jazzy | Jazzy (Ubuntu 24.04, Gazebo Harmonic) |
| need a package that hasn’t been released for Lyrical yet | Jazzy, or Lyrical in Docker for testing |
| have an old project on Humble | Plan to migrate to Jazzy before May/2027 |
🌱 New here?
In this course, commands use <distro> as a variable part: replace it with lyrical or jazzy, always in lowercase. Example: source /opt/ros/jazzy/setup.bash. The rest of the command is the same for both.
Why learn this
Most “it doesn’t work” problems in ROS come from mixing things up: a tutorial for one distro, a package from another, and Gazebo from a third. Choosing your distro at the start and sticking with the Ubuntu ↔ ROS ↔ Gazebo pairing helps you avoid this kind of issue.
Key concepts
📦 Install (Ubuntu, Docker, or Pi)
There are three ways. With Ubuntu (on a PC or virtual machine), you install ready-to-use packages and get the best performance in Gazebo. With Docker, you run a container that already includes ROS on any Linux system (and on Windows or macOS, with extra setup for graphical windows). On a Raspberry Pi 5, the straightforward option is to install the Ubuntu version that matches the distro; on Raspberry Pi OS, use Docker.
Step by step on Ubuntu
Check Ubuntu
Lyrical requires Ubuntu 26.04; Jazzy requires 24.04. Check with lsb_release -a.
Add the ROS repository
Follow the “Setup Sources” section on the official installation page for your distro at docs.ros.org. This step changes from time to time; copy the instructions from there, not from old tutorials.
Install and activate
Run the block below. The desktop package includes RViz2, examples, and tools; ros-dev-tools includes colcon and rosdep.
Goal: install ROS 2 and have it activated automatically in every new terminal.
# after setting up the repository using the official page
sudo apt update && sudo apt upgrade -y
sudo apt install -y ros-<distro>-desktop ros-dev-tools
# activates ROS in every new terminal
echo "source /opt/ros/<distro>/setup.bash" >> ~/.bashrc
source ~/.bashrc
# test: two example nodes, one in each terminal
ros2 run demo_nodes_cpp talker
ros2 run demo_nodes_py listener
How to check
- ✓The talker prints "Publishing: 'Hello World: 1'", "...: 2"; the listener prints "I heard: [Hello World: 1]".
- ✓printenv ROS_DISTRO shows lyrical or jazzy.
- ✗"ros2: command not found"? You missed source in this terminal.
Quick way with Docker: the official image osrf/ros:<distro>-desktop already includes everything. The DISPLAY options let the container open windows (RViz, turtlesim) on your Linux system. Check the available tags on Docker Hub before downloading.
# allows the container to use the display (run once per session)
xhost +local:docker
docker run -it --rm --net=host \
-e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
-v ~/ros2_ws:/root/ros2_ws \
osrf/ros:<distro>-desktop
# ROS is already activated inside the container:
ros2 run turtlesim turtlesim_node
How to check: opens a blue window with a turtle in the middle. The ~/ros2_ws folder on your PC appears inside the container at /root/ros2_ws, so the code you write won’t disappear when the container closes.
💡 What about Raspberry Pi 5?
Flash the Ubuntu Server version that matches the pair (26.04 for Lyrical, 24.04 for Jazzy) with Raspberry Pi Imager and install ros-<distro>-ros-base (without the graphical tools). Visualize on your PC: RViz and Gazebo are too demanding for the Pi. If you want to keep the Raspberry Pi OS from Trail 3, use the image ros:<distro>-ros-base in Docker.
Why learn this
Installing it correctly once saves hours later. Docker is also a good option for the classroom: all students run exactly the same image, with no differences between machines.
Key concepts
🔗 Nodes, topics, messages
A node is a process that does one thing: read the LiDAR, turn the motors, plan the route. Nodes communicate through topics, named channels such as /cmd_vel or /scan. The sender is the publisher; the receiver is the subscriber. A topic can have several on each side. The content has a fixed format; the message: /cmd_vel uses geometry_msgs/msg/Twist, with linear and angular velocity.
🌱 New here?
Twist is the "I want to move like this" message: linear.x in meters per second (forward) and angular.z in radians per second (turning; positive is counterclockwise when viewed from above). A differential-drive robot ignores the other four fields. QoS (quality of service) is the delivery agreement for each topic, such as "keep the last 10 messages".
How to read this map: rectangles are nodes (programs); cyan capsules are topics (channels). The arrow leaves the publisher and points to the subscriber. Notice that /scan has two subscribers: the same laser data feeds the map and the display without any extra code.
Goal: inspect a real graph using turtlesim. Open three terminals (with ROS activated in all of them).
# terminal 1: the turtle simulator
ros2 run turtlesim turtlesim_node
# terminal 2: drive using the keyboard arrow keys
ros2 run turtlesim turtle_teleop_key
# terminal 3: view the graph
ros2 node list
ros2 topic list -t
ros2 topic info /turtle1/cmd_vel
ros2 interface show geometry_msgs/msg/Twist
ros2 topic echo /turtle1/pose
# publish it yourself: the turtle moves in a circle
ros2 topic pub --rate 2 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 1.0}, angular: {z: 0.8}}"
How to check
- ✓ros2 node list terminal /turtlesim e /teleop_turtle.
- ✓topic info says "Publisher count: 1, Subscription count: 1" (2 publishers when topic pub is running).
- ✓O echo prints x, y, and theta changing as the turtle moves. To see the graph drawn out, run rqt_graph.
Why learn this
When the robot doesn't move, the first step is always the same: ros2 topic echo /cmd_vel. If the command arrives, the problem is with the motor; if it doesn't, the problem is with the publisher. Reading the graph from the terminal is the most-used diagnostic tool throughout this trail.
Key concepts
🔁 Services, actions, and parameters
A topic is a continuous stream with no response. ROS 2 has three more building blocks for other cases. A service is a quick request and response: "create a turtle here," "save the map." An action is a long-running task with progress updates and the option to cancel: "go to the kitchen" (all of Nav2 works this way). A parameter is a node setting you can change without recompiling: maximum speed, serial port name, background color.
How to read this map: the vertical lines represent the two sides of the conversation, and time flows downward. With a topic, A only sends. With a service, the client waits for a single response. With an action, the server sends progress updates (dashed line) while it works and then sends the result.
# service: create a second turtle
ros2 service list
ros2 service call /spawn turtlesim/srv/Spawn "{x: 2.0, y: 2.0, theta: 0.0, name: 'robo2'}"
# parameter: change the background color without restarting
ros2 param list /turtlesim
ros2 param set /turtlesim background_r 150
# action: rotate 90 degrees (1,57 rad) and show progress
ros2 action list
ros2 action send_goal --feedback /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}"
How to check: a new turtle appears at (2, 2); the background turns purple; the action terminal prints several lines of "Feedback: remaining: ..." and ends with "Goal finished with status: SUCCEEDED".
✓ The right pattern
- ✓Sensor readings and velocity: topic.
- ✓Switch on a relay, save the map: service.
- ✓Go to a point, rotate the arm: action.
✗ Common hack
- ✗A service that takes 30 s (blocks the caller).
- ✗A "/ready" topic to simulate a response.
- ✗A constant in the code that should be a parameter.
Why learn this
In module 4.5, you'll send the robot to a point using a Nav2 action, and in 4.4, you'll save the map using a service. Recognizing the pattern makes the documentation for these packages easier to read.
Key concepts
🐍 Your first Python node (rclpy), publishing and listening
Now you'll write your own nodes. In Python, the library is rclpy. Every node follows the same pattern: initialize rclpy, create a Node, register publishers, subscribers, and timers, then hand control to spin, which waits for events and calls your functions. You'll create a package called meu_robo with two nodes: speaker publishes a phrase twice per second; listener receives and prints it.
How to read this map: the speaker's timer calls publish(), which sends a String to the topic. The listener doesn't ask anything: ROS calls on_receive() every time a message arrives. This “you don't call it, you get called” is the callback.
Goal: create the workspace and the package. The ros2 pkg create generates the structure; then you just replace the files with the blocks below.
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python --license MIT meu_robo --dependencies rclpy std_msgs
# create the two nodes inside the package's code folder
touch meu_robo/meu_robo/falante.py meu_robo/meu_robo/ouvinte.py
# Node that publishes a sentence to the /conversa topic twice per second
import rclpy
from rclpy.node import Node
from rclpy.executors import ExternalShutdownException
from std_msgs.msg import String
class Falante(Node):
def __init__(self):
super().__init__('falante')
self.pub = self.create_publisher(String, 'conversa', 10) # queue of 10 messages
self.contador = 0
self.timer = self.create_timer(0.5, self.publicar) # calls publicar() every 0.5 s
def publicar(self):
msg = String()
msg.data = f'Hello from the robot #{self.contador}'
self.pub.publish(msg)
self.get_logger().info(f'Published: {msg.data}')
self.contador += 1
def main(args=None):
rclpy.init(args=args)
no = Falante()
try:
rclpy.spin(no)
except (KeyboardInterrupt, ExternalShutdownException):
pass
finally:
no.destroy_node()
rclpy.try_shutdown()
if __name__ == '__main__':
main()
# Node that subscribes to /conversa and prints each message it receives
import rclpy
from rclpy.node import Node
from rclpy.executors import ExternalShutdownException
from std_msgs.msg import String
class Ouvinte(Node):
def __init__(self):
super().__init__('ouvinte')
self.sub = self.create_subscription(String, 'conversa', self.ao_receber, 10)
def ao_receber(self, msg):
self.get_logger().info(f'Heard: {msg.data}')
def main(args=None):
rclpy.init(args=args)
no = Ouvinte()
try:
rclpy.spin(no)
except (KeyboardInterrupt, ExternalShutdownException):
pass
finally:
no.destroy_node()
rclpy.try_shutdown()
if __name__ == '__main__':
main()
O setup.py tells ROS which commands the package provides (the entry_points), e o package.xml lists the dependencies. Replace the two generated files with the minimal versions below.
from setuptools import find_packages, setup
package_name = 'meu_robo'
setup(
name=package_name,
version='0.1.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages', ['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='voce',
maintainer_email='voce@exemplo.com',
description='First robotics course nodes',
license='MIT',
entry_points={
'console_scripts': [
'falante = meu_robo.falante:main',
'ouvinte = meu_robo.ouvinte:main',
],
},
)
<?xml version="1.0"?>
<package format="3">
<name>meu_robo</name>
<version>0.1.0</version>
<description>Primeiros nos do curso de robotica</description>
<maintainer email="voce@exemplo.com">voce</maintainer>
<license>MIT</license>
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
cd ~/ros2_ws
colcon build --symlink-install
source install/setup.bash
ros2 run meu_robo falante # terminal 1
ros2 run meu_robo ouvinte # terminal 2 (source it again in this terminal)
How to check
- ✓colcon finishes with "Summary: 1 package finished".
- ✓Terminal 1: "[falante]: Published: Hello from the robot #0", "#1"... Terminal 2: "[ouvinte]: Heard: Hello from the robot #3".
- ✓In a third terminal, ros2 topic hz /conversa shows about 2 Hz.
- ✗"Package 'meu_robo' not found"? You forgot to source install/setup.bash in that terminal.
💡 Challenge
Change the speaker to publish geometry_msgs/msg/Twist to /turtle1/cmd_vel (add geometry_msgs to package.xml) and make the turtle draw a square: move for 2 s, turn 90 degrees, repeat. That's exactly what a navigation node does with your robot.
Why learn this
This template (Node class, publisher, subscriber, timer, spin) is the foundation of every node in the learning path: the one that calculates odometry in 4.3, the one that sends the robot to a point in 4.5. The message types and callback logic change; the structure stays the same.
Key concepts
✅ Module summary
To go further
- ROS 2 Lyrical documentation — official installation and tutorials
- ROS 2 Jazzy documentation — the same structure, for the previous LTS release
- Command-line tutorials — nodes, topics, services, actions, and parameters with turtlesim
- osrf/ros images on Docker Hub — check the tags for each distro
Next module
4.2 · Describe and simulate the robot: URDF, TF frames, RViz2, and the robot moving in Gazebo.