Building Your First ROS2 Package
By the end of this week, you will be able to:
A ROS2 workspace is a directory structure where you organize, build, and develop ROS2 packages. It provides an isolated environment for your robotics projects.
A workspace acts as a container for ROS2 packages, providing:
| Directory | Purpose |
|---|---|
src/ | Contains all source packages. This is where you write code. |
build/ | Stores intermediate build files (automatically generated). |
install/ | Contains built executables and libraries ready to use. |
log/ | Stores build logs and error messages for debugging. |
ROS2 uses an overlay system where workspaces can build on top of each other.
Underlay: The base ROS2 installation (/opt/ros/jazzy)
Overlay: Your workspace that extends or modifies the underlay
Chain: Underlay → Overlay 1 → Overlay 2 → ... → Your workspace
Benefits:
# Navigate to home directory
cd ~
# Create workspace directory
mkdir -p ros2_ws/src
# Navigate into workspace
cd ros2_ws
# Verify structure
tree -L 2
# Source ROS2 Jazzy (underlay)
source /opt/ros/jazzy/setup.bash
# Verify ROS2 is sourced
echo $ROS_DISTRO
# Output: jazzy
# From workspace root (ros2_ws)
colcon build
# Expected output:
# Starting >>> (nothing to build yet)
# Summary: 0 packages finished
You now have a functional ROS2 workspace, even though it's empty. The build, install, and log directories were automatically created.
# Source the overlay
source install/setup.bash
# Or use local_setup.bash (doesn't source underlay)
# source install/local_setup.bash
~/.bashrc for automatic sourcing.
A ROS2 package is the smallest unit of organization for ROS2 code. It contains:
| Type | Build System | Language |
|---|---|---|
| ament_python | setup.py | Python only |
| ament_cmake | CMakeLists.txt | C++, Python, or mixed |
| ament_cmake_python | Both | Mixed languages |
For this course: We'll focus on ament_python packages.
my_package/
package.xml # Package manifest
setup.py # Python package setup
setup.cfg # Package configuration
resource/ # Resource marker
my_package
my_package/ # Python module
__init__.py
launch/ # Launch files (optional)
config/ # Configuration files (optional)
test/ # Unit tests (optional)
# Navigate to src directory
cd ~/ros2_ws/src
# Create a Python package
ros2 pkg create --build-type ament_python my_robot_controller
# With dependencies
ros2 pkg create --build-type ament_python my_robot_controller \
--dependencies rclpy std_msgs geometry_msgs
# Create with example node
ros2 pkg create --build-type ament_python my_robot_controller \
--dependencies rclpy \
--node-name my_first_node
--build-type ament_python: Specifies Python package--dependencies: Lists package dependencies--node-name: Creates example executable nodeThe package.xml file is the package manifest containing metadata and dependencies.
<?xml version="1.0"?>
<package format="3">
<name>my_robot_controller</name>
<version>0.0.1</version>
<description>My first ROS2 package</description>
<maintainer email="student@uop.edu.jo">Student Name</maintainer>
<license>Apache-2.0</license>
<!-- Build dependencies -->
<depend>rclpy</depend>
<depend>std_msgs</depend>
<depend>geometry_msgs</depend>
<buildtool_depend>ament_python</buildtool_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
The setup.py file configures the Python package installation.
from setuptools import find_packages, setup
package_name = 'my_robot_controller'
setup(
name=package_name,
version='0.0.1',
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='Student Name',
maintainer_email='student@uop.edu.jo',
description='My first ROS2 package',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'my_first_node = my_robot_controller.my_first_node:main',
],
},
)
The entry_points section is crucial for creating executable nodes:
'executable_name = package_name.module_name:function_name'
Example:
my_first_node: Name you'll use with ros2 runmy_robot_controller: Package namemy_first_node: Python file name (without .py)main: Function to executeCreate my_robot_controller/my_first_node.py:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MyFirstNode(Node):
def __init__(self):
super().__init__('my_first_node')
self.publisher_ = self.create_publisher(String, 'robot_news', 10)
self.timer_ = self.create_timer(1.0, self.publish_news)
self.counter_ = 0
self.get_logger().info('My First Node has been started!')
def publish_news(self):
msg = String()
msg.data = f'Hello ROS2! Message #{self.counter_}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.counter_ += 1
def main(args=None):
rclpy.init(args=args)
node = MyFirstNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
import rclpy # ROS2 Python client library
from rclpy.node import Node # Base node class
from std_msgs.msg import String # String message type
self.publisher_ = self.create_publisher(
String, # Message type
'robot_news', # Topic name
10 # Queue size
)
self.timer_ = self.create_timer(
1.0, # Timer period in seconds
self.publish_news # Callback function
)
# Navigate to workspace root
cd ~/ros2_ws
# Build all packages
colcon build
# Build specific package
colcon build --packages-select my_robot_controller
# Build with symlink install (faster for Python)
colcon build --packages-select my_robot_controller --symlink-install
--packages-select: Build only specified package--symlink-install: Create symlinks instead of copying files--parallel-workers N: Use N parallel jobs--event-handlers console_direct+: Show output immediately# Source the overlay
source ~/ros2_ws/install/setup.bash
# Verify package is available
ros2 pkg list | grep my_robot_controller
# Run the node
ros2 run my_robot_controller my_first_node
# Expected output:
# [INFO] [my_first_node]: My First Node has been started!
# [INFO] [my_first_node]: Publishing: "Hello ROS2! Message #0"
# [INFO] [my_first_node]: Publishing: "Hello ROS2! Message #1"
Open a new terminal and:
# Source workspace
source ~/ros2_ws/install/setup.bash
# List topics
ros2 topic list
# Echo the topic
ros2 topic echo /robot_news
# Get topic info
ros2 topic info /robot_news
# Check publishing rate
ros2 topic hz /robot_news
| Type | Description |
|---|---|
<depend> | Runtime and build dependency |
<build_depend> | Only needed during build |
<exec_depend> | Only needed at runtime |
<test_depend> | Only needed for testing |
<buildtool_depend> | Build system tool |
Colcon (collective construction) is the build system for ROS2. It:
# Build all packages
colcon build
# Build specific package
colcon build --packages-select package_name
# Build up to specific package
colcon build --packages-up-to package_name
# Clean build
colcon build --cmake-clean-cache
# Test packages
colcon test
# Show test results
colcon test-result --all
# List packages
colcon list
--symlink-install for Python packages to avoid rebuilding after every code change. Changes to Python files will be immediately available without rebuild.
# 1. Create/modify code
# 2. Build
colcon build --symlink-install
# 3. Source
source install/setup.bash
# 4. Test
ros2 run my_package my_node
# 5. Commit
git add . && git commit
# Error: Package 'my_package' not found
# Solution 1: Build the package
cd ~/ros2_ws
colcon build --packages-select my_package
# Solution 2: Source workspace
source ~/ros2_ws/install/setup.bash
# Solution 3: Check package name
ros2 pkg list | grep my_package
# Error: Executable 'my_node' not found
# Solution: Check setup.py entry_points
# Make sure the executable is listed in console_scripts
# Error: ModuleNotFoundError
# Solution 1: Ensure __init__.py exists
touch ~/ros2_ws/src/my_package/my_package/__init__.py
# Solution 2: Rebuild package
colcon build --packages-select my_package --symlink-install
# Error: Package 'std_msgs' not found
# Solution 1: Add to package.xml
<depend>std_msgs</depend>
# Solution 2: Install missing package
sudo apt install ros-jazzy-std-msgs
# Solution 3: Update rosdep
rosdep update
rosdep install --from-paths src --ignore-src -r -y
Create a complete ROS2 package with multiple nodes demonstrating workspace and package management concepts.
robot_project_wsrobot_controller/robot/status/robot/command/cmd_vel| Component | Points |
|---|---|
| Workspace Structure | 15 |
| Package Configuration | 15 |
| Node 1 Implementation | 20 |
| Node 2 Implementation | 20 |
| Node 3 Implementation | 20 |
| Documentation | 10 |
| Bonus Challenges | +10 |
| Total | 100 (+10) |
Week 3 will cover:
You're building the skills needed for advanced robotics development.