Akshay Parkhi's Weblog

ROS 2 Humble: Complete Installation Guide with Turtlesim from Zero to First Node

2nd March 2026

This is a complete walkthrough for installing ROS 2 Humble on Ubuntu 22.04 and running your first robot simulation with Turtlesim. I wrote this after I completed the process myself. The official docs are thorough but scattered across many pages. This puts everything in one place, from locale setup to writing your first Python node.

What is ROS 2?

ROS 2 (Robot Operating System 2) is not an operating system. It’s an open-source robotics middleware framework. In other words, it is a set of libraries and tools that help you build robot applications.

The core concepts:

  • Nodes — Independent processes that perform computation
  • Topics — Named buses for nodes to exchange messages (publish/subscribe)
  • Services — Request/response communication between nodes
  • Actions — Long-running tasks with feedback
  • DDS — The underlying middleware (Data Distribution Service) that handles communication

ROS 2 Humble Hawksbill is a Long-Term Support (LTS) release targeting Ubuntu 22.04 (Jammy Jellyfish) with support until May 2027.

Prerequisites

RequirementDetails
OSUbuntu 22.04 LTS (native or WSL2 on Windows 11)
Architectureamd64 (x86_64)
Disk Space~3-5 GB for ros-humble-desktop
InternetRequired for downloading packages

Verify your OS version:

cat /etc/os-release
# Expected:
# PRETTY_NAME="Ubuntu 22.04.5 LTS"
# VERSION_CODENAME=jammy

If you’re on WSL2, Windows 11 includes WSLg, which provides GUI support with no extra configuration. Turtlesim’s graphical window requires this GUI support.

Step 1 — Locale Configuration

ROS 2 requires a UTF-8 locale for proper string handling across all tools.

# Check current locale
locale

# Install and configure UTF-8 locale
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

# Verify
locale

You should see LANG=en_US.UTF-8 in the output. ROS 2 internally uses UTF-8 for message serialization and logging. A misconfigured locale can cause unexpected encoding errors in node communication.

Step 2 — Enable Universe Repository

Ubuntu’s universe repository contains community-maintained packages that ROS 2 depends on.

sudo apt install software-properties-common
sudo add-apt-repository universe

Step 3 — Add the ROS 2 GPG Key

This lets apt verify that ROS 2 packages are authentic.

sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
  -o /usr/share/keyrings/ros-archive-keyring.gpg

The -sSL flags mean: silent, show errors, follow redirects. The command stores the key in /usr/share/keyrings/ so that apt can verify packages securely.

Step 4 — Configure the ROS 2 Source Repository

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

What each part does:

  • arch=$(dpkg --print-architecture) — detects your architecture (e.g., amd64)
  • signed-by=... — points to the GPG key we just downloaded
  • $(. /etc/os-release && echo $UBUNTU_CODENAME) — resolves to jammy on Ubuntu 22.04

Verify that the command created the file:

cat /etc/apt/sources.list.d/ros2.list
# Expected:
# deb [arch=amd64 signed-by=/usr/share/keyrings/ros-archive-keyring.gpg]
#   http://packages.ros.org/ros2/ubuntu jammy main

Step 5 — Update & Upgrade

sudo apt update
sudo apt upgrade -y

After apt update, you should see new entries from packages.ros.org. This shows that apt can now see ROS 2 packages.

Step 6 — Install ROS 2 Humble Desktop

This is the main installation step:

sudo apt install ros-humble-desktop -y

What ros-humble-desktop includes:

ComponentDescription
ros-humble-ros-baseCore ROS 2 libraries, CLI tools, communication layer
rclcpp / rclpyClient libraries for C++ and Python
rviz23D visualization tool
rqtGUI plugin framework
turtlesim2D turtle simulator
DDS middlewareFastDDS (default) for node communication

This downloads approximately 500+ packages (~2-3 GB). For lighter installs:

# Minimal install (no GUI tools)
sudo apt install ros-humble-ros-base

# Bare bones (just libraries)
sudo apt install ros-humble-ros-core

Step 7 — Environment Setup

ROS 2 requires sourcing a setup script to configure environment variables (ROS_DISTRO, AMENT_PREFIX_PATH, PYTHONPATH, etc.).

# Source for the current terminal
source /opt/ros/humble/setup.bash

# Add to .bashrc so every new terminal is ready automatically
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

# Verify
echo $ROS_DISTRO
# Expected: humble

