# Behavior Tree Implementation for Autonomous Navigation This section provides an overview of the behavior tree (BT) implementation for autonomous navigation, highlighting its components and functionality. --- ## Introduction The behavior tree controls the robot's navigation and mapping process by integrating sensor data, decision-making logic, and motor control actions. The tree handles user input, obstacle detection, turning, and map completion while ensuring robustness and modularity. --- [![Behavior Tree PDF](https://github.com/user-attachments/assets/ca344831-4801-4433-8673-062602a4e712)](https://github.com/user-attachments/files/17838925/Behavior_Tree.pdf) --- ## Key Features 1. **Modularity**: Each task or decision point is encapsulated in individual nodes, enabling easy maintenance and updates. 2. **Retry Mechanisms**: Ensures robustness by retrying failed tasks until successful. 3. **Integration**: Combines sensor inputs (IMU, sonar, map data) and control outputs (ESC commands) for seamless navigation. --- ## Behavior Tree Components ### 1. **Initialization** - **`WaitForSeconds`**: Delays the system startup to allow hardware and sensors to stabilize. - **`StartPos`**: Extracts the starting position from the map to initialize localization. ### 2. **User Input Handling** - **`CheckStart`**: Monitors for the "start" signal from the web interface. Navigation begins once the user initiates the process. ### 3. **Navigation Logic** #### Obstacle Handling - **`CheckFront`**: Ensures the path ahead is clear. If an obstacle is detected, the robot: - Executes a **`Turn90Degrees`** action to avoid the obstacle. - Stops the motors temporarily for safety. #### Forward Navigation - **`AdjustAngle`**: Aligns the robot’s orientation with respect to walls for accurate forward motion. - **`Forward`**: Commands the robot to move forward when the path is clear. #### Map Completion - **`MapFinished`**: Checks if the robot has returned to the starting position, indicating map completion. ### 4. **Stopping Logic** - **`StopMotors`**: Ensures the robot halts safely at any point. - **`Stop`**: Executes the final stopping sequence after completing navigation. ### 5. **Logging** - **`PublishLog`**: Logs significant events (e.g., user input, mapping completion) for debugging and record-keeping. --- ## Behavior Tree Diagram [behaviorTree.pdf](https://github.com/user-attachments/files/16117536/behaviorTree.pdf) --- ## Code Overview ### Behavior Tree Nodes The following nodes form the building blocks of the behavior tree: #### **Sensing Nodes** - **`Front`**: Monitors front sonar data to detect obstacles. - **`Side`**: Tracks the distance to the wall using side sonar data. - **`Odom`**: Reads IMU data to calculate orientation and yaw. - **`Start`**: Checks for the user's "start" command. - **`Velocity`**: Captures velocity data for stopping decisions. - **`Map`**: Retrieves map data for localization and map completion. #### **Action Nodes** - **`Turn90Degrees`**: Turns the robot 90 degrees to avoid obstacles. - **`AdjustAngle`**: Aligns the robot’s orientation during navigation. - **`AdjustDistance`**: Adjusts the robot's distance from the wall for accurate path following. - **`Forward`**: Commands the robot to move forward. - **`Stop`**: Gradually slows and stops the robot. - **`StopMotors`**: Immediately halts motor movement. - **`WaitForSeconds`**: Introduces delays for timing or stabilization. #### **Condition Nodes** - **`CheckFront`**: Verifies the path ahead is clear. - **`CheckStart`**: Ensures navigation starts only after receiving user input. - **`MapFinished`**: Checks if the robot has completed its mapping task. #### **Utility Nodes** - **`PublishLog`**: Logs messages to the web interface for monitoring. ### Behavior Tree Execution The behavior tree executes in a loop, ticking each node to check its status (`SUCCESS`, `FAILURE`, or `RUNNING`). This modular execution ensures robustness and clear decision-making. --- ## Complete Code ```cpp #include #include #include #include "tf2_geometry_msgs/tf2_geometry_msgs.hpp" #include "behaviortree_ros2/bt_topic_sub_node.hpp" #include "behaviortree_cpp/bt_factory.h" #include "behaviortree_ros2/bt_action_node.hpp" #include "behaviortree_ros2/bt_service_node.hpp" #include "rclcpp/rclcpp.hpp" #include "rclcpp_action/rclcpp_action.hpp" #include "nav2_msgs/action/wait.hpp" #include "nav_msgs/srv/get_map.hpp" #include "nav_msgs/msg/occupancy_grid.hpp" #include "std_msgs/msg/float64.hpp" #include "sensor_msgs/msg/laser_scan.hpp" #include "nav_msgs/msg/odometry.hpp" #include "rclcpp/node_interfaces/node_base_interface.hpp" #include "sensor_msgs/msg/imu.hpp" #include "nav2_util/robot_utils.hpp" #include "tf2_ros/buffer.h" #include "tf2_ros/transform_listener.h" #include "geometry_msgs/msg/pose_stamped.hpp" #include "std_msgs/msg/string.hpp" #include "std_msgs/msg/int32.hpp" #include #include "orientation_msg/msg/esc.hpp" namespace chr = std::chrono; using namespace BT; using GetMap = nav_msgs::srv::GetMap; using geometry_msgs::msg::Pose; using namespace std::literals::chrono_literals; using OccupancyGrid = nav_msgs::msg::OccupancyGrid; int check_start = 0; int pressed_stop = 0; // SyncActionNode (synchronous action) with an input port. /* a SyncActionNode can only return SUCCESS or FAILURE it cannot return RUNNING. so it is only good for simple, fast operations */ // SyncActionNode (synchronous action) with an input port. class Front : public rclcpp::Node { public: Front() : Node("front_node") { subscription_ = this->create_subscription( "/scan/front", 10, std::bind(&Front::topic_callback, this, std::placeholders::_1)); } float get_latest_distance() const { return latest_distance_; } private: void topic_callback(const sensor_msgs::msg::LaserScan::SharedPtr msg) { latest_distance_ = msg->ranges[0]; } rclcpp::Subscription::SharedPtr subscription_; float latest_distance_; }; class Side : public rclcpp::Node { public: Side() : Node("side_node") { subscription_ = this->create_subscription( "/scan/left", 10, std::bind(&Side::topic_callback, this, std::placeholders::_1)); } float get_latest_distance() const { return latest_distance_; } private: void topic_callback(const sensor_msgs::msg::LaserScan::SharedPtr msg) { latest_distance_ = msg->ranges[0]; } rclcpp::Subscription::SharedPtr subscription_; float latest_distance_; }; class Odom : public rclcpp::Node { public: Odom() : Node("odom_node") { subscription_ = this->create_subscription( "/imu", 10, std::bind(&Odom::topic_callback, this, std::placeholders::_1)); } std::shared_ptr get_latest_imu() const { return latest_imu_; } private: void topic_callback(const sensor_msgs::msg::Imu::SharedPtr msg) { latest_imu_ = msg; } rclcpp::Subscription::SharedPtr subscription_; std::shared_ptr latest_imu_; }; //check if start button has been pressed on the web UI class Start: public rclcpp::Node { public: Start() : Node("start_node") { subscription_ = this->create_subscription( "/start", 10, std::bind(&Start::topic_callback, this, std::placeholders::_1)); } int get_latest_start() const { return latest_start_; } private: void topic_callback(const std_msgs::msg::Int32::SharedPtr msg) { latest_start_ = msg->data; } rclcpp::Subscription::SharedPtr subscription_; int latest_start_ = 0; }; class Velocity : public rclcpp::Node { public: Velocity() : Node("velocity_node") { subscription_ = this->create_subscription( "/odom", 10, std::bind(&Velocity::topic_callback, this, std::placeholders::_1)); } std::shared_ptr get_latest_velocity() const { return latest_velocity_; } private: void topic_callback(const nav_msgs::msg::Odometry::SharedPtr msg) { latest_velocity_ = msg; } rclcpp::Subscription::SharedPtr subscription_; std::shared_ptr latest_velocity_; }; class Map : public rclcpp::Node { public: Map() : Node("map_node") { subscription_ = this->create_subscription( "/map", 10, std::bind(&Map::topic_callback, this, std::placeholders::_1)); } std::shared_ptr get_latest_map() const { return latest_map_; } private: void topic_callback(const nav_msgs::msg::OccupancyGrid::SharedPtr msg) { latest_map_ = msg; } rclcpp::Subscription::SharedPtr subscription_; std::shared_ptr latest_map_; }; class CheckFront : public SyncActionNode { public: CheckFront(const std::string &name, const NodeConfig &config, std::shared_ptr front_node) : SyncActionNode(name, config), front_node_(front_node) { } static PortsList providedPorts() { return {}; } NodeStatus tick() override { // May need to change min distance to fit hardware turn radius if (front_node_->get_latest_distance() > 2.5) { std::cout << "Distance from front wall is good: " << front_node_->get_latest_distance() << "\n"; return NodeStatus::SUCCESS; } else { std::cout << "Front sonar distance threshold failed: " << front_node_->get_latest_distance() << "\n"; return NodeStatus::FAILURE; } } private: std::shared_ptr front_node_; }; class CheckStart : public SyncActionNode { public: CheckStart(const std::string &name, const NodeConfig &config, std::shared_ptr start_node) : SyncActionNode(name, config), start_node_(start_node) { } static PortsList providedPorts() { return {}; } NodeStatus tick() override { int start = start_node_->get_latest_start(); if (start == 0) { std::cout << "Waiting for user to start navigation\n"; return NodeStatus::FAILURE; } else if (start == 1) { std::cout << "User started navigation\n"; return NodeStatus::SUCCESS; } else { std::cout << "Error, unable to get user input from website\n"; return NodeStatus::FAILURE; } } private: std::shared_ptr start_node_; }; class Turn90Degrees : public SyncActionNode { public: Turn90Degrees(const std::string& name, const NodeConfig& config, std::shared_ptr odom_node) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("turn_90_degrees_node")), odom_node_(odom_node) { publisher_ = node_->create_publisher("/esc_topic", 10); turn_publisher_ = node_->create_publisher("/turning", 10); } static PortsList providedPorts() { return {}; } NodeStatus tick() override { auto msg = odom_node_->get_latest_imu(); if (!msg) { return NodeStatus::FAILURE; } auto current_orientation = msg->orientation; tf2::Quaternion current_quaternion(current_orientation.x, current_orientation.y, current_orientation.z, current_orientation.w); // Convert the current orientation quaternion to roll, pitch, yaw double roll, pitch, yaw; tf2::Matrix3x3(current_quaternion).getRPY(roll, pitch, yaw); // Calculate the target yaw by subtracting pi/2 from the current yaw if (target_yaw == 0) { if(yaw < -M_PI_4 && yaw > -3*M_PI_4){ target_yaw = -M_PI; } else if(yaw > M_PI_4 && yaw < 3*M_PI_4){ target_yaw = 0; } else if(std::abs(yaw) <= M_PI_4){ target_yaw = -M_PI_2; } else if(std::abs(yaw) >= 3*M_PI_4){ target_yaw = M_PI_2; } } //enable the behavior tree to start checking if we are back at the start pos, //this is an easy way to prevent it from detecting that we are at the start pos on starting navigation check_start = 1; // Calculate the angle difference between the current yaw and the target yaw double angle_difference = std::atan2(std::sin(yaw - target_yaw), std::cos(yaw - target_yaw)); if(std::fabs(angle_difference) < angle_tolerance_) { // Corrects the drift from the turn by full turning left for a short time auto esc = orientation_msg::msg::Esc(); esc.motor_selection = "Left"; esc.power_percentage = -20; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = 20; publisher_->publish(esc); std::cout << "Turn Completed\n"; target_yaw = 0; return NodeStatus::SUCCESS; } // turning right, left motor forward, right motor reverse, may need to adjust speeds else{ auto esc = orientation_msg::msg::Esc(); esc.motor_selection = "Left"; esc.power_percentage = 20; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = -20; publisher_->publish(esc); turn_msg.data = 1; turn_publisher_->publish(turn_msg); std::cout << "Current angle: "<::SharedPtr publisher_; rclcpp::Publisher::SharedPtr turn_publisher_; std::shared_ptr odom_node_; double target_yaw = 0; std_msgs::msg::Int32 turn_msg; const double angle_tolerance_ = 0.4; // radians }; class AdjustAngle : public SyncActionNode { public: AdjustAngle(const std::string& name, const NodeConfig& config, std::shared_ptr odom_node) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("adjust_angle_node")), odom_node_(odom_node) { publisher_ = node_->create_publisher("/esc_topic", 10); } // It is mandatory to define this STATIC method. static PortsList providedPorts() { return {}; } NodeStatus tick() override { auto msg = odom_node_->get_latest_imu(); if (!msg) { return NodeStatus::FAILURE; } auto current_orientation = msg->orientation; tf2::Quaternion current_quaternion(current_orientation.x, current_orientation.y, current_orientation.z, current_orientation.w); // Convert the current orientation quaternion to roll, pitch, yaw double roll, pitch, yaw; tf2::Matrix3x3(current_quaternion).getRPY(roll, pitch, yaw); // Determine target yaw if(yaw < -M_PI_4 && yaw > -3*M_PI_4){ target_yaw = -M_PI_2; } else if(yaw > M_PI_4 && yaw < 3*M_PI_4){ target_yaw = M_PI_2; } else if(std::abs(yaw) <= M_PI_4){ target_yaw = 0; } else if(std::abs(yaw) >= 3*M_PI_4){ target_yaw = M_PI; } // Calculate the angle difference between the current yaw and the target yaw double angle_difference = std::atan2(std::sin(yaw - target_yaw), std::cos(yaw - target_yaw)); if(std::fabs(angle_difference) <= angle_tolerance_) { //start going streight again, may need to adjust speeds auto esc = orientation_msg::msg::Esc(); esc.motor_selection = "Left"; esc.power_percentage = 20; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = 20; publisher_->publish(esc); return NodeStatus::SUCCESS; } // The target yaw is to the left of the current yaw so adjust right else if(angle_difference < 0){ auto esc = orientation_msg::msg::Esc(); esc.motor_selection = "Left"; esc.power_percentage = 20; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = 30; publisher_->publish(esc); std::cout << "Adjusting left, Current angle: "<publish(esc); esc.motor_selection = "Right"; esc.power_percentage = 20; publisher_->publish(esc); std::cout << "Adjusting right, Current angle: "<::SharedPtr publisher_; std::shared_ptr odom_node_; double target_yaw = 0; std_msgs::msg::Int32 turn_msg; const double angle_tolerance_ = 0.2; // radians }; class AdjustDistance : public SyncActionNode { public: AdjustDistance(const std::string& name, const NodeConfig& config, std::shared_ptr side_node) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("adjust_distance_node")), side_node_(side_node) { publisher_ = node_->create_publisher("/esc_topic", 10); } // It is mandatory to define this STATIC method. static PortsList providedPorts() { return {}; } NodeStatus tick() override { auto dist = side_node_->get_latest_distance(); auto msg = orientation_msg::msg::Esc(); if(dist < 1) { std::cout <<"Distance from left wall: " << dist <<"\n"; std::cout << "Adjusting right\n"; msg.motor_selection = "Left"; msg.power_percentage = 20; publisher_->publish(msg); msg.motor_selection = "Right"; msg.power_percentage = 0; publisher_->publish(msg); return NodeStatus::FAILURE; } else if(dist > 3) { std::cout <<"Distance from left wall: " << dist <<"\n"; std::cout << "Adjusting left\n"; msg.motor_selection = "Left"; msg.power_percentage = 0; publisher_->publish(msg); msg.motor_selection = "Right"; msg.power_percentage = 20; publisher_->publish(msg); return NodeStatus::FAILURE; } else { std::cout << "Distance from side wall is good: "<< dist <<"\n"; msg.motor_selection = "Left"; msg.power_percentage = 20; publisher_->publish(msg); msg.motor_selection = "Right"; msg.power_percentage = 20; publisher_->publish(msg); return NodeStatus::SUCCESS; } } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher::SharedPtr publisher_; std::shared_ptr side_node_; }; class Forward : public SyncActionNode { public: Forward(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("forward_node")) { publisher_ = node_->create_publisher("/esc_topic", 10); } // It is mandatory to define this STATIC method. static PortsList providedPorts() { return {}; } NodeStatus tick() override { // may need to adjust motor speed std::cout << "Moving Forward\n"; auto msg = orientation_msg::msg::Esc(); msg.motor_selection = "Left"; msg.power_percentage = 20; publisher_->publish(msg); msg.motor_selection = "Right"; msg.power_percentage = 20; publisher_->publish(msg); return NodeStatus::SUCCESS; } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher::SharedPtr publisher_; }; class Stop : public SyncActionNode { public: Stop(const std::string& name, const NodeConfig& config, std::shared_ptr velocity_node) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("stop_node")), velocity_node_(velocity_node) { publisher_ = node_->create_publisher("/esc_topic", 10); publisher3_ = node_->create_publisher("/done", 10); } static PortsList providedPorts() { return {}; } NodeStatus tick() override { //turn done on and off to have the octomap interpolate data once std_msgs::msg::Int32 val3; val3.data = 1; publisher3_->publish(val3); val3.data = 0; publisher3_->publish(val3); auto esc = orientation_msg::msg::Esc(); auto velocity_msg = velocity_node_->get_latest_velocity(); if (!velocity_msg) { return NodeStatus::FAILURE; } double velocity = velocity_msg->twist.twist.linear.x; if(std::abs(velocity) > 0.1){ std::cout<<"Slowing Down\n"; esc.motor_selection = "Left"; esc.power_percentage = -20; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = -20; publisher_->publish(esc); return NodeStatus::FAILURE; } else{ std::cout<<"Stop Complete"; esc.motor_selection = "Left"; esc.power_percentage = 0; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = 0; publisher_->publish(esc); return NodeStatus::SUCCESS; } } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher::SharedPtr publisher_; rclcpp::Publisher::SharedPtr publisher3_; std::shared_ptr velocity_node_; }; class SaySomething : public SyncActionNode { public: // If your Node has ports, you must use this constructor signature SaySomething(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config) { } // It is mandatory to define this STATIC method. static PortsList providedPorts() { // This action has a single input port called "message" return { InputPort("message") }; } // Override the virtual function tick() NodeStatus tick() override { Expected msg = getInput("message"); // Check if expected is valid. If not, throw its error if (!msg) { throw RuntimeError("missing required input [message]: ", msg.error() ); } // use the method value() to extract the valid message. std::cout << "Robot says: " << msg.value() << std::endl; return NodeStatus::SUCCESS; } }; class MapFinished : public SyncActionNode { public: MapFinished(const std::string& name, const NodeConfig& config, std::shared_ptr map_node) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("stop_node")), tf_buffer_(std::make_shared(RCL_SYSTEM_TIME)), tf_listener_(tf_buffer_), map_node_(map_node) { publisher_ = node_->create_publisher("/done", 10); turn_publisher_ = node_->create_publisher("/turning", 10); // Initialize the starting position starting_position_ = Pose(); } static PortsList providedPorts() { return { InputPort("starting_position") }; } NodeStatus tick() override { turn_msg.data = 0; turn_publisher_->publish(turn_msg); // Get the inputs auto map_input = map_node_->get_latest_map(); auto starting_position_input = getInput("starting_position"); std_msgs::msg::Int32 val; val.data = 0; if (!map_input) { throw RuntimeError("missing required input [map]: "); } if (!starting_position_input) { throw RuntimeError("missing required input [starting_position]: ", starting_position_input.error()); } // Update the starting position starting_position_ = starting_position_input.value(); // Get the current pose of the robot geometry_msgs::msg::PoseStamped current_pose; // Wait for the transform to be available if(!tf_buffer_.canTransform("map", "base_link", tf2::TimePointZero, tf2::durationFromSec(1.0))) { std::cout << "Waiting for transform...\n"; publisher_->publish(val); return NodeStatus::FAILURE; } if(nav2_util::getCurrentPose(current_pose, tf_buffer_, "map", "base_link", tf2::Duration(30s).count())) { // current_pose now contains the pose of the robot in the map frame // You can use current_pose for your purposes here std::cout << "Current pose retrieved successfully.\n"; } else { // Handle the case where the current pose could not be retrieved std::cout << "Failed to retrieve current pose.\n"; publisher_->publish(val); return NodeStatus::FAILURE; } // Check if the current position is approximately equal to the starting position if (isApproximatelyEqual(current_pose.pose, starting_position_) && check_start == 1) { val.data = 1; std::cout << "We are back at the starting position!\n"; publisher_->publish(val); return NodeStatus::SUCCESS; } else { std::cout << "Current Pose X: "<< current_pose.pose.position.x << " Y: " << current_pose.pose.position.y << "\nStart POS X: "<< starting_position_.position.x << " Y: " << starting_position_.position.y <<"\n"; publisher_->publish(val); return NodeStatus::FAILURE; } } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher::SharedPtr publisher_; Pose starting_position_; rclcpp::Clock::SharedPtr clock_; tf2_ros::Buffer tf_buffer_; tf2_ros::TransformListener tf_listener_; std::shared_ptr map_node_; rclcpp::Publisher::SharedPtr turn_publisher_; std_msgs::msg::Int32 turn_msg; bool isApproximatelyEqual(const Pose& pos1, const Pose& pos2) { // Define a small threshold for position comparison const double THRESHOLD = 1; // meters // Use the TF2 library to calculate the difference between the two poses tf2::Transform tf1, tf2; tf2::fromMsg(pos1, tf1); tf2::fromMsg(pos2, tf2); // Calculate the distance between the two positions double distance = tf2::tf2Distance(tf1.getOrigin(), tf2.getOrigin()); std::cout << "Distance to start pos: "<("seconds") }; } NodeStatus onStart() override; NodeStatus onRunning() override; void onHalted() override; private: float _seconds; chr::system_clock::time_point _completion_time; }; NodeStatus WaitForSeconds::onStart() { if ( !getInput("seconds", _seconds)) { throw RuntimeError("missing required input [seconds]"); } printf("[ WaitForSeconds: ] seconds = %f\n",_seconds); _completion_time = chr::system_clock::now() + chr::milliseconds(int(_seconds*1000)); return NodeStatus::RUNNING; } NodeStatus WaitForSeconds::onRunning() { std::this_thread::sleep_for(chr::milliseconds(1000)); if(chr::system_clock::now() >= _completion_time) { std::cout << "[ WaitForSeconds: FINISHED ]" << std::endl; return NodeStatus::SUCCESS; } return NodeStatus::RUNNING; } void WaitForSeconds::onHalted() { printf("[ WaitForSeconds: ABORTED ]"); } class StartPos : public SyncActionNode { public: StartPos(const std::string& name, const NodeConfig& config, std::shared_ptr map_node) : SyncActionNode(name, config), map_node_(map_node) { // Initialize the starting position starting_position_ = Pose(); } static PortsList providedPorts() { return {OutputPort("starting_position")}; } NodeStatus tick() override { auto latest_map = map_node_->get_latest_map(); if (!latest_map) { RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "No map received!"); return NodeStatus::FAILURE; } RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Processing the map!"); // Extract the starting position from the map metadata starting_position_.position.x = latest_map->info.origin.position.x; starting_position_.position.y = latest_map->info.origin.position.y; // Set the output starting position setOutput("starting_position", starting_position_); return NodeStatus::SUCCESS; } private: std::shared_ptr map_node_; Pose starting_position_; }; class PublishLog : public SyncActionNode { public: PublishLog(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("publish_log_node")) { publisher_ = node_->create_publisher("/web_message", 10); } // It is mandatory to define this STATIC method. static PortsList providedPorts() { return {InputPort("message")}; } NodeStatus tick() override { Expected msg = getInput("message"); auto now = std::chrono::system_clock::now(); auto now_c = std::chrono::system_clock::to_time_t(now); std::stringstream ss; if(msg.value() == "place holder"){ if(pressed_stop){ ss << std::put_time(std::localtime(&now_c), "%F %T") << " - User canceled navigation"; std_msgs::msg::String message; message.data = ss.str(); publisher_->publish(message); return NodeStatus::SUCCESS; } else{ ss << std::put_time(std::localtime(&now_c), "%F %T") << " - The submarine has completed mapping!"; std_msgs::msg::String message; message.data = ss.str(); publisher_->publish(message); return NodeStatus::SUCCESS; } } else{ ss << std::put_time(std::localtime(&now_c), "%F %T") << msg.value(); std_msgs::msg::String message; message.data = ss.str(); publisher_->publish(message); return NodeStatus::SUCCESS; } } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher::SharedPtr publisher_; }; class StopMotors : public SyncActionNode { public: StopMotors(const std::string& name, const NodeConfig& config) : SyncActionNode(name, config), node_(rclcpp::Node::make_shared("stop_motors_node")) { publisher_ = node_->create_publisher("/esc_topic", 10); } // It is mandatory to define this STATIC method. static PortsList providedPorts() { return {}; } NodeStatus tick() override { auto esc = orientation_msg::msg::Esc(); esc.motor_selection = "Left"; esc.power_percentage = 0; publisher_->publish(esc); esc.motor_selection = "Right"; esc.power_percentage = 0; publisher_->publish(esc); return NodeStatus::SUCCESS; } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher::SharedPtr publisher_; }; static const char* xml_text = R"( )"; void spin_node(std::shared_ptr node) { rclcpp::executors::SingleThreadedExecutor executor; executor.add_node(node); executor.spin(); } int main(int argc, char **argv) { rclcpp::init(argc, argv); auto front_node = std::make_shared(); std::thread front_spin(spin_node, front_node); auto side_node = std::make_shared(); std::thread side_spin(spin_node, side_node); auto odom_node = std::make_shared(); std::thread odom_spin(spin_node, odom_node); auto start_node = std::make_shared(); std::thread start_spin(spin_node, start_node); auto velocity_node = std::make_shared(); std::thread velocity_spin(spin_node, velocity_node); auto map_node = std::make_shared(); std::thread map_spin(spin_node, map_node); // Initialize the BehaviorTreeFactory BehaviorTreeFactory factory; factory.registerNodeType("WaitForSeconds"); factory.registerNodeType("SaySomething"); factory.registerNodeType("MapFinished",map_node); factory.registerNodeType("Turn90Degrees",odom_node); //factory.registerNodeType("AdjustDistance",side_node); factory.registerNodeType("AdjustAngle",odom_node); factory.registerNodeType("Forward"); factory.registerNodeType("Stop",velocity_node); factory.registerNodeType("PublishLog"); factory.registerNodeType("CheckFront",front_node); factory.registerNodeType("CheckStart",start_node); factory.registerNodeType("StartPos",map_node); factory.registerNodeType("StopMotors"); // Create the behavior tree from the XML text auto tree = factory.createTreeFromText(xml_text); // Behavior tree execution loop NodeStatus status = tree.tickOnce(); while(status == NodeStatus::RUNNING) { tree.sleep(std::chrono::milliseconds(100)); status = tree.tickOnce(); } rclcpp::shutdown(); front_spin.join(); side_spin.join(); odom_spin.join(); start_spin.join(); velocity_spin.join(); map_spin.join(); return 0; } ``` --- ## Advantages of Using a Behavior Tree 1. **Modularity**: Each behavior is encapsulated in its own node, making it reusable and easy to modify. 2. **Fallback Mechanism**: Provides robustness by ensuring alternative actions are taken when primary ones fail. 3. **Clarity**: The hierarchical structure improves readability and debugging. 4. **Scalability**: Additional behaviors can be incorporated without affecting the existing logic. --- This behavior tree ensures smooth navigation and mapping by combining conditional checks, obstacle avoidance, and user interaction in a structured and efficient manner.