ROS2 Workspace & Package Creation

Complete Guide for Python & C++ Development

ROS2 Workspace Setup

What is this? A ROS2 workspace is like a main project folder where you organize all your robot code. Think of it as your "robot programming workspace" - it keeps everything organized and helps ROS2 find your packages.

1Create Workspace Directory

# Create workspace directory
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws

2Initialize Workspace

# Build workspace (creates build, install, log directories)
colcon build

3Source Workspace

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

# Add to bashrc for automatic sourcing
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc
ros2_ws/
├── build/          # Build artifacts
├── install/        # Install space
├── log/            # Build logs
└── src/            # Source packages (your packages go here)

Python Package Creation

What is this? A Python package is like a folder that contains your Python robot programs. It's where you write scripts to make your robot move, sense, and think. Each package focuses on one specific task.

1Create Python Package

# Navigate to src directory
cd ~/ros2_ws/src

# Create Python package
ros2 pkg create --build-type ament_python raise_python --dependencies rclpy std_msgs geometry_msgs
going to create a new package
package name: raise_python
destination directory: /home/user/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['user ']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: ['rclpy', 'std_msgs', 'geometry_msgs']
creating folder ./raise_python
creating ./raise_python/package.xml
creating folder ./raise_python/resource
creating ./raise_python/resource/raise_python
creating ./raise_python/setup.py
creating ./raise_python/setup.cfg
creating folder ./raise_python/test
creating ./raise_python/test/test_copyright.py
creating ./raise_python/test/test_flake8.py
creating ./raise_python/test/test_pep257.py
creating folder ./raise_python/raise_python
creating ./raise_python/raise_python/__init__.py

2Python Package Structure

raise_python/
├── package.xml               # Package metadata
├── setup.py                  # Python package setup
├── setup.cfg                 # Build configuration
├── resource/
│   └── raise_python          # Package marker
├── test/                     # Test files
│   ├── test_copyright.py
│   ├── test_flake8.py
│   └── test_pep257.py
└── raise_python/             # Python source code
    └── __init__.py

3Update setup.py

# Edit ~/ros2_ws/src/raise_python/setup.py
from setuptools import setup

package_name = 'raise_python'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='your_name',
    maintainer_email='your_email@example.com',
    description='RAISE 2025 Python package for agricultural robotics',
    license='Apache License 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'turtle_mover = raise_python.turtle_mover:main',
            'plant_health_monitor = raise_python.plant_health_monitor:main',
        ],
    },
)

C++ Package Creation

What is this? A C++ package is similar to Python package but uses C++ language. C++ is faster and often used for real-time robot control. It's like the "high-performance engine" for your robot.

1Create C++ Package

# Navigate to src directory
cd ~/ros2_ws/src

# Create C++ package
ros2 pkg create --build-type ament_cmake raise_cpp --dependencies rclcpp std_msgs geometry_msgs

2C++ Package Structure

raise_cpp/
├── CMakeLists.txt            # Build configuration
├── package.xml               # Package metadata
├── src/                      # C++ source files
├── include/                  # Header files
│   └── raise_cpp/
└── launch/                   # Launch files (optional)

3Update CMakeLists.txt

# Add to ~/ros2_ws/src/raise_cpp/CMakeLists.txt (after existing content)

# Add executable
add_executable(turtle_mover_cpp src/turtle_mover_cpp.cpp)
ament_target_dependencies(turtle_mover_cpp rclcpp geometry_msgs)

# Install targets
install(TARGETS
  turtle_mover_cpp
  DESTINATION lib/${PROJECT_NAME})

# Install include directory
install(DIRECTORY include/
  DESTINATION include/)

TurtleSim Movement Scripts

What is this? TurtleSim is like a simple robot simulator - it's a virtual turtle that you can program to move around. This is perfect for learning robot programming without needing real hardware.

1Python TurtleSim Mover

Create File
Save as: ~/ros2_ws/src/raise_python/raise_python/turtle_mover.py
#!/usr/bin/env python3

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

