../_images/tiagopro-icon.png ../_images/kangaroo-icon.png ../_images/tiago-icon.png ../_images/ari-icon.png ../_images/talos-icon.png ../_images/mobile-bases-icon.png

🧩 Goal Navigation Plugins#

In this section you’ĺl learn about the creation of a plugin for the bt_navigator and how to integrate it into the Behavior Tree used for the Goal Navigation.

How to create your plugin for BT Navigator#

In this tutorial, you are going to learn how to create a Behavior Tree Action Node that can be used in any BT implementing a Navigation logic. This Action Node will simply print a log with a different level of verbosity for debugging purposes. Apart from developing a Behavior Tree Action Node, you can also create any other type of BT Node such as decorator, condition, and control nodes. Each node type has a unique role in the BT to perform actions like planning, control the flow of the BT, check the status of a condition, or modify the output of other BT nodes.

For this tutorial, we are going to use a new ROS 2 package called goal_navigation_tutorial, in which we are going to create a new C++ source file called log_printer_node.cpp.

#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "behaviortree_cpp_v3/action_node.h"

namespace goal_navigation_tutorial
{
/**
* @brief A BT node that logs a message to the console
* with a specific log level that could be INFO, WARN, ERROR.
*/
class LogPrinter : public BT::AsyncActionNode
{
public:
   /**
   * @brief A constructor for goal_navigation_tutorial::LogPrinter
   *
   * @param xml_tag_name Name for the XML tag for this node
   * @param conf  BT node configuration
   */
   LogPrinter(
      const std::string & xml_tag_name,
      const BT::NodeConfiguration & conf)
   : BT::AsyncActionNode(xml_tag_name, conf)
   {
      node_ = config().blackboard->get<rclcpp::Node::SharedPtr>("node");
      getInput("log_text", log_text_);
      getInput("log_level", log_level_);

      if (log_text_.empty()) {
            RCLCPP_WARN(node_->get_logger(), "LogPrinter: log_text is empty");
      }
      if (log_level_.empty()) {
            RCLCPP_WARN(node_->get_logger(), "LogPrinter: log_level is empty, setting to INFO");
            log_level_ = "INFO";
      }
   }

   /**
   * @brief Creates list of BT ports
   * @return BT::PortsList Containing basic ports along with node-specific ports
   */
   static BT::PortsList providedPorts()
   {
      return
      {
            BT::InputPort<std::string>("log_text", "Text to be logged"),
            BT::InputPort<std::string>("log_level", "Log level (INFO, WARN, ERROR)"),
      };
   }

private:
   /**
   * @brief Function to perform some user-defined operation on tick
   */
   BT::NodeStatus tick() override
   {
      if (log_level_ == "INFO") {
            RCLCPP_INFO(node_->get_logger(), "%s", log_text_.c_str());
            return BT::NodeStatus::SUCCESS;
      } else if (log_level_ == "WARN") {
            RCLCPP_WARN(node_->get_logger(), "%s", log_text_.c_str());
            return BT::NodeStatus::SUCCESS;
      } else if (log_level_ == "ERROR") {
            RCLCPP_ERROR(node_->get_logger(), "%s", log_text_.c_str());
            return BT::NodeStatus::SUCCESS;
      } else {
            RCLCPP_ERROR(node_->get_logger(), "Invalid log level: %s", log_level_.c_str());
            return BT::NodeStatus::FAILURE;
      }
   }

   std::string log_text_;
   std::string log_level_;
   rclcpp::Node::SharedPtr node_;
};

}  // namespace goal_navigation_tutorial

#include "behaviortree_cpp_v3/bt_factory.h"

BT_REGISTER_NODES(factory)
{
   factory.registerNodeType<goal_navigation_tutorial::LogPrinter>("LogPrinter");
}

Compile and Install#

To compile and install the plugin, the file CMakeLists.txt should declare the following (minimum) dependencies:

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(behaviortree_cpp_v3 REQUIRED)

set(dependencies
     rclcpp
     rclcpp_lifecycle
     behaviortree_cpp_v3
)
include_directories(
   ${rclcpp_INCLUDE_DIRS}
   ${rclcpp_components_INCLUDE_DIRS}
   ${behaviortree_cpp_v3_INCLUDE_DIRS}
)

To compile and install the plugin, you need to add and install a new library

add_library(pal_log_printer_bt_node SHARED plugins/action/log_printer_node.cpp)
list(APPEND plugin_libs pal_log_printer_bt_node)

install(TARGETS ${plugin_libs}
   ARCHIVE DESTINATION lib
   LIBRARY DESTINATION lib
   RUNTIME DESTINATION bin
)

foreach(bt_plugin ${plugin_libs})
ament_target_dependencies(${bt_plugin} ${dependencies})
target_compile_definitions(${bt_plugin} PRIVATE BT_PLUGIN_EXPORT)
endforeach()

Finally, the package.xml file should include the required dependencies:

<depend>rclcpp</depend>
<depend>rclcpp_components</depend>
<depend>behaviortree_cpp_v3</depend>

Add your custom plugin to the Behavior Tree#

