ROS2 Lifecycle Nodes
ROS2 Managed (Lifecycle) Node implementation with Clean Architecture (Python & C++)
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-nodesSKILL.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.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.
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.
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.
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.
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.
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.
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.
Build the colcon workspace (optionally a single package) and report the outcome.