class TurtleMover(Node):
    def __init__(self):
        super().__init__('turtle_mover')
        
        # Create publisher for turtle velocity commands
        self.publisher_ = self.create_publisher(
            Twist,
            '/turtle1/cmd_vel',
            10
        )
        
        self.get_logger().info('🐢 Turtle Mover initialized!')
        
    def move_distance(self, distance, speed=1.0):
        """Move turtle forward for a specific distance"""
        self.get_logger().info(f'📐 Moving {distance}m at {speed}m/s')
        
        # Calculate time needed
        move_time = distance / speed
        
        # Create Twist message
        vel_msg = Twist()
        vel_msg.linear.x = speed
        vel_msg.linear.y = 0.0
        vel_msg.linear.z = 0.0
        vel_msg.angular.x = 0.0
        vel_msg.angular.y = 0.0
        vel_msg.angular.z = 0.0
        
        # Publish velocity
        start_time = time.time()
        while (time.time() - start_time) < move_time:
            self.publisher_.publish(vel_msg)
            time.sleep(0.1)
        
        # Stop turtle
        self.stop_turtle()
        self.get_logger().info('✅ Movement completed!')
        
    def rotate_angle(self, angle_degrees, angular_speed=30.0):
        """Rotate turtle by specific angle in degrees"""
        self.get_logger().info(f'🔄 Rotating {angle_degrees}° at {angular_speed}°/s')
        
        # Convert to radians
        angle_radians = math.radians(abs(angle_degrees))
        angular_speed_rad = math.radians(angular_speed)
        
        # Calculate time needed
        rotate_time = angle_radians / angular_speed_rad
        
        # Create Twist message
        vel_msg = Twist()
        vel_msg.linear.x = 0.0
        vel_msg.linear.y = 0.0
        vel_msg.linear.z = 0.0
        vel_msg.angular.x = 0.0
        vel_msg.angular.y = 0.0
        # Set direction based on positive/negative angle
        vel_msg.angular.z = angular_speed_rad if angle_degrees > 0 else -angular_speed_rad
        
        # Publish velocity
        start_time = time.time()
        while (time.time() - start_time) < rotate_time:
            self.publisher_.publish(vel_msg)
            time.sleep(0.1)
        
        # Stop turtle
        self.stop_turtle()
        self.get_logger().info('✅ Rotation completed!')
        
    def stop_turtle(self):
        """Stop the turtle"""
        vel_msg = Twist()
        # All velocities set to 0.0 by default
        self.publisher_.publish(vel_msg)
        
    def draw_square(self, side_length=2.0, speed=1.0):
        """Draw a square with given side length"""
        self.get_logger().info(f'🔲 Drawing square with side length {side_length}m')
        
        for i in range(4):
            self.get_logger().info(f'📏 Side {i+1}/4')
            self.move_distance(side_length, speed)
            time.sleep(0.5)  # Pause between movements
            self.rotate_angle(90)  # Turn 90 degrees
            time.sleep(0.5)
            
        self.get_logger().info('🎨 Square completed!')

def main(args=None):
    rclpy.init(args=args)
    turtle_mover = TurtleMover()
    
    try:
        # Example movements
        turtle_mover.get_logger().info('🚀 Starting turtle movement demo...')
        
        # Move forward 3 meters at 1.5 m/s
        turtle_mover.move_distance(3.0, 1.5)
        time.sleep(1)
        
        # Rotate 180 degrees
        turtle_mover.rotate_angle(180)
        time.sleep(1)
        
        # Move back 2 meters
        turtle_mover.move_distance(2.0, 1.0)
        time.sleep(1)
        
        # Draw a square
        turtle_mover.draw_square(1.5, 1.0)
        
        turtle_mover.get_logger().info('🎉 Demo completed!')
        
    except KeyboardInterrupt:
        turtle_mover.get_logger().info('🛑 Movement interrupted')
    finally:
        turtle_mover.stop_turtle()
        turtle_mover.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

2C++ TurtleSim Mover

Create File
Save as: ~/ros2_ws/src/raise_cpp/src/turtle_mover_cpp.cpp
#include 
#include 
#include 
#include 

class TurtleMoverCpp : public rclcpp::Node
{
public:
    TurtleMoverCpp() : Node("turtle_mover_cpp")
    {
        // Create publisher for turtle velocity commands
        publisher_ = this->create_publisher(
            "/turtle1/cmd_vel", 10);
        
        RCLCPP_INFO(this->get_logger(), "🐢 C++ Turtle Mover initialized!");
    }
    
