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.
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.
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>
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;
}
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");
}
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
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"]
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
.
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.