Skip to main content
ClaudeWave
Skill200 estrellas del repoactualizado 4d ago

behaviortree_node_creation

Scaffold a Behavior Tree leaf node — plain BehaviorTree.CPP (SyncActionNode / StatefulActionNode / ConditionNode) or a BehaviorTree.ROS2 wrapper (RosActionNode / RosServiceNode / RosTopicPubNode / RosTopicSubNode) — with ports, factory/plugin registration, and XML v4 usage. Trigger when the user asks to write a behavior-tree node (not Nav 2-specific).

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/harunkurtdev/ros2-claude-code-template /tmp/behaviortree_node_creation && cp -r /tmp/behaviortree_node_creation/.claude/skills/behaviortree_node_creation ~/.claude/skills/behaviortree_node_creation
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# Writing a Behavior Tree Node (BehaviorTree.CPP + ROS 2)

How to write, register, and use a custom behavior-tree leaf node — both
plain BehaviorTree.CPP nodes and the ROS 2 wrappers from
BehaviorTree.ROS2.

- Core library reference: `rules/behaviortree_cpp.md`.
- ROS 2 wrappers reference: `rules/behaviortree_ros2.md`.
- Copy from the real samples:
  - plain → `~/nav2_ws/src/BehaviorTree.CPP/sample_nodes/`
    (`dummy_nodes`, `movebase_node`)
  - ROS 2 → `~/nav2_ws/src/BehaviorTree.ROS2/btcpp_ros2_samples/src/`
    (`sleep_action`, `set_bool_node`)
- Nav 2-specific BT nodes: use skill `nav2_behavior_tree` instead.

## First decision: which base class?

| Your node… | Base class | Library |
|------------|-----------|---------|
| does quick work, finishes in one tick | `BT::SyncActionNode` | BT.CPP |
| is async / long-running (no ROS) | `BT::StatefulActionNode` | BT.CPP |
| is a boolean check | `BT::ConditionNode` | BT.CPP |
| calls a ROS 2 **action** | `BT::RosActionNode<ActionT>` | BT.ROS2 |
| calls a ROS 2 **service** | `BT::RosServiceNode<ServiceT>` | BT.ROS2 |
| **publishes** to a topic | `BT::RosTopicPubNode<MsgT>` | BT.ROS2 |
| **subscribes** to a topic | `BT::RosTopicSubNode<MsgT>` | BT.ROS2 |

Golden rule: **never block in a tick**. Sync nodes return immediately;
async/ROS nodes return `RUNNING` and resume on the next tick.

## A. Plain BT.CPP node

```cpp
#include "behaviortree_cpp/bt_factory.h"

class SaySomething : public BT::SyncActionNode {
public:
  SaySomething(const std::string& name, const BT::NodeConfig& config)
    : BT::SyncActionNode(name, config) {}

  static BT::PortsList providedPorts() {                 // omit if no ports
    return { BT::InputPort<std::string>("message") };
  }
  BT::NodeStatus tick() override {
    auto msg = getInput<std::string>("message");
    if (!msg) throw BT::RuntimeError("missing 'message': ", msg.error());
    std::cout << msg.value() << "\n";
    return BT::NodeStatus::SUCCESS;
  }
};
```

Async work → `StatefulActionNode`:

```cpp
class MoveBase : public BT::StatefulActionNode {
public:
  static BT::PortsList providedPorts() { return { BT::InputPort<Pose2D>("goal") }; }
  BT::NodeStatus onStart() override;     // kick off; return RUNNING (or SUCCESS if instant)
  BT::NodeStatus onRunning() override;   // poll; return RUNNING until done, then SUCCESS/FAILURE
  void onHalted() override;              // cancellation cleanup
};
```

## B. ROS 2 wrapper node (action example)

