Skip to main content
ClaudeWave
Skill200 repo starsupdated 4d ago

ROS2 Lifecycle Nodes

ROS2 Managed (Lifecycle) Node implementation with Clean Architecture (Python & C++)

Install in Claude Code
Copy
git clone --depth 1 https://github.com/harunkurtdev/ros2-claude-code-template /tmp/ros2-lifecycle-nodes && cp -r /tmp/ros2-lifecycle-nodes/.claude/skills/ros2_lifecycle ~/.claude/skills/ros2-lifecycle-nodes
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# ROS2 Lifecycle Nodes Skill

## Lifecycle States

```
              +---------+
              | UNKNOWN |
              +----+----+
                   |
              create()
                   v
           +------+------+
           | UNCONFIGURED|<----+
           +------+------+     |
                  |            |
            configure()   cleanup()
                  v            |
           +------+------+     |
           |  INACTIVE  |------+
           +------+------+
                  |
            activate()
                  v
           +------+------+
           |   ACTIVE   |
           +------+------+
                  |
           deactivate()
                  v
           +------+------+
           |  INACTIVE  |
           +-------------+
```

## Python Implementation

See previous Python example.

## C++ Implementation

### Lifecycle Node Template

```cpp
// infrastructure/ros2/nodes/managed_node.hpp
#pragma once

#include <rclcpp/rclcpp.hpp>
#include <rclcpp_lifecycle/lifecycle_node.hpp>
#include <rclcpp_lifecycle/lifecycle_publisher.hpp>
#include <std_msgs/msg/string.hpp>

namespace infrastructure::ros2::nodes {

using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn;

class ManagedNode : public rclcpp_lifecycle::LifecycleNode {
public:
    explicit ManagedNode(const std::string& node_name, bool intra_process_comms = false)
        : rclcpp_lifecycle::LifecycleNode(node_name,
            rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms)) {}

    CallbackReturn on_configure(const rclcpp_lifecycle::State &) override {
        RCLCPP_INFO(get_logger(), "Configuring...");
        pub_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
        return CallbackReturn::SUCCESS;
    }

    CallbackReturn on_activate(const rclcpp_lifecycle::State &) override {
        RCLCPP_INFO(get_logger(), "Activating...");
        pub_->on_activate();
        // Start timers, etc.
        return CallbackReturn::SUCCESS;
    }

    CallbackReturn on_deactivate(const rclcpp_lifecycle::State &) override {
        RCLCPP_INFO(get_logger(), "Deactivating...");
        pub_->on_deactivate();
        return CallbackReturn::SUCCESS;
    }

    CallbackReturn on_cleanup(const rclcpp_lifecycle::State &) override {
        RCLCPP_INFO(get_logger(), "Cleaning up...");
        pub_.reset();
        return CallbackReturn::SUCCESS;
    }

    CallbackReturn on_shutdown(const rclcpp_lifecycle::State &) override {
        RCLCPP_INFO(get_logger(), "Shutting down...");
        return CallbackReturn::SUCCESS;
    }

private:
    rclcpp_lifecycle::LifecyclePublisher<std_msgs::msg::String>::SharedPtr pub_;
};

} // namespace
```

### Lifecycle Client (C++)

Allows controlling the state of another node using the `lifeycle_msgs` services.

```cpp
// infrastructure/ros2/lifecycle/lifecycle_client.hpp
#include <rclcpp/rclcpp.hpp>
#include <lifecycle_msgs/srv/change_state.hpp>
#include <lifecycle_msgs/srv/get_state.hpp>

class LifecycleClient {
public:
    LifecycleClient(rclcpp::Node::SharedPtr node, const std::string& target_node) {
        client_ = node->create_client<lifecycle_msgs::srv::ChangeState>(
            target_node + "/change_state");
    }

    bool change_state(std::uint8_t transition_id) {
        auto request = std::make_shared<lifecycle_msgs::srv::ChangeState::Request>();
        request->transition.id = transition_id;

        // ... async call logic ...
        return true;
    }

private:
    rclcpp::Client<lifecycle_msgs::srv::ChangeState>::SharedPtr client_;
};
```

## Best Practices

- **Resource Management**: Allocate resources (memory, connections) in `on_configure` and release them in `on_cleanup`.
- **Activity**: Only publish data or perform main logic when in the **ACTIVE** state.
- **Error Handling**: Use `on_error` callback to handle failures during transitions.
- **Launch Integration**: Use `LifecycleNode` in launch files to automatically manage states.
behaviortree-reviewerSubagent

Use proactively before opening a PR that adds or changes BehaviorTree.CPP nodes or BehaviorTree.ROS2 wrappers (RosActionNode/RosServiceNode/RosTopicPub/SubNode, TreeExecutionServer). Reviews a diff against BT.CPP v4 conventions — node base-class choice, non-blocking ticks, ports/blackboard typing, factory/plugin registration, XML v4, and the ROS 2 wrapper contract. Returns a punch list with file:line anchors, not a rewrite.

clean-arch-architectSubagent

Use when a design decision touches Clean Architecture boundaries in a ROS 2 project — which layer a new behaviour belongs to, whether a port belongs in domain or application, whether a new node should be lifecycle-managed, whether to compose nodes or split packages. Returns an architectural recommendation with trade-offs, not implementation.

ecs-architectSubagent

Use when a design decision touches the gz-sim ECS — where new state should live, which system phase should write it, how to avoid coupling, whether to add a component vs. a member variable, whether a new system should be split or merged with an existing one. Returns an architectural recommendation with trade-offs, not implementation.

gz-style-reviewerSubagent

Use proactively before opening any gz-sim PR. Reviews a diff against the project's C++17 style, ECS conventions, plugin registration patterns, CMake structure, test placement, Migration.md / Changelog.md expectations, and pre-commit configuration. Returns a punch list, not a rewrite.

ros2-controllers-reviewerSubagent

Use proactively before opening a PR that adds or changes a ros2_control controller, broadcaster, or hardware component (incl. URDF <ros2_control> bringup). Reviews a diff against ros2_controllers / ros2_control_demos conventions — controller & hardware lifecycle, command/state interface configuration, real-time safety of update()/read()/write(), generate_parameter_library usage, pluginlib registration, chainable-controller correctness, URDF wiring, and tests. Returns a punch list with file:line anchors, not a rewrite.

ros2-style-reviewerSubagent

Use proactively before opening any ROS 2 / Nav 2 PR. Reviews a diff against this template's Clean Architecture, ROS 2 communication, lifecycle, testing, and Nav 2 plugin conventions. Returns a punch list with file:line anchors, not a rewrite.

vda5050-reviewerSubagent

Use proactively before opening a PR that touches a VDA 5050 connector / fleet bridge. Reviews a diff against VDA 5050 v3.0.0 protocol compliance (topics, QoS, header rules, base/horizon, action state machine, schema validation) and the template's Clean Architecture for the MQTT↔Nav 2 bridge. Returns a punch list with file:line anchors, not a rewrite.

buildSlash Command

Build the colcon workspace (optionally a single package) and report the outcome.