Akshay Parkhi's Weblog

Subscribe

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 getting your first robot simulation running with Turtlesim. I wrote this after going through 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 — 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

Check 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 out of the box — required for Turtlesim’s graphical window.

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 the authenticity of ROS 2 packages.

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 key is stored in /usr/share/keyrings/ for secure apt verification.

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

Breaking this down:

  • 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 it was created:

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 confirms 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’s included in ros-humble-desktop:

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 up ~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 — 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 forward/backward speed, angular.z is 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, go to Plugins > Services > Service Caller, select /spawn from the dropdown, fill in parameters, and click Call. A visual interface for calling services, viewing topics, and plotting data — without memorizing 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: update to Windows 11 (22000+), run wsl --update in PowerShell, then restart WSL with wsl --shutdown.

“Failed to get old console mode” in turtle_teleop_key

This happens when running in a non-interactive shell (script or 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 mastering turtlesim, the natural progression is:

  1. Understanding Nodes — deep dive into node lifecycle
  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 every pattern it demonstrates — pub/sub, services, actions, remapping, multi-node communication — is exactly what you’ll use when controlling a real robot arm or running a fleet of autonomous agents.

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