Complete Guide for Python & C++ Development
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.
# Create workspace directory
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# Build workspace (creates build, install, log directories)
colcon build
# 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)
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.
# 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
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
# 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',
],
},
)
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.
# 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
raise_cpp/ âââ CMakeLists.txt # Build configuration âââ package.xml # Package metadata âââ src/ # C++ source files âââ include/ # Header files â âââ raise_cpp/ âââ launch/ # Launch files (optional)
# 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/)
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.
~/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()
~/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;
}
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.
~/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()
# 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
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.
# Navigate to workspace root
cd ~/ros2_ws
# Build all packages
colcon build
# Source the workspace
source ~/ros2_ws/install/setup.bash
# 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+
# 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
# 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
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.
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
# 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))"
# 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
# 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
source ~/ros2_ws/install/setup.bashcolcon build --symlink-install for faster Python developmentlog/ directory for detailed build errorsros2 doctor to check your ROS2 environment~/.bashrc for automatic workspace activation