To add your custom plugin to the Behavior Tree used by the bt_navigator, you need to create a new XML file

In order to do this you first need to create a new directory in the .pal folder of your robot.

mkdir -p ~/.pal/pal_navigation_cfg_params/params/nav2_bt_navigator

And then, within this folder, you can create a new XML file for your custom Behavior Tree.

touch ~/.pal/pal_navigation_cfg_params/params/nav2_bt_navigator/my_custom_bt_w_custom_plugin.xml

Now you can generate the XML file with the following content:

my_custom_bt_w_custom_plugin.xml#
<!--
   This Behavior Tree adds a custom plugin to the default Nav2 BT Navigator Behavior Tree.
   The custom plugin is a LogPrinter node that logs a message with a specific log level.
-->
<root main_tree_to_execute="MainTree">
   <BehaviorTree ID="MainTree">
      <RecoveryNode number_of_retries="6" name="NavigateRecovery">
         <PipelineSequence name="NavigateWithReplanning">
         <RateController hz="1.0">
            <RecoveryNode number_of_retries="1" name="ComputePathToPose">
               <ComputePathToPose goal="{goal}" path="{path}" planner_id="GridBased"/>
               <ClearEntireCostmap name="ClearGlobalCostmap-Context" service_name="global_costmap/clear_entirely_global_costmap"/>
            </RecoveryNode>
         </RateController>
         <RecoveryNode number_of_retries="1" name="FollowPath">
            <FollowPath path="{path}" controller_id="FollowPath"/>
            <ClearEntireCostmap name="ClearLocalCostmap-Context" service_name="local_costmap/clear_entirely_local_costmap"/>
         </RecoveryNode>
         </PipelineSequence>
         <ReactiveFallback name="RecoveryFallback">
         <GoalUpdated/>
         <RoundRobin name="RecoveryActions">
            <Sequence name="ClearingActions">
               <LogPrinter log_text="executing clear costmaps" log_level="WARN">
               <ClearEntireCostmap name="ClearLocalCostmap-Subtree" service_name="local_costmap/clear_entirely_local_costmap"/>
               <ClearEntireCostmap name="ClearGlobalCostmap-Subtree" service_name="global_costmap/clear_entirely_global_costmap"/>
            </Sequence>
            <LogPrinter log_text="executing spin wait and backup beahviors" log_level="WARN">
            <Spin spin_dist="1.57"/>
            <Wait wait_duration="5"/>
            <BackUp backup_dist="0.30" backup_speed="0.05"/>
         </RoundRobin>
         </ReactiveFallback>
      </RecoveryNode>
   </BehaviorTree>
</root>

In this Behavior Tree it has been added a custom plugin, the LogPrinter node, it has two input ports, log_text and log_level, that allow to specify the text to be logged and the log level (INFO, WARN, ERROR).

LogPrinter node#
<LogPrinter log_text="text to print" log_level="INFO">

Configuration#

Now you can configure the bt_navigator, to use the custom plugin and custom Behavior Tree you created. To do so, you first need to create a new directory in the .pal folder of your robot.

mkdir -p ~/.pal/pal_navigation_cfg_params/params/nav2_bt_navigator

And then, within this folder, you can create a new BT Navigator configuration file.

touch ~/.pal/pal_navigation_cfg_params/params/nav2_bt_navigator/my_bt_navigator_config.yaml

Within the newly created my_bt_navigator_config.yaml file, you can insert your custom bt_navigator parameters. For the list of available parameters, you can refer to the Nav2 BT Navigator configuration guide. When creating a new Navigation configuration file, apart from the node parameters, you also need to specify the robot to which they refer.

pal_navigation_cfg:
   ros__parameters:
      supported_robots:
      - some_pal_robot # e.g. tiago, omni_base, tiago_pro, etc.

bt_navigator:
   ros__parameters:
      # Your BT Navigator Parameters
      default_nav_to_pose_bt_xml: /home/pal/.pal/pal_navigation_cfg_params/params/nav2_bt_navigator/my_custom_bt_w_custom_plugin.xml # Full path to your custom BT XML file

      # Other parameters

      plugin_lib_names:
      # List of plugin libraries to load
      - pal_log_printer_bt_node

Once created, to start using your custom BT Navigator configuration, you need to set it in the navigation pipeline by modifying the <robot>_nav.yaml file in the <robot>_2dnav package.

sudo vi /opt/pal/$PAL_DISTRO/share/<robot>_2dnav/params/<robot>_nav.yaml

And then, change the params field of the bt_navigator to the name of your custom BT Navigator configuration file.

nodes:
   # Other nodes
   bt_navigator:
      app: bt_navigator
      params: [my_bt_navigator_config]

Attention

Make sure that the name of the custom BT Navigator configuration file you use in the params field (my_bt_navigator_config) is the same as the name of the file you created in the .pal folder (my_bt_navigator_config.yaml).

Finally, to apply the changes and start using the new BT Navigator configuration, you need to restart the navigation pipeline with the command:

pal module restart navigation

See also#

To continue learning about the creation of Behavior Trees nodes used in Nav2, you can check Writing a New Behavior Tree Plugin.