This sets ~15 environment variables that tell ROS 2 where to find packages, libraries, executables, and Python modules. Without it, ros2 commands won’t work.

Step 8 — Verify Installation

ros2

If you see a list of available commands (action, bag, node, topic, service, etc.), ROS 2 Humble is installed and working.

Testing with Turtlesim

Turtlesim is a lightweight 2D simulator. It is the “Hello World” of ROS 2. It teaches core concepts: nodes, topics, services, and actions.

# Install (included in ros-humble-desktop, but just in case)
sudo apt install ros-humble-turtlesim

# Check available executables
ros2 pkg executables turtlesim
ExecutableDescription
turtlesim_nodeThe simulator GUI window
turtle_teleop_keyKeyboard controller (arrow keys)
draw_squareMoves turtle in a square pattern automatically
mimicMirrors one turtle’s movement onto another

Launch the Simulator

Terminal 1 — Start the simulator:

source /opt/ros/humble/setup.bash
ros2 run turtlesim turtlesim_node

# Output:
# [INFO] [turtlesim]: Starting turtlesim with node name /turtlesim
# [INFO] [turtlesim]: Spawning turtle [turtle1] at x=[5.54], y=[5.54], theta=[0.00]

A window appears with a blue background and a turtle in the center.

Control the Turtle with Keyboard

Terminal 2 — Start the keyboard controller:

source /opt/ros/humble/setup.bash
ros2 run turtlesim turtle_teleop_key
KeyAction
Up ArrowMove forward
Down ArrowMove backward
Left ArrowRotate counter-clockwise
Right ArrowRotate clockwise

The turtle draws a path as it moves. This is the publish/subscribe pattern in action:

[turtle_teleop_key] ──publishes──▶ /turtle1/cmd_vel ──subscribes──▶ [turtlesim_node]
   (your keyboard)                  (ROS 2 topic)                    (moves turtle)

Exploring ROS 2 CLI Tools

With turtlesim running, open a third terminal and explore:

List Active Nodes and Topics

# Active nodes
ros2 node list
# /turtlesim

# Active topics
ros2 topic list
# /parameter_events
# /rosout
# /turtle1/cmd_vel
# /turtle1/color_sensor
# /turtle1/pose
TopicDescription
/turtle1/cmd_velVelocity commands (Twist messages)
/turtle1/poseTurtle’s current position and orientation
/turtle1/color_sensorColor of the pixel under the turtle
/parameter_eventsParameter change notifications
/rosoutLogging output from all nodes

Watch Live Messages

# See live velocity commands (press arrow keys in the teleop terminal)
ros2 topic echo /turtle1/cmd_vel

# Output when pressing Up:
# linear:
#   x: 2.0
#   y: 0.0
#   z: 0.0
# angular:
#   x: 0.0
#   y: 0.0
#   z: 0.0

Inspect Topic and Message Structure

# Topic metadata
ros2 topic info /turtle1/cmd_vel
# Type: geometry_msgs/msg/Twist
# Publisher count: 1
# Subscription count: 1

# Message structure
ros2 interface show geometry_msgs/msg/Twist
# Vector3  linear
#         float64 x
#         float64 y
#         float64 z
# Vector3  angular
#         float64 x
#         float64 y
#         float64 z

How the Twist message controls the turtle: linear.x is the forward/backward speed, and angular.z is the rotation speed. The other fields are unused in 2D.

List Services and Actions

# Available services
ros2 service list
# /clear
# /kill
# /reset
# /spawn
# /turtle1/set_pen
# /turtle1/teleport_absolute
# /turtle1/teleport_relative

# Available actions
ros2 action list
# /turtle1/rotate_absolute

Advanced Turtlesim: Services, Spawning & More

Spawn a Second Turtle

ros2 service call /spawn turtlesim/srv/Spawn \
  "{x: 1.0, y: 1.0, theta: 0.0, name: 'turtle2'}"

# A second turtle appears. New topics are automatically created:
# /turtle2/cmd_vel
# /turtle2/color_sensor
# /turtle2/pose

Control the Second Turtle (Topic Remapping)

By default, turtle_teleop_key publishes to /turtle1/cmd_vel. Use --remap to control turtle2:

ros2 run turtlesim turtle_teleop_key \
  --ros-args --remap turtle1/cmd_vel:=turtle2/cmd_vel

Teleport, Clear, Kill, Reset

# Teleport to exact position
ros2 service call /turtle1/teleport_absolute \
  turtlesim/srv/TeleportAbsolute "{x: 8.0, y: 8.0, theta: 1.57}"

