Chapter 5: Hands-on - First ROS 2 Package
⏱️ Time Estimate: 45 minutes
Prerequisites
Before starting this hands-on exercise, ensure you have:
- Ubuntu 22.04 LTS installed (or WSL2/VM)
- ROS 2 Humble Hawksbill installed
- Python 3.8 or higher
- Completed Modules 1.1 through 1.4
- Basic command line familiarity
Learning Objectives
By the end of this chapter, you will:
- Create your first ROS 2 workspace and package
- Implement a publisher and subscriber pair
- Run nodes and verify communication
- Use ROS 2 command-line tools for introspection
- Understand the complete workflow for ROS 2 development
Introduction
This hands-on chapter will guide you through creating your first complete ROS 2 package with a publisher and subscriber. You'll build, run, and verify communication between two nodes, giving you practical experience with ROS 2's publish-subscribe communication model.
Step 1: Create a ROS 2 Workspace
First, let's create a workspace directory for our ROS 2 packages:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
The src directory is where you'll place all your source code. The workspace structure is:
~/ros2_ws/- Root workspace directory~/ros2_ws/src/- Source code directory~/ros2_ws/build/- Build artifacts (created after building)~/ros2_ws/install/- Installation directory (created after building)~/ros2_ws/log/- Log files (created during building)
Step 2: Create a Package
Now create a new ROS 2 package named robobook_examples:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python robobook_examples
This command creates a basic Python package structure. Navigate to the package directory:
cd robobook_examples
ls -la
You should see the package structure including setup.py, package.xml, and robobook_examples/ directory.
Step 3: Create Python Nodes
Create a directory for your Python scripts:
mkdir -p robobook_examples
Now copy the publisher code to robobook_examples/simple_publisher.py:
#!/usr/bin/env python3
"""
Simple ROS 2 Publisher Example
This node publishes a "Hello World" message to a topic every 0.5 seconds.
MIT License
Copyright (c) 2025 RoboBook
"""
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimplePublisher(Node):
"""
A simple ROS 2 publisher node that sends messages to a topic.
"""
def __init__(self):
"""
Initialize the node with a name and create publisher.
"""
super().__init__('simple_publisher')
# Create a publisher for String messages on the 'chatter' topic
self.publisher_ = self.create_publisher(String, 'chatter', 10)
# Create a timer to publish messages every 0.5 seconds
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
# Counter for message numbering
self.i = 0
# Log that the publisher has started
self.get_logger().info('Simple Publisher node initialized')
def timer_callback(self):
"""
Callback function that runs every timer tick.
Creates and publishes a message.
"""
# Create a String message
msg = String()
msg.data = f'Hello World: {self.i}'
# Publish the message
self.publisher_.publish(msg)
# Log the published message
self.get_logger().info(f'Publishing: "{msg.data}"')
# Increment the counter
self.i += 1
def main(args=None):
"""
Main function to initialize and run the publisher node.
"""
# Initialize ROS 2
rclpy.init(args=args)
# Create the publisher node
simple_publisher = SimplePublisher()
try:
# Keep the node running until interrupted
rclpy.spin(simple_publisher)
except KeyboardInterrupt:
simple_publisher.get_logger().info('Keyboard interrupt received, shutting down')
finally:
# Clean up
simple_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Now create the subscriber code in robobook_examples/simple_subscriber.py:
#!/usr/bin/env python3
"""
Simple ROS 2 Subscriber Example
This node subscribes to a topic and logs received messages.
MIT License
Copyright (c) 2025 RoboBook
"""
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class SimpleSubscriber(Node):
"""
A simple ROS 2 subscriber node that listens to messages from a topic.
"""
def __init__(self):
"""
Initialize the node with a name and create subscriber.
"""
super().__init__('simple_subscriber')
# Create a subscriber for String messages on the 'chatter' topic
self.subscription = self.create_subscription(
String,
'chatter',
self.listener_callback,
10 # QoS history depth
)
# Prevent unused variable warning
self.subscription # type: ignore
# Log that the subscriber has started
self.get_logger().info('Simple Subscriber node initialized')
def listener_callback(self, msg):
"""
Callback function that runs when a message is received.
Args:
msg: The received String message
"""
# Log the received message
self.get_logger().info(f'I heard: "{msg.data}"')
def main(args=None):
"""
Main function to initialize and run the subscriber node.
"""
# Initialize ROS 2
rclpy.init(args=args)
# Create the subscriber node
simple_subscriber = SimpleSubscriber()
try:
# Keep the node running until interrupted
rclpy.spin(simple_subscriber)
except KeyboardInterrupt:
simple_subscriber.get_logger().info('Keyboard interrupt received, shutting down')
finally:
# Clean up
simple_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Step 4: Update Package Setup
Now you need to make your Python scripts executable and update the setup files. First, make them executable:
chmod +x robobook_examples/simple_publisher.py
chmod +x robobook_examples/simple_subscriber.py
Next, update the setup.py file in the package root to include your scripts:
from setuptools import find_packages, setup
package_name = 'robobook_examples'
setup(
name=package_name,
version='0.0.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='robobook',
maintainer_email='robobook@todo.todo',
description='RoboBook example package',
license='MIT',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'simple_publisher = robobook_examples.simple_publisher:main',
'simple_subscriber = robobook_examples.simple_subscriber:main',
],
},
)
Step 5: Build the Package
Now build your workspace:
cd ~/ros2_ws
source /opt/ros/humble/setup.bash
colcon build --packages-select robobook_examples
If you get an error about colcon, install it:
sudo apt update
sudo apt install python3-colcon-common-extensions
Then run the build command again.
Step 6: Source the Workspace
After building, source your workspace to make the nodes available:
source ~/ros2_ws/install/setup.bash
Step 7: Run the Publisher and Subscriber
Open two terminal windows or tabs.
In the first terminal, run the publisher:
source ~/ros2_ws/install/setup.bash
ros2 run robobook_examples simple_publisher
In the second terminal, run the subscriber:
source ~/ros2_ws/install/setup.bash
ros2 run robobook_examples simple_subscriber
You should see the publisher sending messages and the subscriber receiving them!
Step 8: Use ROS 2 Command-Line Tools
While your nodes are running, open a third terminal and try these commands:
List active nodes:
ros2 node list
Check node information:
ros2 node info /simple_publisher
ros2 node info /simple_subscriber
List active topics:
ros2 topic list
Check topic information:
ros2 topic info /chatter
Monitor messages on the topic:
ros2 topic echo /chatter
Expected Output
Publisher Terminal:
[INFO] [1699123456.789] [simple_publisher]: Simple Publisher node initialized
[INFO] [1699123457.289] [simple_publisher]: Publishing: "Hello World: 0"
[INFO] [1699123457.789] [simple_publisher]: Publishing: "Hello World: 1"
[INFO] [1699123458.289] [simple_publisher]: Publishing: "Hello World: 2"
...
Subscriber Terminal:
[INFO] [1699123457.290] [simple_subscriber]: Simple Subscriber node initialized
[INFO] [1699123457.290] [simple_subscriber]: I heard: "Hello World: 0"
[INFO] [1699123457.789] [simple_subscriber]: I heard: "Hello World: 1"
[INFO] [1699123458.289] [simple_subscriber]: I heard: "Hello World: 2"
...
Troubleshooting
Common Issues and Solutions
-
"command 'colcon' not found"
- Install colcon:
sudo apt install python3-colcon-common-extensions
- Install colcon:
-
"ModuleNotFoundError: No module named 'rclpy'"
- Ensure ROS 2 is sourced:
source /opt/ros/humble/setup.bash
- Ensure ROS 2 is sourced:
-
Nodes don't see each other
- Check that both terminals have the workspace sourced
- Verify both nodes have unique names
- Check ROS_DOMAIN_ID:
echo $ROS_DOMAIN_ID(should be same in both terminals)
-
"ament_python" build type error
- Install ament:
sudo apt install python3-ament-build
- Install ament:
Verification Steps
-
Check if both nodes are running:
ros2 node list -
Verify topic communication:
ros2 topic echo /chatter -
Check topic statistics:
ros2 topic hz /chatter
Assessment Checklist
✅ I can create a ROS 2 workspace and package ✅ I can implement a publisher node that sends messages ✅ I can implement a subscriber node that receives messages ✅ I can build my ROS 2 package successfully ✅ I can run publisher and subscriber nodes simultaneously ✅ I can verify communication between nodes using command-line tools ✅ I can troubleshoot basic ROS 2 communication issues
Knowledge Check
Complete these knowledge check questions to verify your understanding:
-
Multiple Choice: What command is used to create a new ROS 2 package?
- A)
ros2 create pkg - B)
ros2 pkg create - C)
create_ros2_package - D)
ros2 new package
Answer: B)
ros2 pkg create - A)
-
True/False: The
colcon buildcommand builds all packages in the workspace by default.- A) True
- B) False
Answer: A) True - colcon build will build all packages in the workspace unless specific packages are specified
-
Multiple Choice: What is the purpose of the
setup.pyfile in a ROS 2 Python package?- A) To define entry points for executable nodes
- B) To specify package dependencies
- C) To configure the build process
- D) All of the above
Answer: D) All of the above
-
Short Answer: Explain the difference between
ros2 topic echoandros2 topic hzcommands.Answer:
ros2 topic echoprints messages from a topic as they are received, whileros2 topic hzmeasures the frequency (rate) at which messages are published on a topic. -
Scenario: Your publisher and subscriber nodes are not communicating. List three things you would check to troubleshoot the issue.
Answer: Check if both terminals have the workspace sourced, verify the topic names match exactly, and ensure both nodes are running and properly configured.
Extensions
Try these additional exercises:
- Modify the message content to include timestamp information
- Create multiple subscribers to see how one publisher can serve many
- Change the message rate and observe the effect
- Experiment with different QoS settings for reliability
- Add parameters to control the message content or publishing rate
Next Steps
Now that you've created your first ROS 2 package with working publisher-subscriber communication, you're ready to explore more advanced ROS 2 concepts:
- Module 2: Learn about robot simulation with Gazebo
- Module 3: Explore NVIDIA Isaac for perception and navigation
- Advanced topics: Services, actions, parameters, and launch files