    void move_distance(double distance, double speed = 1.0)
    {
        RCLCPP_INFO(this->get_logger(), "📐 Moving %.2fm at %.2fm/s", distance, speed);
        
        // Calculate time needed
        double move_time = distance / speed;
        
        // Create Twist message
        auto vel_msg = geometry_msgs::msg::Twist();
        vel_msg.linear.x = speed;
        vel_msg.linear.y = 0.0;
        vel_msg.linear.z = 0.0;
        vel_msg.angular.x = 0.0;
        vel_msg.angular.y = 0.0;
        vel_msg.angular.z = 0.0;
        
        // Publish velocity
        auto start_time = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration(move_time);
        
        while (std::chrono::steady_clock::now() - start_time < duration)
        {
            publisher_->publish(vel_msg);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        
        // Stop turtle
        stop_turtle();
        RCLCPP_INFO(this->get_logger(), "✅ Movement completed!");
    }
    
    void rotate_angle(double angle_degrees, double angular_speed = 30.0)
    {
        RCLCPP_INFO(this->get_logger(), "🔄 Rotating %.2f° at %.2f°/s", angle_degrees, angular_speed);
        
        // Convert to radians
        double angle_radians = std::abs(angle_degrees) * M_PI / 180.0;
        double angular_speed_rad = angular_speed * M_PI / 180.0;
        
        // Calculate time needed
        double rotate_time = angle_radians / angular_speed_rad;
        
        // Create Twist message
        auto vel_msg = geometry_msgs::msg::Twist();
        vel_msg.linear.x = 0.0;
        vel_msg.linear.y = 0.0;
        vel_msg.linear.z = 0.0;
        vel_msg.angular.x = 0.0;
        vel_msg.angular.y = 0.0;
        // Set direction based on positive/negative angle
        vel_msg.angular.z = (angle_degrees > 0) ? angular_speed_rad : -angular_speed_rad;
        
        // Publish velocity
        auto start_time = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration(rotate_time);
        
        while (std::chrono::steady_clock::now() - start_time < duration)
        {
            publisher_->publish(vel_msg);
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
        
        // Stop turtle
        stop_turtle();
        RCLCPP_INFO(this->get_logger(), "✅ Rotation completed!");
    }
    
    void stop_turtle()
    {
        auto vel_msg = geometry_msgs::msg::Twist();
        // All velocities are 0.0 by default
        publisher_->publish(vel_msg);
    }
    
    void draw_square(double side_length = 2.0, double speed = 1.0)
    {
        RCLCPP_INFO(this->get_logger(), "🔲 Drawing square with side length %.2fm", side_length);
        
        for (int i = 0; i < 4; i++)
        {
            RCLCPP_INFO(this->get_logger(), "📏 Side %d/4", i + 1);
            move_distance(side_length, speed);
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            rotate_angle(90);
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }
        
        RCLCPP_INFO(this->get_logger(), "🎨 Square completed!");
    }

private:
    rclcpp::Publisher::SharedPtr publisher_;
};

int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);
    auto turtle_mover = std::make_shared();
    
    try
    {
        // Example movements
        RCLCPP_INFO(turtle_mover->get_logger(), "🚀 Starting turtle movement demo...");
        
        // Move forward 3 meters at 1.5 m/s
        turtle_mover->move_distance(3.0, 1.5);
        std::this_thread::sleep_for(std::chrono::seconds(1));
        
        // Rotate 180 degrees
        turtle_mover->rotate_angle(180);
        std::this_thread::sleep_for(std::chrono::seconds(1));
        
        // Move back 2 meters
        turtle_mover->move_distance(2.0, 1.0);
        std::this_thread::sleep_for(std::chrono::seconds(1));
        
        // Draw a square
        turtle_mover->draw_square(1.5, 1.0);
        
        RCLCPP_INFO(turtle_mover->get_logger(), "🎉 Demo completed!");
    }
    catch (const std::exception& e)
    {
        RCLCPP_ERROR(turtle_mover->get_logger(), "🛑 Error: %s", e.what());
    }
    
    turtle_mover->stop_turtle();
    rclcpp::shutdown();
    return 0;
}

Plant Health Monitor Example

What is this? This is a practical example for agricultural robotics - a program that monitors plant health (like soil moisture) and can trigger actions (like watering). It shows how robots can help in farming.

1Plant Health Monitor Node

Create File
Save as: ~/ros2_ws/src/raise_python/raise_python/plant_health_monitor.py
#!/usr/bin/env python3

import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32, String

class PlantHealthMonitor(Node):
    def __init__(self):
        super().__init__('plant_health_monitor')
        
        # Create a subscriber to /soil_moisture
        self.subscription = self.create_subscription(
            Float32,                    # Message type
            '/soil_moisture',           # Topic name
            self.soil_moisture_callback, # Callback function
            10                          # Queue size
        )
        
        # Create a publisher for /plant_health_status
        self.publisher_ = self.create_publisher(
            String,                     # Message type
            '/plant_health_status',     # Topic name
            10                          # Queue size
        )
        
        # Initialize health monitoring parameters
        self.dry_threshold = 30.0      # Below this = too dry
        self.wet_threshold = 70.0      # Above this = too wet
        self.last_reading = 0.0        # Track last moisture reading
        self.reading_count = 0         # Count readings processed
        
        self.get_logger().info('🌿 Plant health monitor started!')
        self.get_logger().info(f'📊 Dry threshold: {self.dry_threshold}%')
        self.get_logger().info(f'📊 Wet threshold: {self.wet_threshold}%')
        self.get_logger().info('🔄 Waiting for soil moisture data...')

    def soil_moisture_callback(self, msg):
        """Process soil moisture data and determine plant health"""
        moisture = msg.data
        self.last_reading = moisture
        self.reading_count += 1
        
        # Implement health logic
        if moisture < self.dry_threshold:
            health_status = "Needs Water"
            emoji = "🔴"
            action = "IRRIGATE"
        elif moisture > self.wet_threshold:
            health_status = "Too Wet"
            emoji = "🔵"
            action = "DRAIN"
        else:
            health_status = "Healthy"
            emoji = "🟢"
            action = "MONITOR"
        
        # Create a String message and publish health status
        health_msg = String()
        health_msg.data = health_status
        self.publisher_.publish(health_msg)
        
        # Log the analysis with additional context
        self.get_logger().info(
            f'{emoji} Reading #{self.reading_count}: {moisture:.1f}% → '
            f'Health: {health_status} → Action: {action}'
        )

