6. How to create a new Action or Condition node - fkie/fkie_behavior_trees GitHub Wiki

Actions in Behavior Tree Framework are loaded as plugins at run-time, and because of that, they must be compiled inside a shared C++ library.

This tutorial aims to explain the step-by-step process of creating a new BT Action or Condition.

Tutorial: Create the HelloWordAction

We want to create a new action that prints Hello world messages in the console. The final package code is available on fkie_bt_tutorials. In this tutorial, we will show only the relevant components, and things to take into account when creating new actions or conditions.

1. Create/Define a package to host the new action

In our case, we have created the package fkie_bt_tutorials. Remember that the package should have as dependency behaviortree_cpp_v3 as well as fkie_behavior_tree_manager.

CMakeLists.txt

find_package(BehaviorTreeV3)

package.xml

<depend>behaviortree_cpp_v3</depend>
<depend>fkie_behavior_tree_manager</depend>

2. Create a C++ class for the action:

The [HelloWorldAction] is located in HelloWorldAction.hpp.

The class might inherit from BT::SyncActionNode if an action is needed, or from BT::ConditionNode for a condition.

class HelloWorldAction : public BT::SyncActionNode

Hint: If you want to program actions that lock the execution of the tree, it is advisable to inherit from BT::ActionNodeBase, and implement the method halt(). This method will be called if the user wants to interrupt the tree execution.

The action must provide a list of Ports in a static function providedPorts(). Port behaves as blackboard variables that shares information between BT nodes. There are 3 types of ports: InputPort if you want to read, OutputPort if you want to write and BidirectionalPort for read/write access.

static BT::PortsList providedPorts()
{
  BT::PortsList ports = {
    BT::InputPort<std::string>("message_in"),
    BT::OutputPort<std::string>("message_out")};
  return ports;
}

Finally, we reach the tick() function that works like a callback. This is called by the BT manager, depending on the tree hierarchy and the current execution state. In this function the real action task is executed, such as calling action servers, publishing messages, computing paths etc... The function must return the final state of the node: SUCCESS, FAILURE or RUNNING.

BT::NodeStatus tick() override
{
    ...
    return BT::NodeStatus::SUCCESS;
}

3. Register the action

An important step is to register the action [HelloWorldAction], so that, the BT manager can find it in run-time.

The relevant files are [TutorialActions.h] and [TutorialActions.cpp]

...
BT_REGISTER_NODES(factory)
{
  BTTutorialGroup::RegisterNodes(factory);
}
#include "fkie_bt_tutorials/actions/HelloWorldAction.hpp"
...
inline void RegisterNodes(BT::BehaviorTreeFactory &factory)
{
  // Register the action [HelloWorldAction] with the same name
  factory.registerNodeType<HelloWorldAction>("HelloWorldAction");
}

4. Build the action

We need to compile our HelloWorldAction as part of a shared-library. But depending on the usage, it can also be compiled as a static library. In this tutorial, we have created a library called TutorialActions_dyn that contains the HelloWorldAction.

# static library
add_library(TutorialActions  STATIC src/TutorialActions.cpp )
target_link_libraries(TutorialActions PRIVATE ${catkin_LIBRARIES} BT::behaviortree_cpp_v3)
set_target_properties(TutorialActions PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} )

# plugin for run-time
add_library(TutorialActions_dyn SHARED src/TutorialActions.cpp )
target_link_libraries(TutorialActions_dyn PRIVATE ${catkin_LIBRARIES} BT::behaviortree_cpp_v3)
target_compile_definitions(TutorialActions_dyn  PRIVATE BT_PLUGIN_EXPORT)
set_target_properties(TutorialActions_dyn PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} )

install(
  TARGETS TutorialActions TutorialActions_dyn
  ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
  LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

Now we can compile our new action:

cd path-to-project
catkin build --this

5. Running the action

As an example, we have added three instances of our action HelloWorldAction running in a sequence, so the tree file looks like:

<root main_tree_to_execute="BehaviorTree">
  <BehaviorTree ID="BehaviorTree">
    <Sequence name="Main loop">
      <!-- Execute action without defining ports -->
      <Action ID="HelloWorldAction" name="hello_world_no_message_in" />

      <!-- Execute action with constant [message_in] value -->
      <Action ID="HelloWorldAction" name="hello_world_constant_message_in" message_in="Hi constant World" message_out="{message_out}"/>

      <!-- Syntax: message_out="{message_out}" remaps ports -->
      <Action ID="HelloWorldAction" name="hello_world_message_port" message_in="{message_out}" message_out="{message_out}"/>
    </Sequence>
  </BehaviorTree>
</root>

Then, we have created a launch file containing a node running the behavior_tree_manager_node and a launcher for Groot. We also need to specify the path to the tree/static_file and load the config file parameters. The launch file is located in 1_hello_world_tutorial.launch.

<launch>
  <!-- launch Groot: Assumes that executable is located at ~/ros/external/Groot/build/Groot -->
  <node name="load_groot" pkg="fkie_behavior_tree_manager" type="run_groot.sh" output="screen"/>

  <!-- load behavior tree manager -->
  <node name="bt_manager" pkg="fkie_behavior_tree_manager" type="behavior_tree_manager_node" clear_params="true" output="screen">
    <!-- Load tree XML file -->
    <param name="tree/static_file" value="$(find fkie_bt_tutorials)/config/hello_world_tree.xml"/>

    <!-- Load parameters -->
    <rosparam command="load" file="$(find fkie_bt_tutorials)/config/hello_world_config.yaml" />
  </node>
</launch>

Important: In the configuration file, we have to add the corresponding share library libTutorialActions_dyn to the parameter action_plugins:

action_plugins: ["libTutorialActions_dyn.so"]

6. Monitoring the tree execution using Groot

Once the node bt_manager is running, we can execute Groot to monitor its current tree state. Note that, if we want to use Groot, we need to add ZMQ_LOGGER as logger_type.

recording_sample

The console output should print something like:

ros.BTTutorialGroup.HelloWorldAction: message_in: Hello World
ros.BTTutorialGroup.HelloWorldAction: message_in: Hi constant World
ros.BTTutorialGroup.HelloWorldAction: message_in: BT Tutorials: HelloWorldAction

If you notice that the message_in text changes, just have a look into remapping ports.

⚠️ **GitHub.com Fallback** ⚠️