# Teleport relative to current position
ros2 service call /turtle1/teleport_relative \
  turtlesim/srv/TeleportRelative "{linear: 2.0, angular: 0.5}"

# Clear the drawing
ros2 service call /clear std_srvs/srv/Empty

# Kill a turtle
ros2 service call /kill turtlesim/srv/Kill "{name: 'turtle2'}"

# Reset everything
ros2 service call /reset std_srvs/srv/Empty

Publish Velocity from Command Line

# Continuous circle (no teleop node needed)
ros2 topic pub /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: 1.0}}"

# Single movement (one-shot)
ros2 topic pub --once /turtle1/cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 1.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

Using rqt (GUI Tool)

sudo apt install '~nros-humble-rqt*'
rqt

In the rqt window:

  1. Go to Plugins > Services > Service Caller.
  2. Select /spawn from the dropdown.
  3. Enter the parameters.
  4. Click Call.

rqt is a visual interface for calling services, viewing topics, and plotting data. You do not need to memorize CLI commands.

Controlling the Turtle Programmatically (Python)

Circle Movement

Create turtle_circle.py:

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist

class TurtleCircle(Node):
    def __init__(self):
        super().__init__('turtle_circle')
        self.publisher = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
        self.timer = self.create_timer(0.1, self.move)  # 10 Hz
        self.get_logger().info('Turtle circle controller started')

    def move(self):
        msg = Twist()
        msg.linear.x = 2.0    # forward speed
        msg.angular.z = 1.0   # rotation speed
        self.publisher.publish(msg)

def main():
    rclpy.init()
    node = TurtleCircle()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Run it:

source /opt/ros/humble/setup.bash
python3 turtle_circle.py

The turtle drives in a continuous circle. This shows the basic pattern:

  • Create a node.
  • Create a publisher.
  • Use a timer to publish messages at a fixed rate.

Square Movement

Create turtle_square.py:

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import time

class TurtleSquare(Node):
    def __init__(self):
        super().__init__('turtle_square')
        self.publisher = self.create_publisher(Twist, '/turtle1/cmd_vel', 10)
        self.get_logger().info('Drawing a square...')
        self.draw_square()

    def publish_for_duration(self, msg, duration):
        start = time.time()
        while time.time() - start < duration:
            self.publisher.publish(msg)
            time.sleep(0.1)

    def draw_square(self):
        for i in range(4):
            # Move forward
            forward = Twist()
            forward.linear.x = 2.0
            self.publish_for_duration(forward, 2.0)

            # Stop briefly
            stop = Twist()
            self.publish_for_duration(stop, 0.5)

            # Turn 90 degrees
            turn = Twist()
            turn.angular.z = 1.5708  # ~pi/2
            self.publish_for_duration(turn, 1.0)

            # Stop briefly
            self.publish_for_duration(stop, 0.5)

        self.get_logger().info('Square complete!')

Subscribe to Turtle’s Pose

Create turtle_listener.py:

import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose

class TurtleListener(Node):
    def __init__(self):
        super().__init__('turtle_listener')
        self.subscription = self.create_subscription(
            Pose, '/turtle1/pose', self.pose_callback, 10
        )
        self.get_logger().info('Listening to turtle1 pose...')

    def pose_callback(self, msg):
        self.get_logger().info(
            f'Position: ({msg.x:.2f}, {msg.y:.2f}) | '
            f'Theta: {msg.theta:.2f} | '
            f'Speed: linear={msg.linear_velocity:.2f}, '
            f'angular={msg.angular_velocity:.2f}'
        )

def main():
    rclpy.init()
    node = TurtleListener()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Understanding the Architecture

The ROS 2 Communication Stack

┌─────────────────────────────────────────────────┐
│              Your Application Node               │
│         (Python: rclpy / C++: rclcpp)            │
├─────────────────────────────────────────────────┤
│              ROS 2 Client Library                 │
│        (rcl — ROS Client Library in C)           │
├─────────────────────────────────────────────────┤
│              ROS 2 Middleware (rmw)               │
│          (Abstract middleware interface)          │
├─────────────────────────────────────────────────┤
│              DDS Implementation                  │
│       (FastDDS / CycloneDDS / Connext DDS)       │
├─────────────────────────────────────────────────┤
│              UDP/TCP Transport                    │
└─────────────────────────────────────────────────┘

How Turtlesim Nodes Communicate

