PTENES
MODULE 4.1 · FOUNDATION

🧩 ROS 2 without fear

ROS 2 seems huge because it comes with so much ready-made. To get started, you only need five ideas: node, topic, message, service, and action. In this module, you’ll choose a distro, install it (or run it in Docker), inspect the graph from the terminal, and write your first Python node.

6
Topics
Intermediate
Level
Foundation
Type
PC only
Hardware required
0%
0 of 0 topics read · Section 1 of 6

Detailed content

1

🧠 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.

DDS: delivers messages by name LiDAR driverpublishes /scan camerapublishes /image navigationsubscribes to /scan base (ESP32)subscribes to /cmd_vel RViz on PCanother computer

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

Middleware
delivers messages
DDS
discovers who’s speaking
ROS graph
nodes + connections
Package
unit of code
2

📅 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.

202220242026202820302032 today (Oct/2026) Humble · ends May/2027 Jazzy · Ubuntu 24.04 · Harmonic Lyrical · Ubuntu 26.04 · Jetty

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 PCLyrical (Ubuntu 26.04, Gazebo Jetty)
are following a tutorial, book, or robot that requires JazzyJazzy (Ubuntu 24.04, Gazebo Harmonic)
need a package that hasn’t been released for Lyrical yetJazzy, or Lyrical in Docker for testing
have an old project on HumblePlan 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

Distro
named ROS version
LTS
five years of support
Official pairing
Ubuntu ↔ ROS ↔ Gazebo
<distro>
lyrical or jazzy
3

📦 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

1

Check Ubuntu

Lyrical requires Ubuntu 26.04; Jazzy requires 24.04. Check with lsb_release -a.

2

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.

3

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.

terminal (Ubuntu)
# 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.

terminal (Docker on Linux)
# 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

desktop
ROS + graphical tools
ros-base
lightweight, for the robot
source
activates ROS in the terminal
Container
same environment on every PC
4

🔗 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".

teleop /cmd_vel base lidar /scan slam rviz2 publishessubscribes Twist: linear.x, angular.z

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
# 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

Node
one process, one task
Topic
named channel
Message
data format
topic echo
see what's being sent
5

🔁 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.

topic service action ABclientserverclientserver no response requestresponsequick, one-time goalprogressresultlong-running, cancellable

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.

terminal (with turtlesim open)
# 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

Service
request → response
Action
goal, progress, result
Parameter
setting without recompiling
.srv / .action
interface formats
6

🐍 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.

speaker0.5 s timerpublish()create_publisher /conversastd_msgs/String listenercreate_subscriptionon_receive(msg)prints to the log spin() keeps both nodes alive, waiting for events

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.

terminal
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
meu_robo/meu_robo/falante.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()
meu_robo/meu_robo/ouvinte.py
# 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.

meu_robo/setup.py
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',
        ],
    },
)
meu_robo/package.xml
<?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>
terminal
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

rclpy
ROS 2 in Python
Callback
ROS calls you
entry_points
ros2 run command name
colcon build
builds and installs

✅ Module summary

✓
ROS 2 is a graph of small programs that exchange data by name, via DDS, on one or more computers.
✓
Choose Lyrical to start from scratch, or Jazzy if your materials call for it; skip Humble because it reaches end of support in May 2027.
✓
Installation: install from the repository using the official page, ros-<distro>-desktop, or use Docker osrf/ros:<distro>-desktop.
✓
Topic for a stream, service for a question, action for a long-running task, parameter for a setting.
✓
Node in rclpy: Node + publisher/subscriber + timer + spin, packaged with setup.py and built with colcon.

To go further

Next module

4.2 · Describe and simulate the robot: URDF, TF frames, RViz2, and the robot moving in Gazebo.