Skip to content
Back to Insights

How to Set Up ROS 2: A Practical Guide

TL;DR

Mohammed Cherifi
Published
6 min read

TL;DR

  • Install ROS 2 Jazzy on Ubuntu 24.04 with sudo apt install ros-jazzy-desktop
  • Core concepts: nodes (processes), topics (pub/sub), services (RPC), actions (long-running tasks)
  • Write a Python/C++ publisher/subscriber in 5 minutes
  • Use ros2 launch for multi-node orchestration
  • Debug with rqt, ros2 bag, and rviz2

## 1. Installing ROS 2 (Jazzy) on Ubuntu

Prerequisites

Step 1: Set up locale and repositories

sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
export LANG=en_US.UTF-8
sudo apt install software-properties-common
sudo add-apt-repository universe

Step 2: Add ROS 2 repository

sudo apt update && sudo apt install curl -y
curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
sudo apt update

Step 3: Install ROS 2 Jazzy

# Desktop install (recommended): ROS, RViz, demos, tutorials
sudo apt install ros-jazzy-desktop

# Base install (minimal): just ROS and command-line tools
# sudo apt install ros-jazzy-ros-base

# Development tools (optional)
sudo apt install ros-dev-tools

Step 4: Set up environment

# Source ROS 2 setup file (add to ~/.bashrc for persistence)
source /opt/ros/jazzy/setup.bash

# Verify installation
ros2 --version
# Expected output: ros2 version 26.0.0 (or higher)

Gotchas:

  • If ros2 --version fails, ensure /opt/ros/jazzy/setup.bash is sourced.
  • For Python packages, install python3-colcon-common-extensions if missing.

## 2. Nodes, Topics, Services, and Actions

Core Concepts

ConceptDescription<a href="/services/physical-ai-robotics">physical ai</a> Stack Mapping
NodeA process that performs computation (e.g., sensor driver, planner).COMPUTE
TopicAsynchronous pub/sub communication (e.g., /scan for LiDAR data).CONNECT
ServiceSynchronous request/response (e.g., /reset_pose for localization).REASON
ActionLong-running task with feedback (e.g., /navigate_to_pose for navigation).ACT

CLI Commands

# List nodes
ros2 node list

# List topics
ros2 topic list

# Echo a topic
ros2 topic echo /scan

# Call a service
ros2 service call /reset_pose std_srvs/srv/Empty

# List actions
ros2 action list

## 3. Writing Your First Publisher/Subscriber

Python Example

Publisher (publisher.py)

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalPublisher(Node):
    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello ROS 2: {self.i}'
        self.publisher_.publish(msg)
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.i += 1

def main(args=None):
    rclpy.init(args=args)
    minimal_publisher = MinimalPublisher()
    rclpy.spin(minimal_publisher)
    minimal_publisher.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Subscriber (subscriber.py)

#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalSubscriber(Node):
    def __init__(self):
        super().__init__('minimal_subscriber')
        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10)
        self.subscription  # prevent unused variable warning

    def listener_callback(self, msg):
        self.get_logger().info(f'I heard: "{msg.data}"')

def main(args=None):
    rclpy.init(args=args)
    minimal_subscriber = MinimalSubscriber()
    rclpy.spin(minimal_subscriber)
    minimal_subscriber.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Run the Example

# Terminal 1: Run publisher
python3 publisher.py

# Terminal 2: Run subscriber
python3 subscriber.py

# Expected output (Terminal 2):
# [INFO] [minimal_subscriber]: I heard: "Hello ROS 2: 0"
# [INFO] [minimal_subscriber]: I heard: "Hello ROS 2: 1"

C++ Example

Publisher (publisher.cpp)

#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node {
public:
  MinimalPublisher() : Node("minimal_publisher"), count_(0) {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

private:
  void timer_callback() {
    auto message = std_msgs::msg::String();
    message.data = "Hello ROS 2: " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;
};

int main(int argc, char * argv[]) {
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

Build and Run

# Create a workspace
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake cpp_pubsub

# Replace the generated src/cpp_pubsub/src/publisher_member_function.cpp with the code above
# Build
cd ~/ros2_ws
colcon build --packages-select cpp_pubsub

# Source the workspace
source install/setup.bash

# Run publisher
ros2 run cpp_pubsub talker

## 4. Launch Files and Parameters

Launch File Example (launch_example.py)

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='cpp_pubsub',
            executable='talker',
            name='talker_node',
            parameters=[{'use_sim_time': True}]
        ),
        Node(
            package='demo_nodes_py',
            executable='listener',
            name='listener_node'
        )
    ])

