Week 2: Workspace & Package Management

Building Your First ROS2 Package

Week 2 of 16 Ubuntu 24.04 ROS2 Jazzy Python 3.12+

Week Overview

Learning Objectives

By the end of this week, you will be able to:

  1. Create and manage ROS2 workspaces using colcon
  2. Understand ROS2 package structure and organization
  3. Build Python packages for ROS2
  4. Configure package.xml and setup.py files
  5. Manage package dependencies effectively
  6. Use colcon build system efficiently
  7. Organize code following ROS2 best practices
  8. Create your first custom ROS2 Python package
Course Information
  • Week: 2 of 16
  • Duration: 3 hours (2 hours in-campus + 1 hour asynchronous)
  • Office Hours: Every Day, 10:00 AM - 12:00 PM
  • Prerequisites: Week 1 completed (Linux basics, ROS2 installation)

Understanding ROS2 Workspaces

What is a Workspace?

A ROS2 workspace is a directory structure where you organize, build, and develop ROS2 packages. It provides an isolated environment for your robotics projects.

Key Concept

A workspace acts as a container for ROS2 packages, providing:

  • Organized development environment
  • Dependency management
  • Build isolation
  • Source control integration
  • Multiple package coordination

Workspace Structure

workspace_name/ src/ # Source space (your code) package_1/ package_2/ package_n/ build/ # Build space (intermediate files) install/ # Install space (executables) log/ # Build and test logs
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.

Overlay Concept

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:

  • Test modifications without affecting base installation
  • Develop multiple independent projects
  • Override packages from underlay
  • Maintain clean separation of concerns

Creating Your First Workspace

Step 1: Create Directory Structure

# Navigate to home directory
cd ~

# Create workspace directory
mkdir -p ros2_ws/src

# Navigate into workspace
cd ros2_ws

# Verify structure
tree -L 2

Step 2: Source ROS2 Environment

# Source ROS2 Jazzy (underlay)
source /opt/ros/jazzy/setup.bash

# Verify ROS2 is sourced
echo $ROS_DISTRO
# Output: jazzy

Step 3: Build the Workspace

# From workspace root (ros2_ws)
colcon build

# Expected output:
# Starting >>> (nothing to build yet)
# Summary: 0 packages finished
✓ First Workspace Created!

You now have a functional ROS2 workspace, even though it's empty. The build, install, and log directories were automatically created.

Step 4: Source Your Workspace

# Source the overlay
source install/setup.bash

# Or use local_setup.bash (doesn't source underlay)
# source install/local_setup.bash
Important: You must source your workspace in every new terminal where you want to use packages from this workspace. Add it to ~/.bashrc for automatic sourcing.

Understanding ROS2 Packages

What is a Package?

A ROS2 package is the smallest unit of organization for ROS2 code. It contains:

  • Source code (Python, C++, or both)
  • Configuration files
  • Launch files
  • Message/service/action definitions
  • Documentation
  • Tests

Package Types

Type Build System Language
ament_pythonsetup.pyPython only
ament_cmakeCMakeLists.txtC++, Python, or mixed
ament_cmake_pythonBothMixed languages

For this course: We'll focus on ament_python packages.

Python Package Structure

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)

Creating Your First Package

Using ros2 pkg create

# 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
Command Breakdown
  • --build-type ament_python: Specifies Python package
  • --dependencies: Lists package dependencies
  • --node-name: Creates example executable node

Understanding package.xml

The 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>

Understanding setup.py

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',
        ],
    },
)
Entry Points Explained

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 run
  • my_robot_controller: Package name
  • my_first_node: Python file name (without .py)
  • main: Function to execute

Creating Your First Node

Writing a Simple Publisher Node

Create 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()

Code Explanation

Import Statements
import rclpy                    # ROS2 Python client library
from rclpy.node import Node     # Base node class
from std_msgs.msg import String # String message type
Creating a Publisher
self.publisher_ = self.create_publisher(
    String,         # Message type
    'robot_news',   # Topic name
    10              # Queue size
)
Creating a Timer
self.timer_ = self.create_timer(
    1.0,                    # Timer period in seconds
    self.publish_news       # Callback function
)

Building and Running Your Package

Building the Package

# 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
Useful Colcon Options
  • --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

Sourcing the Workspace

# Source the overlay
source ~/ros2_ws/install/setup.bash

# Verify package is available
ros2 pkg list | grep my_robot_controller

Running Your Node

# 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"

Testing the Node

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

Package Dependencies

Types of Dependencies

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

Common ROS2 Dependencies

  • rclpy: ROS2 Python client library (always needed)
  • std_msgs: Standard messages (String, Int, Float, etc.)
  • geometry_msgs: Geometric messages (Pose, Twist, Transform)
  • sensor_msgs: Sensor messages (Image, LaserScan, Imu)
  • nav_msgs: Navigation messages (Odometry, Path)
  • tf2_ros: Transform library

Colcon Build System

Understanding Colcon

Colcon (collective construction) is the build system for ROS2. It:

  • Builds multiple packages in correct order
  • Handles dependencies automatically
  • Supports parallel builds
  • Works with different build types (CMake, Python)
  • Provides testing integration

Colcon Commands Reference

# 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: Use --symlink-install for Python packages to avoid rebuilding after every code change. Changes to Python files will be immediately available without rebuild.

Best Practices

Workspace Organization
  • One workspace per project
  • Use meaningful names
  • Group related packages
  • Use Git for version control
  • Maintain README files
Package Design
  • Single responsibility per package
  • Minimal dependencies
  • Follow naming conventions
  • Modular code structure
  • Use YAML for configuration
Code Quality
  • Follow PEP 8 style guide
  • Use type hints
  • Write docstrings
  • Include error handling
  • Use ROS2 logger, not print()
Development Workflow
# 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

Troubleshooting

# 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

Week 2 Project: Robot Controller Package

Project Overview

Create a complete ROS2 package with multiple nodes demonstrating workspace and package management concepts.

Requirements

1. Create Workspace
  • Name: robot_project_ws
  • Proper directory structure
2. Create Package
  • Name: robot_controller
  • Type: Python (ament_python)
  • Dependencies: rclpy, std_msgs, geometry_msgs
3. Implement Three Nodes
Node 1: Status Publisher
  • Publishes robot status every 2 seconds
  • Topic: /robot/status
  • Message: String with status info
Node 2: Command Subscriber
  • Subscribes to /robot/command
  • Processes commands (start, stop, reset)
  • Logs received commands
Node 3: Velocity Publisher
  • Publishes velocity commands
  • Topic: /cmd_vel
  • Message type: geometry_msgs/Twist
4. Documentation
  • README.md with setup instructions
  • Code comments
  • Node descriptions
5. Testing
  • Demonstrate all nodes running
  • Show topic communication
  • Provide terminal output

Bonus Challenges

  • Create a launch file to start all nodes
  • Add parameters for timer periods
  • Implement error handling
  • Add a configuration file (YAML)
  • Write unit tests

Grading Rubric

Component Points
Workspace Structure15
Package Configuration15
Node 1 Implementation20
Node 2 Implementation20
Node 3 Implementation20
Documentation10
Bonus Challenges+10
Total100 (+10)

Resources & References

Official Documentation
Additional Resources
Next Week Preview: Publishers & Subscribers

Week 3 will cover:

  • In-depth topic communication
  • Creating publishers in Python
  • Creating subscribers in Python
  • Message types and custom messages
  • Quality of Service (QoS)
  • Callback functions

Excellent Progress!

You're building the skills needed for advanced robotics development.