Terminal 1: turtlesim_node          Terminal 2: turtle_teleop_key
┌──────────────────────┐            ┌──────────────────────┐
│  /turtlesim node     │            │  /teleop_turtle node │
│                      │            │                      │
│  Subscribes to:      │            │  Publishes to:       │
│  /turtle1/cmd_vel    │◄───DDS────│  /turtle1/cmd_vel    │
│                      │            │                      │
│  Publishes:          │            │  Reads:              │
│  /turtle1/pose       │            │  Keyboard input      │
│  /turtle1/color_sensor│           │                      │
│                      │            └──────────────────────┘
│  Services:           │
│  /spawn, /kill, etc  │            Terminal 3: CLI tools
│                      │            ┌──────────────────────┐
│  Actions:            │            │  ros2 topic echo     │
│  /turtle1/rotate_abs │            │  ros2 service call   │
└──────────────────────┘            │  rqt                 │
                                    └──────────────────────┘

Key Patterns Demonstrated

PatternExample in Turtlesim
Publish/Subscribeteleop_key publishes Twist → turtlesim_node subscribes
Services/spawn — request to create a turtle, response with name
Actions/turtle1/rotate_absolute — long-running rotation with feedback
ParametersBackground color, default pen settings
Remapping--remap turtle1/cmd_vel:=turtle2/cmd_vel
Multi-nodeMultiple turtles, each with independent topics

Troubleshooting

“ros2: command not found”

source /opt/ros/humble/setup.bash
# Add to .bashrc to make it permanent:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

Turtlesim Window Doesn’t Appear (WSL2)

# Test WSLg with a simple GUI app
sudo apt install x11-apps -y
xclock

If xclock doesn’t appear:

  1. Update to Windows 11 (22000+).
  2. Run wsl --update in PowerShell.
  3. Restart WSL with wsl --shutdown.

“Failed to get old console mode” in turtle_teleop_key

This happens when you run it in a non-interactive shell (a script or a background process). Run it directly in an interactive terminal.

Can’t Control the Right Turtle

# Use topic remapping to control turtle2
ros2 run turtlesim turtle_teleop_key \
  --ros-args --remap turtle1/cmd_vel:=turtle2/cmd_vel

Quick Reference Card

# === SETUP ===
source /opt/ros/humble/setup.bash

# === RUN TURTLESIM ===
ros2 run turtlesim turtlesim_node        # Start simulator
ros2 run turtlesim turtle_teleop_key     # Keyboard control

# === INSPECT ===
ros2 node list                            # Active nodes
ros2 topic list                           # Active topics
ros2 service list                         # Available services
ros2 action list                          # Available actions
ros2 topic echo /turtle1/cmd_vel          # Live message stream
ros2 topic info /turtle1/cmd_vel          # Topic metadata
ros2 interface show geometry_msgs/msg/Twist  # Message structure

# === CONTROL ===
ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 2.0}, angular: {z: 1.0}}"

# === SERVICES ===
ros2 service call /spawn turtlesim/srv/Spawn \
  "{x: 2.0, y: 2.0, theta: 0.0, name: 'turtle2'}"
ros2 service call /clear std_srvs/srv/Empty
ros2 service call /reset std_srvs/srv/Empty
ros2 service call /kill turtlesim/srv/Kill "{name: 'turtle2'}"

# === GUI ===
rqt

What’s Next?

After you master turtlesim, the natural progression is:

  1. Understanding Nodes — node lifecycle in detail
  2. Understanding Topics — publisher/subscriber in detail
  3. Understanding Services — request/response patterns
  4. Understanding Actions — long-running tasks with feedback
  5. Creating a Package — build your own ROS 2 package
  6. Writing a Publisher/Subscriber (Python) — your first custom node
  7. Gazebo — graduate to 3D robot simulation

The official ROS 2 Humble tutorials cover all of these. The Turtlesim exercise may seem trivial. But it demonstrates pub/sub, services, actions, remapping, and multi-node communication. Every one of these patterns is exactly what you’ll use when you control a real robot arm or run a fleet of autonomous agents.

More recent articles

This is ROS 2 Humble: Complete Installation Guide with Turtlesim from Zero to First Node by Akshay Parkhi, posted on 2nd March 2026.

Next: XR-Robotics with Pico 4 Ultra: VR Teleoperation Setup from Headset to Robot Simulation

Previous: RDF, ROS, and Sim-to-Real: Understanding Robot Description Files