Run the Launch File

ros2 launch launch_example.py

Parameters

# List parameters
ros2 param list

# Get a parameter
ros2 param get /talker_node use_sim_time

# Set a parameter
ros2 param set /talker_node use_sim_time false

## 5. DDS, QoS, and Real-Time Considerations

QoS Profiles

ROS 2 uses Quality of Service (QoS) policies to configure communication reliability, durability, and latency.

PolicyOptionsUse Case
ReliabilityRELIABLE, BEST_EFFORTSensor data (BEST_EFFORT), commands (RELIABLE)
DurabilityVOLATILE, TRANSIENT_LOCALMap data (TRANSIENT_LOCAL), telemetry (VOLATILE)
HistoryKEEP_LAST(n), KEEP_ALLLimited buffer (KEEP_LAST), full replay (KEEP_ALL)

Example: Configuring QoS in Python

from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy

qos_profile = QoSProfile(
    reliability=ReliabilityPolicy.RELIABLE,
    history=HistoryPolicy.KEEP_LAST,
    depth=10
)

self.publisher = self.create_publisher(
    String,
    'topic',
    qos_profile
)

Real-Time Setup

  1. Use a real-time kernel:
    sudo apt install linux-image-rt-amd64
    
  2. Configure DDS for real-time:
    • Use rmw_connextdds or rmw_cyclonedds with real-time settings.
    • Example cyclonedds.xml:
      <CycloneDDS>
          <Domain>
              <General>
                  <NetworkInterfaceAddress>lo</NetworkInterfaceAddress>
                  <AllowMulticast>false</AllowMulticast>
              </General>
              <Tracing>
                  <Verbosity>warning</Verbosity>
              </Tracing>
          </Domain>
      </CycloneDDS>
      
  3. Set thread priorities:
    rclcpp::ExecutorOptions options;
    options.context = rclcpp::Context::make_shared();
    options.context->init(0, nullptr, rclcpp::InitOptions().use_real_time());
    rclcpp::executors::SingleThreadedExecutor executor(options);
    

## 6. ros2_control and Hardware Interfaces

Overview

ros2_control is a framework for real-time control of robot hardware. It separates hardware interfaces from control algorithms.

Example: URDF with ros2_control

<robot name="my_robot">
  <ros2_control name="MyRobotSystem" type="system">
    <hardware>
      <plugin>my_robot_hardware/MyRobotSystemHardware</plugin>
    </hardware>
    <joint name="joint1">
      <command_interface name="position"/>
      <state_interface name="position"/>
      <state_interface name="velocity"/>
    </joint>
  </ros2_control>
</robot>

Launch File for ros2_control

from launch import LaunchDescription
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
from launch.substitutions import PathJoinSubstitution

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='controller_manager',
            executable='ros2_control_node',
            parameters=[PathJoinSubstitution([
                FindPackageShare('my_robot_description'),
                'config',
                'my_robot_controllers.yaml'
            ])]
        ),
        Node(
            package='controller_manager',
            executable='spawner',
            arguments=['joint_state_broadcaster']
        ),
        Node(
            package='controller_manager',
            executable='spawner',
            arguments=['joint_trajectory_controller']
        )
    ])

## 7. Debugging with rqt, ros2 bag, and rviz2

rqt (GUI Tools)

# Launch rqt
rqt

# Common plugins:
# - Topic Monitor (/rqt_topic)
# - Service Caller (/rqt_service_caller)
# - Node Graph (/rqt_graph)

ros2 bag (Recording/Playback)

# Record topics
ros2 bag record /scan /odom

# Play back
ros2 bag play rosbag2_2026_06_02-12_00_00

# Info
ros2 bag info rosbag2_2026_06_02-12_00_00

rviz2 (Visualization)

# Launch RViz2
rviz2

AI Disclosure: This article was generated with AI assistance and has NOT been individually reviewed by a human editor. AI tools were used in research, drafting and editing. Treat specifics as a starting point and verify anything you intend to rely on.

Sources

  1. https://docs.ros.org
  2. ROS 2 Jazzy requirements
Share:
Weekly AI Insights

The AI Dispatch

From demo to dependable operation. Get a weekly decision note for Physical AI products.

Unsubscribe anytime. No spam, ever.

Does this expose a product decision?

Bring the decision, deadline and evidence you have. The contact brief will route you to the smallest useful next step.

Discuss the product decision