Install any skill in seconds. Free to start, no credit card required.
Get Started Free →ROS2 Service and Action implementation with Clean Architecture (Python & C++)
.claude/skills/harunkurtdev-ros2-service-action/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 65% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-14 | ✗→✓ | ▲ Improved | -23% | 0% |
This skill provides a guide for implementing ROS2 Services and Actions adhering to Clean Architecture principles.
# srv/SetRobotMode.srv
# Request
string mode # "idle", "active", "emergency"
bool force_change # Force mode change
---
# Response
bool success
string message
string previous_modeSee previous Python example.
cpp// infrastructure/ros2/services/robot_mode_service.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include "robot_interfaces/srv/set_robot_mode.hpp" #include "domain/use_cases/set_robot_mode.hpp" namespace infrastructure::ros2::services { class RobotModeServiceNode : public rclcpp::Node { public: explicit RobotModeServiceNode( std::shared_ptr<domain::use_cases::SetRobotModeUseCase> use_case, const rclcpp::NodeOptions& options = rclcpp::NodeOptions()); private: void handle_set_mode( const std::shared_ptr<robot_interfaces::srv::SetRobotMode::Request> request, std::shared_ptr<robot_interfaces::srv::SetRobotMode::Response> response); std::shared_ptr<domain::use_cases::SetRobotModeUseCase> use_case_; rclcpp::Service<robot_interfaces::srv::SetRobotMode>::SharedPtr service_; }; } // namespace // infrastructure/ros2/services/robot_mode_service.cpp #include "infrastructure/ros2/services/robot_mode_service.hpp" namespace infrastructure::ros2::services { RobotModeServiceNode::RobotModeServiceNode( std::shared_ptr<domain::use_cases::SetRobotModeUseCase> use_case, const rclcpp::NodeOptions& options) : Node("robot_mode_service", options), use_case_(use_case) { using namespace std::placeholders; service_ = this->create_service<robot_interfaces::srv::SetRobotMode>( "/robot/set_mode", std::bind(&RobotModeServiceNode::handle_set_mode, this, _1, _2) ); } void RobotModeServiceNode::handle_set_mode( const std::shared_ptr<robot_interfaces::srv::SetRobotMode::Request> request, std::shared_ptr<robot_interfaces::srv::SetRobotMode::Response> response) { // Conversion from message to domain object would go here // ... // Execute use case // auto result = use_case_->execute(...); // Map result to response // response->success = result.success; } } // namespace
cpp// infrastructure/ros2/clients/robot_mode_client.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include "robot_interfaces/srv/set_robot_mode.hpp" namespace infrastructure::ros2::clients { class RobotModeClient { public: explicit RobotModeClient(rclcpp::Node::SharedPtr node); std::future<std::shared_ptr<robot_interfaces::srv::SetRobotMode::Response>> set_mode_async(const std::string& mode, bool force = false); private: rclcpp::Node::SharedPtr node_; rclcpp::Client<robot_interfaces::srv::SetRobotMode>::SharedPtr client_; }; } // namespace
# action/NavigateToPoint.action
# Goal
geometry_msgs/Point target_point
float32 max_velocity
bool avoid_obstacles
---
# Result
bool success
string message
float32 total_distance
float32 total_time
---
# Feedback
geometry_msgs/Point current_position
float32 distance_remaining
float32 estimated_time
string statuscpp// infrastructure/ros2/actions/navigation_action_server.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <rclcpp_action/rclcpp_action.hpp> #include "robot_interfaces/action/navigate_to_point.hpp" #include "domain/use_cases/navigate_to_point.hpp" namespace infrastructure::ros2::actions { class NavigationActionServer : public rclcpp::Node { public: using NavigateToPoint = robot_interfaces::action::NavigateToPoint; using GoalHandle = rclcpp_action::ServerGoalHandle<NavigateToPoint>; explicit NavigationActionServer( std::shared_ptr<domain::use_cases::NavigateToPointUseCase> use_case, const rclcpp::NodeOptions& options = rclcpp::NodeOptions()); private: rclcpp_action::GoalResponse handle_goal( const rclcpp_action::GoalUUID& uuid, std::shared_ptr<const NavigateToPoint::Goal> goal); rclcpp_action::CancelResponse handle_cancel( const std::shared_ptr<GoalHandle> goal_handle); void handle_accepted(const std::shared_ptr<GoalHandle> goal_handle); void execute(const std::shared_ptr<GoalHandle> goal_handle); std::shared_ptr<domain::use_cases::NavigateToPointUseCase> use_case_; rclcpp_action::Server<NavigateToPoint>::SharedPtr action_server_; }; } // namespace
cpp// infrastructure/ros2/clients/navigation_client.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <rclcpp_action/rclcpp_action.hpp> #include "robot_interfaces/action/navigate_to_point.hpp" namespace infrastructure::ros2::clients { class NavigationClient { public: using NavigateToPoint = robot_interfaces::action::NavigateToPoint; using GoalHandle = rclcpp_action::ClientGoalHandle<NavigateToPoint>; explicit NavigationClient(rclcpp::Node::SharedPtr node); std::shared_future<std::shared_ptr<GoalHandle>> navigate_to( double x, double y, double z, std::function<void(const NavigateToPoint::Feedback&)> feedback_cb); private: rclcpp::Node::SharedPtr node_; rclcpp_action::Client<NavigateToPoint>::SharedPtr client_; }; } // namespace
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 24,557 | 21,473 | -13% | 1 | 1 | 0% | 4,763 | 6,186 | +30% | 0 | 0 | — |
case-02 | pass→pass | 7,725 | 5,912 | -23% | 1 | 1 | 0% | 1,310 | 2,645 | +102% | 0 | 0 | — |
case-03 | fail→pass | 15,212 | 12,282 | -19% | 1 | 1 | 0% | 2,776 | 3,921 | +41% | 0 | 0 | — |
case-04 | pass→pass | 15,239 | 8,339 | -45% | 1 | 1 | 0% | 2,929 | 3,105 | +6% | 0 | 0 | — |
case-05 | fail→fail | 17,297 | 16,533 | -4% | 1 | 1 | 0% | 3,207 | 4,847 | +51% | 0 | 0 | — |
case-06 | fail→pass | 8,528 | 5,839 | -32% | 1 | 1 | 0% | 1,565 | 2,589 | +65% | 0 | 0 | — |
case-07 | pass→pass | 13,396 | 9,175 | -32% | 1 | 1 | 0% | 2,459 | 3,242 | +32% | 0 | 0 | — |
case-08 | pass→pass | 8,551 | 5,812 | -32% | 1 | 1 | 0% | 1,726 | 2,769 | +60% | 0 | 0 | — |
case-09 | pass→pass | 12,284 | 10,430 | -15% | 1 | 1 | 0% | 2,257 | 3,587 | +59% | 0 | 0 | — |
case-10 | fail→fail | 15,527 | 11,436 | -26% | 1 | 1 | 0% | 2,835 | 3,581 | +26% | 0 | 0 | — |
case-11 | fail→pass | 6,474 | 2,098 | -68% | 1 | 1 | 0% | 1,103 | 1,931 | +75% | 0 | 0 | — |
case-12 | fail→pass | 8,976 | 5,241 | -42% | 1 | 1 | 0% | 1,554 | 2,452 | +58% | 0 | 0 | — |
case-13 | pass→pass | 15,322 | 2,638 | -83% | 1 | 1 | 0% | 2,636 | 2,008 | -24% | 0 | 0 | — |
case-14 | fail→pass | 15,650 | 2,992 | -81% | 1 | 1 | 0% | 2,683 | 2,079 | -23% | 0 | 0 | — |
case-15 | fail→pass | 10,193 | 4,765 | -53% | 1 | 1 | 0% | 1,717 | 2,352 | +37% | 0 | 0 | — |
case-16 | pass→pass | 3,917 | 3,621 | -8% | 1 | 1 | 0% | 622 | 2,200 | +254% | 0 | 0 | — |
case-17 | fail→pass | 6,539 | 2,170 | -67% | 1 | 1 | 0% | 1,148 | 1,959 | +71% | 0 | 0 | — |
case-18 | fail→pass | 9,435 | 2,115 | -78% | 1 | 1 | 0% | 1,560 | 1,965 | +26% | 0 | 0 | — |
case-19 | fail→fail | 10,117 | 8,409 | -17% | 1 | 1 | 0% | 1,647 | 3,028 | +84% | 0 | 0 | — |
case-20 | pass→pass | 17,271 | 14,225 | -18% | 1 | 1 | 0% | 3,447 | 4,403 | +28% | 0 | 0 | — |
case-21 | pass→pass | 11,641 | 11,643 | +0% | 1 | 1 | 0% | 2,155 | 3,815 | +77% | 0 | 0 | — |
case-22 | pass→pass | 13,172 | 11,236 | -15% | 1 | 1 | 0% | 2,520 | 3,723 | +48% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +36 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.