def main(args=None):
    rclpy.init(args=args)
    node = PlantHealthMonitor()
    
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info('🛑 Shutting down plant health monitor...')
    finally:
        node.destroy_node()
        rclpy.shutdown()
        print("✅ Plant health monitor shut down successfully!")

if __name__ == '__main__':
    main()

2Test the Plant Health Monitor

# Terminal 1: Run the plant health monitor
ros2 run raise_python plant_health_monitor

# Terminal 2: Publish test moisture data
# Dry soil (needs water)
ros2 topic pub /soil_moisture std_msgs/msg/Float32 "data: 25.0"

# Good soil moisture
ros2 topic pub /soil_moisture std_msgs/msg/Float32 "data: 50.0"

# Too wet soil
ros2 topic pub /soil_moisture std_msgs/msg/Float32 "data: 85.0"

# Terminal 3: Monitor plant health status
ros2 topic echo /plant_health_status

Build & Run Your Packages

What is this? After writing your robot programs, you need to "build" them (like compiling) and then "run" them to see your robot in action. These commands turn your code into working robot programs.

1Build All Packages

# Navigate to workspace root
cd ~/ros2_ws

# Build all packages
colcon build

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

2Build Specific Package

# Build only Python package
colcon build --packages-select raise_python

# Build only C++ package
colcon build --packages-select raise_cpp

# Build with verbose output
colcon build --packages-select raise_python --event-handlers console_direct+

3Run the Applications

# Terminal 1: Start TurtleSim
ros2 run turtlesim turtlesim_node

# Terminal 2: Run Python turtle mover
ros2 run raise_python turtle_mover

# OR run C++ turtle mover
ros2 run raise_cpp turtle_mover_cpp

# Terminal 3: Run plant health monitor
ros2 run raise_python plant_health_monitor

4Check Package Status

# List all packages
ros2 pkg list | grep raise

# Check if package is properly installed
ros2 pkg list | grep raise_python
ros2 pkg list | grep raise_cpp

# Get package information
ros2 pkg xml raise_python
ros2 pkg xml raise_cpp

Troubleshooting

What is this? Sometimes things don't work as expected. This section helps you fix common problems that students often encounter when developing ROS2 projects.

Common Issues

Package Not Found
If ros2 run raise_python turtle_mover fails:
# Rebuild and source workspace
cd ~/ros2_ws
colcon build --packages-select raise_python
source ~/ros2_ws/install/setup.bash

# Check if entry point is correctly defined in setup.py
cat ~/ros2_ws/src/raise_python/setup.py | grep -A5 entry_points

2Python Import Issues

# Make Python files executable
chmod +x ~/ros2_ws/src/raise_python/raise_python/turtle_mover.py
chmod +x ~/ros2_ws/src/raise_python/raise_python/plant_health_monitor.py

# Check Python path
python3 -c "import sys; print('\n'.join(sys.path))"

3C++ Compilation Issues

# Clean build and rebuild C++ package
cd ~/ros2_ws
rm -rf build/raise_cpp install/raise_cpp log/raise_cpp
colcon build --packages-select raise_cpp --cmake-clean-cache

4Useful Debug Commands

# Check active nodes
ros2 node list

# Check active topics
ros2 topic list

# Monitor topic data
ros2 topic echo /turtle1/cmd_vel

# Check node info
ros2 node info /turtle_mover

# Check workspace sourcing
echo $AMENT_PREFIX_PATH
Pro Tips
  • Always source your workspace after building: source ~/ros2_ws/install/setup.bash
  • Use colcon build --symlink-install for faster Python development
  • Check log/ directory for detailed build errors
  • Use ros2 doctor to check your ROS2 environment
  • Add sourcing to ~/.bashrc for automatic workspace activation
Back to RAISE 2025