- Overview
- Supported Platforms and Versions
- Workspace Structure
- Creating the ROS 2 Workspace
- Automatic Workspace Sourcing
- Dependency Management
- Creating Packages
- Python Node
- C++ Node
- Launch Files
- Build and Run Instructions
- Development Workflow
- Testing
- Code Quality
- Performance Considerations
- Cleaning the Workspace
- Continuous Integration
- Docker Deployment
- Advanced Configuration
- Troubleshooting
- Git Ignore
- Rules and Best Practices
- Contributing
- Extending This Template
- Documentation
- Versioning
- Security
- Resources
- License
- Acknowledgments
- Support
This repository provides a production-ready ROS 2 workspace template for professional development. It demonstrates standard package structure, build configuration, development workflows, testing, and CI/CD integration for both Python and C++ nodes.
Intended for:
- Engineers starting new ROS 2 projects
- Teams establishing workspace conventions
- Developers requiring a clean, minimal baseline
- Organizations implementing ROS 2 best practices
Not a tutorial. This template assumes familiarity with ROS 2 concepts. It prioritizes practical structure over pedagogical explanation.
- ROS 2 Distributions: Humble, Iron, Jazzy
- Operating Systems: Ubuntu 22.04 (Humble/Iron), Ubuntu 24.04 (Jazzy)
- Build System: colcon
- Languages: Python 3.10+, C++17
- DDS Implementations: FastDDS (default), CycloneDDS, Connext
ros2_ws/
├── src/
│ ├── py_package/
│ │ ├── py_package/
│ │ │ ├── __init__.py
│ │ │ └── talker_listener_node.py
│ │ ├── launch/
│ │ │ └── talker_listener_launch.py
│ │ ├── test/
│ │ │ └── test_talker_listener.py
│ │ ├── resource/
│ │ │ └── py_package
│ │ ├── package.xml
│ │ ├── setup.py
│ │ ├── setup.cfg
│ │ └── README.md
│ └── cpp_package/
│ ├── include/
│ │ └── cpp_package/
│ ├── src/
│ │ └── talker_listener_node.cpp
│ ├── launch/
│ │ └── talker_listener_launch.py
│ ├── test/
│ │ └── test_talker_listener.cpp
│ ├── CMakeLists.txt
│ ├── package.xml
│ └── README.md
├── build/
├── install/
├── log/
├── .github/
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
└── README.md
The build/, install/, and log/ directories are generated during the build process and must not be committed to version control.
mkdir -p ~/ros2_ws/src
cd ~/ros2_wsSource your ROS 2 installation:
source /opt/ros/humble/setup.bash # or iron, jazzyInitialize the workspace by building with no packages:
colcon buildTo avoid manually sourcing the workspace in every new terminal, add it to your .bashrc:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrcAlternative: Manual editing
gedit ~/.bashrcAdd these lines at the end of the file:
# ROS 2 Humble setup
source /opt/ros/humble/setup.bash
# Workspace setup
source ~/ros2_ws/install/setup.bashSave and close, then reload:
source ~/.bashrcImportant Notes:
- This makes the workspace available automatically in every new terminal
- If you have multiple workspaces, source only the one you're actively using
- Rebuild the workspace whenever you modify packages, then the sourcing happens automatically
Install all dependencies using rosdep:
cd ~/ros2_ws
rosdep update
rosdep install --from-paths src --ignore-src -r -yDeclare dependencies in package.xml:
<depend>- Build and runtime dependency<build_depend>- Build-time only<exec_depend>- Runtime only<test_depend>- Testing only
Example:
<depend>rclpy</depend>
<depend>std_msgs</depend>
<test_depend>pytest</test_depend>cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python py_package --dependencies rclpy std_msgsCreate additional directories:
mkdir -p py_package/launch
mkdir -p py_package/testcd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake cpp_package --dependencies rclcpp std_msgsCreate additional directories:
mkdir -p cpp_package/launch
mkdir -p cpp_package/testFile: src/py_package/py_package/talker_listener_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class TalkerListenerNode(Node):
"""
A ROS 2 node that publishes and subscribes to String messages.
This node demonstrates basic pub/sub patterns in ROS 2 using Python.
It publishes messages at 1 Hz and logs received messages.
"""
def __init__(self):
super().__init__('talker_listener_node')
# Publisher
self.publisher_ = self.create_publisher(String, 'chatter', 10)
# Subscriber
self.subscription_ = self.create_subscription(
String,
'chatter',
self.listener_callback,
10
)
# Timer for publishing
self.timer_ = self.create_timer(1.0, self.timer_callback)
self.counter_ = 0
self.get_logger().info('Talker-Listener node initialized')
def timer_callback(self):
"""Publish a message at regular intervals."""
msg = String()
msg.data = f'Hello from Python: {self.counter_}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.counter_ += 1
def listener_callback(self, msg):
"""Handle received messages."""
self.get_logger().info(f'Received: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
node = TalkerListenerNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()File: src/py_package/setup.py
from setuptools import find_packages, setup
from glob import glob
import os
package_name = 'py_package'
setup(
name=package_name,
version='1.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='Your Name',
maintainer_email='you@example.com',
description='Python package with publisher and subscriber',
license='MIT',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'talker_listener_node = py_package.talker_listener_node:main',
],
},
)File: src/py_package/setup.cfg
[develop]
script_dir=$base/lib/py_package
[install]
install_scripts=$base/lib/py_package
File: src/py_package/package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>py_package</name>
<version>1.0.0</version>
<description>Python package with publisher and subscriber</description>
<maintainer email="you@example.com">Your Name</maintainer>
<license>MIT</license>
<depend>rclpy</depend>
<depend>std_msgs</depend>
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>File: src/cpp_package/src/talker_listener_node.cpp
#include <chrono>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
/**
* @brief A ROS 2 node that publishes and subscribes to String messages.
*
* This node demonstrates basic pub/sub patterns in ROS 2 using C++.
* It publishes messages at 1 Hz and logs received messages.
*/
class TalkerListenerNode : public rclcpp::Node
{
public:
TalkerListenerNode()
: Node("talker_listener_node"), counter_(0)
{
// Publisher
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
// Subscriber
subscription_ = this->create_subscription<std_msgs::msg::String>(
"chatter", 10,
std::bind(&TalkerListenerNode::listener_callback, this, std::placeholders::_1));
// Timer for publishing
timer_ = this->create_wall_timer(
1s, std::bind(&TalkerListenerNode::timer_callback, this));
RCLCPP_INFO(this->get_logger(), "Talker-Listener node initialized");
}
private:
/**
* @brief Publish a message at regular intervals.
*/
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello from C++: " + std::to_string(counter_);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
counter_++;
}
/**
* @brief Handle received messages.
* @param msg The received message
*/
void listener_callback(const std_msgs::msg::String::SharedPtr msg)
{
RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
rclcpp::TimerBase::SharedPtr timer_;
size_t counter_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<TalkerListenerNode>());
rclcpp::shutdown();
return 0;
}File: src/cpp_package/CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(cpp_package)
# Compiler options
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# Build executable
add_executable(talker_listener_node src/talker_listener_node.cpp)
ament_target_dependencies(talker_listener_node rclcpp std_msgs)
# Install targets
install(TARGETS
talker_listener_node
DESTINATION lib/${PROJECT_NAME}
)
# Install launch files
install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}
)
# Testing
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
# Add gtest
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest(${PROJECT_NAME}_test test/test_talker_listener.cpp)
target_link_libraries(${PROJECT_NAME}_test ${PROJECT_NAME})
ament_target_dependencies(${PROJECT_NAME}_test rclcpp std_msgs)
endif()
ament_package()File: src/cpp_package/package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>cpp_package</name>
<version>1.0.0</version>
<description>C++ package with publisher and subscriber</description>
<maintainer email="you@example.com">Your Name</maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>File: src/py_package/launch/talker_listener_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
"""Generate launch description for Python talker-listener node."""
return LaunchDescription([
Node(
package='py_package',
executable='talker_listener_node',
name='talker_listener_node',
output='screen',
parameters=[],
remappings=[],
arguments=['--ros-args', '--log-level', 'info']
)
])File: src/cpp_package/launch/talker_listener_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
"""Generate launch description for C++ talker-listener node."""
return LaunchDescription([
Node(
package='cpp_package',
executable='talker_listener_node',
name='talker_listener_node',
output='screen',
parameters=[],
remappings=[],
arguments=['--ros-args', '--log-level', 'info']
)
])cd ~/ros2_ws
colcon buildSource the workspace overlay:
source install/setup.bashFor production deployment:
colcon build --cmake-args -DCMAKE_BUILD_TYPE=ReleaseFor debugging:
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debugros2 launch py_package talker_listener_launch.pyros2 launch cpp_package talker_listener_launch.pyros2 run py_package talker_listener_node
ros2 run cpp_package talker_listener_nodeFor rapid Python development without rebuilding:
colcon build --symlink-installChanges to Python source files take effect immediately. C++ packages still require rebuild.
colcon build --packages-select py_packagecolcon build --packages-up-to cpp_packagecolcon build --event-handlers console_direct+colcon build --parallel-workers 4colcon build --cmake-clean-cachecd ~/ros2_ws
colcon test
colcon test-result --verbosecolcon test --packages-select py_package
colcon test-result --verboseFile: src/py_package/test/test_talker_listener.py
import unittest
import rclpy
from py_package.talker_listener_node import TalkerListenerNode
class TestTalkerListenerNode(unittest.TestCase):
@classmethod
def setUpClass(cls):
rclpy.init()
@classmethod
def tearDownClass(cls):
rclpy.shutdown()
def test_node_creation(self):
"""Test that the node can be created."""
node = TalkerListenerNode()
self.assertIsNotNone(node)
node.destroy_node()
def test_publisher_exists(self):
"""Test that the publisher is created."""
node = TalkerListenerNode()
self.assertEqual(len(node.publishers), 1)
node.destroy_node()
def test_subscription_exists(self):
"""Test that the subscription is created."""
node = TalkerListenerNode()
self.assertEqual(len(node.subscriptions), 1)
node.destroy_node()
if __name__ == '__main__':
unittest.main()File: src/cpp_package/test/test_talker_listener.cpp
#include <gtest/gtest.h>
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>
TEST(TalkerListenerTest, NodeCreation) {
rclcpp::init(0, nullptr);
auto node = std::make_shared<rclcpp::Node>("test_node");
ASSERT_NE(node, nullptr);
rclcpp::shutdown();
}
TEST(TalkerListenerTest, PublisherCreation) {
rclcpp::init(0, nullptr);
auto node = std::make_shared<rclcpp::Node>("test_node");
auto publisher = node->create_publisher<std_msgs::msg::String>("test_topic", 10);
ASSERT_NE(publisher, nullptr);
rclcpp::shutdown();
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}Build with coverage flags:
colcon build --cmake-args -DCMAKE_CXX_FLAGS="--coverage"
colcon testGenerate coverage report:
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
lcov --list coverage.infoament_cpplint src/cpp_package/src/ament_flake8 src/py_package/
ament_pep257 src/py_package/clang-tidy src/cpp_package/src/*.cpp -- -I/opt/ros/humble/includefind src/cpp_package -name '*.cpp' -o -name '*.hpp' | xargs clang-format -iblack src/py_package/Configure Quality of Service for different communication patterns:
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy, DurabilityPolicy
# Reliable communication
reliable_qos = QoSProfile(
depth=10,
reliability=ReliabilityPolicy.RELIABLE,
history=HistoryPolicy.KEEP_LAST,
durability=DurabilityPolicy.VOLATILE
)
self.publisher_ = self.create_publisher(String, 'topic', reliable_qos)C++ equivalent:
auto qos = rclcpp::QoS(rclcpp::KeepLast(10))
.reliable()
.durability_volatile();
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", qos);Release build with optimizations:
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-O3"Switch to CycloneDDS for better performance:
sudo apt install ros-humble-rmw-cyclonedds-cpp
export RMW_IMPLEMENTATION=rmw_cyclonedds_cppcd ~/ros2_ws
rm -rf build/ install/ log/rm -rf build/ install/ log/
colcon buildrm -rf build/py_package install/py_package
colcon build --packages-select py_packageFile: .github/workflows/ci.yml
name: ROS 2 CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
build-and-test:
runs-on: ubuntu-22.04
strategy:
matrix:
ros_distribution: [humble, iron]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup ROS 2
uses: ros-tooling/setup-ros@v0.7
with:
required-ros-distributions: ${{ matrix.ros_distribution }}
- name: Install dependencies
run: |
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
rosdep update
rosdep install --from-paths src --ignore-src -r -y
- name: Build workspace
run: |
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release
- name: Run tests
run: |
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
source install/setup.bash
colcon test --return-code-on-test-failure
- name: Show test results
if: always()
run: colcon test-result --verbose
- name: Run linters
run: |
source /opt/ros/${{ matrix.ros_distribution }}/setup.bash
ament_flake8 src/py_package/
ament_cpplint src/cpp_package/File: Dockerfile
FROM ros:humble
# Install dependencies
RUN apt-get update && apt-get install -y \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Create workspace
WORKDIR /ros2_ws
# Copy source files
COPY src ./src
# Install rosdep dependencies
RUN apt-get update && \
rosdep update && \
rosdep install --from-paths src --ignore-src -r -y && \
rm -rf /var/lib/apt/lists/*
# Build workspace
RUN . /opt/ros/humble/setup.sh && \
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release
# Source workspace
RUN echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc && \
echo "source /ros2_ws/install/setup.bash" >> ~/.bashrc
CMD ["bash"]docker build -t ros2_project .
docker run -it --rm --net=host ros2_projectInside container:
ros2 launch py_package talker_listener_launch.pyFile: docker-compose.yml
version: '3.8'
services:
ros2_node:
build: .
network_mode: host
command: ros2 launch py_package talker_listener_launch.py
volumes:
- ./src:/ros2_ws/src
environment:
- ROS_DOMAIN_ID=0# Network isolation
export ROS_DOMAIN_ID=42
# Change DDS implementation
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
# Disable shared memory
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
# Set log level
export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity}] [{name}]: {message}"File: config/params.yaml
talker_listener_node:
ros__parameters:
publish_rate: 1.0
queue_size: 10
topic_name: "chatter"Load parameters in launch file:
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
config = os.path.join(
get_package_share_directory('py_package'),
'config',
'params.yaml'
)
return LaunchDescription([
Node(
package='py_package',
executable='talker_listener_node',
parameters=[config]
)
])Problem: package 'X' not found
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash
rosdep install --from-paths src --ignore-src -r -yProblem: Changes not reflected after build
- Python: Rebuild with
--symlink-install - C++: Clean and rebuild package
rm -rf build/cpp_package install/cpp_package
colcon build --packages-select cpp_packageProblem: Launch file not found
Verify launch directory installation in setup.py:
data_files=[
# ...
(os.path.join('share', package_name, 'launch'), glob('launch/*.py')),
]Or in CMakeLists.txt:
install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}
)Problem: Multiple nodes conflict
Use unique node names or namespaces:
Node(
package='py_package',
executable='talker_listener_node',
name='node_1',
namespace='robot1'
)colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug
ros2 run --prefix 'gdb -ex run --args' cpp_package talker_listener_nodeAdd to your Python code:
import pdb; pdb.set_trace()ros2 run py_package talker_listener_node --ros-args --log-level debugCheck active nodes:
ros2 node listCheck active topics:
ros2 topic list
ros2 topic echo /chatterCheck message flow:
ros2 topic hz /chatter
ros2 topic bw /chatterFile: .gitignore
# ROS 2 build artifacts
build/
install/
log/
# Python
*.pyc
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
pip-log.txt
pip-delete-this-directory.txt
.pytest_cache/
# C++
*.o
*.a
*.so
*.out
*.app
*.i*86
*.x86_64
*.hex
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# CMake
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
# Coverage
*.gcda
*.gcno
*.gcov
coverage.info
coverage/
# Documentation
doc/html/
doc/latex/
- Use lowercase with underscores
- Be descriptive and concise
- Avoid generic names like
utilsorcommon - Use consistent prefixes for related packages (e.g.,
robot_driver,robot_msgs)
- Nodes should have clear, singular purposes
- Name reflects functionality (e.g.,
camera_driver_node, notnode1) - Use consistent naming:
<function>_nodepattern - Avoid abbreviations unless widely recognized
- Use lowercase with underscores
- Namespace related topics (e.g.,
/camera/image,/camera/info) - Use descriptive names that indicate data type
- Follow ROS naming conventions (REP-144)
- One node per executable in simple cases
- Use libraries for shared code across nodes
- Keep nodes focused and composable
- Separate concerns: drivers, logic, visualization
- Use composition for complex multi-node systems
- Always build from workspace root
- Source workspace overlay after every build
- Use
--symlink-installduring Python development - Run tests before committing changes
- Use release builds for deployment
- Declare all dependencies in
package.xml - Keep dependency lists minimal and explicit
- Prefer standard ROS packages over custom solutions
- Document non-ROS dependencies in README
- Use rosdep for system dependency management
- Document public APIs with docstrings/Doxygen
- Maintain package-level README files
- Keep CHANGELOG.md updated
- Document launch file parameters
- Include usage examples
- Never commit build artifacts
- Use meaningful commit messages
- Tag releases with semantic versioning
- Keep branches focused and short-lived
- Review code before merging
- Follow PEP 8 style guide
- Use type hints where appropriate
- Write docstrings for all public functions
- Maximum line length: 100 characters
- Follow ROS 2 C++ Style Guide
- Use modern C++ features (C++17)
- Document public APIs with Doxygen
- Use RAII and smart pointers
Run before committing:
# Python linting
ament_flake8 src/py_package/
ament_pep257 src/py_package/
# C++ linting
ament_cpplint src/cpp_package/
# Run tests
colcon test
colcon test-result --verbose- Create a feature branch from
main
git checkout -b feature/your-feature-name- Make changes and commit with descriptive messages
git commit -m "Add feature: brief description"- Ensure all tests pass
colcon test- Push branch and create pull request
git push origin feature/your-feature-name- Request review from maintainers
- Address review comments
- Merge after approval
<type>: <subject>
<body>
<footer>
Types: feat, fix, docs, style, refactor, test, chore
Example:
feat: add parameter configuration for publish rate
Allows users to configure the publishing rate through
a ROS parameter instead of hardcoded value.
Closes #123
cd ~/ros2_ws/src
ros2 pkg create --build-type [ament_python|ament_cmake] package_name \
--dependencies dep1 dep2 dep3Create a separate package for message definitions:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake my_msgsFile: src/my_msgs/msg/CustomMessage.msg
std_msgs/Header header
string data
float64 value
Update CMakeLists.txt:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/CustomMessage.msg"
DEPENDENCIES std_msgs
)Update package.xml:
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>File: src/my_msgs/srv/CustomService.srv
string request_data
---
bool success
string response_data
File: src/my_msgs/action/CustomAction.action
# Goal
string goal_data
---
# Result
bool success
---
# Feedback
float32 progress
Organize related packages under a common directory:
src/
├── my_project/
│ ├── my_project_core/ # Main logic
│ ├── my_project_drivers/ # Hardware interfaces
│ ├── my_project_msgs/ # Custom messages
│ ├── my_project_bringup/ # Launch files
│ └── my_project_description/ # URDF/meshes
Add Gazebo integration:
sudo apt install ros-humble-gazebo-ros-pkgsCreate simulation launch file:
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
IncludeLaunchDescription(
PythonLaunchDescriptionSource([
'/opt/ros/humble/share/gazebo_ros/launch/gazebo.launch.py'
])
),
Node(
package='py_package',
executable='talker_listener_node',
output='screen'
)
])pip install sphinx sphinx-rtd-theme
cd ~/ros2_ws/src/py_package
sphinx-quickstart docssudo apt install doxygen graphviz
cd ~/ros2_ws/src/cpp_package
doxygen -g Doxyfile
doxygen DoxyfileEach package should include:
README.mdwith package-specific details- API documentation (Sphinx/Doxygen)
- Usage examples
- Configuration parameters
- Known limitations
This project follows Semantic Versioning:
- MAJOR: Breaking API changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
See CHANGELOG.md for release history.
Example CHANGELOG.md:
# Changelog
## [1.0.0] - 2025-01-17
### Added
- Initial release
- Python and C++ example nodes
- Launch files
- Unit tests
- CI/CD pipeline
### Changed
- N/A
### Deprecated
- N/A
### Removed
- N/A
### Fixed
- N/A
### Security
- N/AReport security issues to: ookkshirsagar@gmail.com
Do not create public issues for security vulnerabilities.
- Keep dependencies updated
- Use rosdep for system packages
- Run security audits regularly
- Validate all external inputs
- Use secure communication when needed
Static analysis tools:
# C++ security checks
cppcheck --enable=all src/cpp_package/src/
# Python security checks
bandit -r src/py_package/This project is licensed under the MIT License.
MIT License
Copyright (c) 2025
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
This template is built on best practices from the ROS 2 community and incorporates patterns from successful production deployments.
For questions and support:
- Open an issue on GitHub
- Join ROS Discourse discussions
- Consult official ROS 2 documentation
Maintained by: Omkar Chandrakant Kshirsagar
Last Updated: 2026-01-17
ROS 2 Version: Humble / Iron / Jazzy