```cpp
#include "behaviortree_ros2/bt_action_node.hpp"
#include "btcpp_ros2_interfaces/action/sleep.hpp"
using namespace BT;

class SleepAction : public RosActionNode<btcpp_ros2_interfaces::action::Sleep> {
public:
  SleepAction(const std::string& name, const NodeConfig& conf, const RosNodeParams& params)
    : RosActionNode<btcpp_ros2_interfaces::action::Sleep>(name, conf, params) {}

  static PortsList providedPorts() {
    return providedBasicPorts({ InputPort<unsigned>("msec") });   // + the action-name port
  }
  bool setGoal(Goal& goal) override {                 // BT input → goal; false ⇒ INVALID_GOAL
    goal.msec_timeout = getInput<unsigned>("msec").value();
    return true;
  }
  NodeStatus onResultReceived(const WrappedResult& wr) override {
    return wr.result->done ? NodeStatus::SUCCESS : NodeStatus::FAILURE;
  }
  NodeStatus onFeedback(const std::shared_ptr<const Feedback> fb) override {
    return NodeStatus::RUNNING;   // must NOT return IDLE
  }
  NodeStatus onFailure(ActionNodeErrorCode error) override {
    RCLCPP_ERROR(logger(), "Sleep failed: %d", error);
    return NodeStatus::FAILURE;
  }
};
```

Service nodes override `setRequest()` + `onResponseReceived()`; pub nodes
override `setMessage()`; sub nodes handle the received message.

## Registration

Plain BT.CPP:
```cpp
factory.registerNodeType<SaySomething>("SaySomething");
// free function → factory.registerSimpleAction("CheckBattery", std::bind(CheckBattery));
// plugin: BT_REGISTER_NODES(factory){ factory.registerNodeType<MyNode>("MyNode"); }
//         → factory.registerFromPlugin("libmy_nodes.so");
```

ROS 2 (needs `RosNodeParams` to carry the node handle / timeouts):
```cpp
BT::RosNodeParams params;
params.nh = node;                       // std::shared_ptr<rclcpp::Node>
params.default_port_value = "sleep_action";   // default action/service/topic name
factory.registerNodeType<SleepAction>("SleepAction", params);
// or as a plugin:  CreateRosNodePlugin(SleepAction, "SleepAction");
//                  RegisterRosNode(factory, "libsleep_action.so", params);
```

## Use it in a tree (XML v4)

```xml
<root BTCPP_format="4">
  <BehaviorTree ID="MainTree">
    <Sequence>
      <SaySomething message="starting"/>
      <SleepAction msec="2000"/>
    </Sequence>
  </BehaviorTree>
</root>
```

```cpp
factory.registerBehaviorTreeFromFile("main.xml");
auto tree = factory.createTree("MainTree");
tree.tickWhileRunning();   // ROS nodes: spin the node in parallel / use the executor
```

To expose a whole tree as a ROS 2 action, subclass
`BT::TreeExecutionServer` and override `registerNodesIntoFactory()` (see
`behaviortree_ros2.md`).

## Ports & blackboard

- `getInput<T>("name")` returns `BT::Expected<T>` — always check it.
- `setOutput("name", value)` writes to the blackboard.
- `{key}` in XML binds a port to a blackboard entry.
- For custom port types, specialize `BT::convertFromString<T>(StringView)`.
- Reserved port names: `name`, `ID`, anything starting with `_`.

## Common pitfalls

- **Blocking in `tick()` / `onRunning()`** — defeats the tree; return
  `RUNNING` and resume next tick.
- **`onFeedback` returning `IDLE`** — not allowed (throws); return
  `RUNNING` (or SUCCESS/FAILURE to stop early).
- **Forgetting `providedPorts()`** when the XML uses ports, or a name
  mismatch between `providedPorts` and the XML attribute.
- **Registering a ROS wrapper without `RosNodeParams`** — it needs the
  node handle; use the `(factory, params)` overload / `RegisterRosNode`.
- **Mixing v3 and v4 XML** —
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.