Stack conventions
ROS 2 (robotics)
Robot software on ROS 2 (Jazzy or Humble): C++ (rclcpp) and Python (rclpy) packages built with colcon and ament, with launch files, parameters, simulation tests and the safety rules of software that moves hardware.
Robot software on ROS 2 (Jazzy or Humble): C++ (rclcpp) and Python (rclpy) packages built with colcon and ament, with launch files, parameters, simulation tests and the safety rules of software that moves hardware.
Used for: ros2.
Principles
- Keep one responsibility per node and one package per deployable unit; nodes communicate only through topics, services, actions and parameters with declared interfaces.
- Separate pure robot logic (kinematics, control laws, planners, state machines) from ROS plumbing so it is unit-testable without a running graph.
- Declare every parameter with a type, a range and a description (rcl_interfaces ParameterDescriptor) and validate it in a parameter callback.
- Choose QoS deliberately per topic - reliable for commands and state, best effort for high-rate sensor data - and document it at the interface.
- Treat safety as a design input - a watchdog stops actuators on lost commands or stale sensors, limits are enforced in software below the planner, and an emergency stop path never depends on the network.
- Prefer composable nodes (rclcpp_components) and lifecycle (managed) nodes for anything that starts hardware, so bring-up and shutdown are ordered and observable.
- Use the ROS clock (node->now(), get_clock()) for all time so the same code runs in simulation and on the robot.
- Keep callbacks short and non-blocking; long work goes to a separate thread or an action server.
- Pin the ROS distribution and every dependency in package.xml so rosdep can install exactly what a package needs.
Conventions
Accessibility
Concurrency
- Use executors and callback groups deliberately - MutuallyExclusive for shared state, Reentrant only with locking - and never block an executor thread.
- Protect state shared between callbacks and worker threads with a mutex; keep lock scopes short.
- Do not spin inside a callback; use async service calls and futures.
Dependencies
- Resolve system dependencies with rosdep from package.xml; vendor nothing that a rosdep key provides.
- Pin the ROS distribution in CI and container images (ros:jazzy-ros-base by digest), and upgrade distributions deliberately.
Documentation
- Document every node's topics, services, actions and parameters (with types, units, QoS and defaults) in the package README.
- Keep a launch and bring-up section (rosdep install, colcon build, ros2 launch ...) current.
Errors
- Never let an exception escape a callback; catch it, log it with RCLCPP_ERROR or the node's logger, and move the node to a safe state.
- Check service and action results and timeouts; a missing server is an expected condition, not a crash.
- Refuse invalid parameters in the parameter callback (SetParametersResult with successful=false and a reason) instead of clamping silently.
Logging
- Log through the node's logger (RCLCPP_INFO_THROTTLE / get_logger().info) with throttling in high-rate callbacks; never printf or print in nodes.
- Publish diagnostics (diagnostic_updater) for hardware and health so operators see state on /diagnostics, and keep logs free of secrets.
Naming
- Package, node, topic, service and parameter names in snake_case; topics relative (no leading slash) so namespaces and remapping work.
- Message, service and action types in PascalCase in their own *_msgs / *_interfaces package.
- Frames follow REP 105 (base_link, odom, map) and units follow REP 103 (SI units, radians, right-handed coordinates).
Performance
- Avoid allocation and blocking calls in control loops; use real-time-safe patterns (preallocated messages, realtime_tools) where timing matters.
- Use intra-process communication and loaned messages for large data; choose sensor-data QoS for high-rate streams.
- Measure latency and jitter with ros2 topic hz/delay and tracing (ros2_tracing) before optimising.
Security
- Enable SROS2 (DDS Security) with per-node enclaves and access control for robots on shared or untrusted networks; set ROS_DOMAIN_ID and ROS_AUTOMATIC_DISCOVERY_RANGE deliberately.
- Validate every incoming message (sizes, ranges, NaN and infinity, frame ids) before it reaches control code; reject commands above declared limits.
- Keep secrets (keystores, certificates, cloud credentials) out of packages and launch files; mount them at run time and never log them.
- Never pass message or parameter content to a shell; launch processes with an argument list (command injection).
- Keep dependencies patched and pinned through rosdep keys and package.xml versions; review new dependencies.
Structure
- A colcon workspace with packages under src/ (or services/), each with package.xml (format 3), CMakeLists.txt (ament_cmake) or setup.py/setup.cfg (ament_python), and a README.
- Keep interfaces (msg, srv, action) in a separate package so they build once and every language can use them.
- launch/ for launch files, config/ for parameter YAML, include/
/ and src/ for C++, / for Python, test/ for tests. - Declare every dependency in package.xml (depend, build_depend, exec_depend, test_depend) so rosdep resolves it; never rely on what happens to be installed.
Testing
- Unit-test the logic core without ROS (ament_cmake_gtest or pytest); test nodes with launch_testing for the integration of topics, services and parameters.
- Run ament_lint_auto (cpplint, clang-format or uncrustify, flake8, xmllint, copyright) through colcon test in CI.
- Test controllers in simulation (Gazebo or a mock hardware interface such as ros2_control mock components) before hardware; record and replay bags for perception regressions.
- Name tests after the behaviour (
TEST(Watchdog, StopsWhenCommandsAreStale)), one behaviour per test. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Is the logic separated from ROS plumbing and unit-tested without a running graph?
- Does every node that commands actuators have a watchdog and enforce limits below the planner?
- Are parameters declared with types, ranges and descriptions, and validated on change?
- Is QoS chosen deliberately for each new topic and documented?
- Is all time taken from the ROS clock, so the code runs in simulation and bag replay?
- Are callbacks short and non-blocking, with callback groups used correctly?
- Are incoming messages validated (sizes, ranges, NaN, frames) before control code uses them?
- Are all dependencies declared in package.xml and resolvable by rosdep?
- Do colcon test and the ament linters pass, including launch tests for changed interfaces?
- Are topics relative, frames per REP 105 and units per REP 103?
- Are secrets and keystores kept out of packages and launch files?
- Is the README of each changed package current (interfaces, parameters, launch)?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
clang-format |
format | clang-format | no |
ruff |
lint | ruff | no |
ament-lint |
lint | ament_lint (cpplint, xmllint, copyright) | no |
rosdep-check |
static | rosdep | no |
osv-scanner |
security | osv-scanner | no |
gitleaks |
security | gitleaks | no |