add humble-navigation2

This commit is contained in:
X-lanni
2025-05-27 19:03:40 +08:00
parent 974abb5e1e
commit e74ec539c2
1280 changed files with 204114 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
cmake_minimum_required(VERSION 3.5)
project(nav2_behaviors)
find_package(ament_cmake REQUIRED)
find_package(nav2_common REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(rclcpp_lifecycle REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(std_msgs REQUIRED)
find_package(nav2_behavior_tree REQUIRED)
find_package(nav2_util REQUIRED)
find_package(nav2_msgs REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(nav2_costmap_2d REQUIRED)
find_package(nav2_core REQUIRED)
find_package(pluginlib REQUIRED)
nav2_package()
include_directories(
include
)
set(library_name behavior_server_core)
set(executable_name behavior_server)
set(dependencies
rclcpp
rclcpp_action
rclcpp_lifecycle
rclcpp_components
std_msgs
nav2_util
nav2_behavior_tree
nav2_msgs
nav_msgs
tf2
tf2_geometry_msgs
geometry_msgs
nav2_costmap_2d
nav2_core
pluginlib
)
# plugins
add_library(nav2_spin_behavior SHARED
plugins/spin.cpp
)
ament_target_dependencies(nav2_spin_behavior
${dependencies}
)
add_library(nav2_wait_behavior SHARED
plugins/wait.cpp
)
ament_target_dependencies(nav2_wait_behavior
${dependencies}
)
add_library(nav2_drive_on_heading_behavior SHARED
plugins/drive_on_heading.cpp
)
ament_target_dependencies(nav2_drive_on_heading_behavior
${dependencies}
)
add_library(nav2_back_up_behavior SHARED
plugins/back_up.cpp
)
ament_target_dependencies(nav2_back_up_behavior
${dependencies}
)
add_library(nav2_assisted_teleop_behavior SHARED
plugins/assisted_teleop.cpp
)
ament_target_dependencies(nav2_assisted_teleop_behavior
${dependencies}
)
pluginlib_export_plugin_description_file(nav2_core behavior_plugin.xml)
# Library
add_library(${library_name} SHARED
src/behavior_server.cpp
)
ament_target_dependencies(${library_name}
${dependencies}
)
# Executable
add_executable(${executable_name}
src/main.cpp
)
target_link_libraries(${executable_name} ${library_name})
ament_target_dependencies(${executable_name}
${dependencies}
)
rclcpp_components_register_nodes(${library_name} "behavior_server::BehaviorServer")
install(TARGETS ${library_name}
nav2_spin_behavior
nav2_wait_behavior
nav2_assisted_teleop_behavior
nav2_drive_on_heading_behavior
nav2_back_up_behavior
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(TARGETS ${executable_name}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(DIRECTORY include/
DESTINATION include/
)
install(FILES behavior_plugin.xml
DESTINATION share/${PROJECT_NAME}
)
install(DIRECTORY plugins/
DESTINATION share/${PROJECT_NAME}/plugins/
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
find_package(ament_cmake_gtest REQUIRED)
add_subdirectory(test)
endif()
ament_export_include_directories(include)
ament_export_libraries(${library_name}
nav2_spin_behavior
nav2_wait_behavior
nav2_assisted_teleop_behavior
nav2_drive_on_heading_behavior
nav2_back_up_behavior
)
ament_export_dependencies(${dependencies})
ament_package()
+15
View File
@@ -0,0 +1,15 @@
# Behaviors
The `nav2_behaviors` package implements a task server for executing behaviors.
The package defines:
- A `TimedBehavior` template which is used as a base class to implement specific timed behavior action server - but not required.
- The `Backup`, `DriveOnHeading`, `Spin` and `Wait` behaviors.
The only required class a behavior must derive from is the `nav2_core/behavior.hpp` class, which implements the pluginlib interface the behavior server will use to dynamically load your behavior. The `nav2_behaviors/timed_behavior.hpp` derives from this class and implements a generic action server for a timed behavior behavior (e.g. calls an implmentation function on a regular time interval to compute a value) but **this is not required** if it is not helpful. A behavior does not even need to be an action if you do not wish, it may be a service or other interface. However, most motion and behavior primitives are probably long-running and make sense to be modeled as actions, so the provided `timed_behavior.hpp` helps in managing the complexity to simplify new behavior development, described more below.
The value of the centralized behavior server is to **share resources** amongst several behaviors that would otherwise be independent nodes. Subscriptions to TF, costmaps, and more can be quite heavy and add non-trivial compute costs to a robot system. By combining these independent behaviors into a single server, they may share these resources while retaining complete independence in execution and interface.
See its [Configuration Guide Page](https://navigation.ros.org/configuration/packages/configuring-behavior-server.html) for additional parameter descriptions and a [tutorial about writing behaviors](https://navigation.ros.org/plugin_tutorials/docs/writing_new_behavior_plugin.html).
See the [Navigation Plugin list](https://navigation.ros.org/plugins/index.html) for a list of the currently known and available planner plugins.
@@ -0,0 +1,31 @@
<class_libraries>
<library path="nav2_spin_behavior">
<class name="nav2_behaviors/Spin" type="nav2_behaviors::Spin" base_class_type="nav2_core::Behavior">
<description></description>
</class>
</library>
<library path="nav2_back_up_behavior">
<class name="nav2_behaviors/BackUp" type="nav2_behaviors::BackUp" base_class_type="nav2_core::Behavior">
<description></description>
</class>
</library>
<library path="nav2_drive_on_heading_behavior">
<class name="nav2_behaviors/DriveOnHeading" type="nav2_behaviors::DriveOnHeading&lt;&gt;" base_class_type="nav2_core::Behavior">
<description></description>
</class>
</library>
<library path="nav2_wait_behavior">
<class name="nav2_behaviors/Wait" type="nav2_behaviors::Wait" base_class_type="nav2_core::Behavior">
<description></description>
</class>
</library>
<library path="nav2_assisted_teleop_behavior">
<class name="nav2_behaviors/AssistedTeleop" type="nav2_behaviors::AssistedTeleop" base_class_type="nav2_core::Behavior">
<description></description>
</class>
</library>
</class_libraries>
@@ -0,0 +1,99 @@
// Copyright (c) 2018 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <chrono>
#include <string>
#include <memory>
#include <vector>
#include "nav2_util/lifecycle_node.hpp"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/create_timer_ros.h"
#include "pluginlib/class_loader.hpp"
#include "pluginlib/class_list_macros.hpp"
#include "nav2_core/behavior.hpp"
#ifndef NAV2_BEHAVIORS__BEHAVIOR_SERVER_HPP_
#define NAV2_BEHAVIORS__BEHAVIOR_SERVER_HPP_
namespace behavior_server
{
/**
* @class behavior_server::BehaviorServer
* @brief An server hosting a map of behavior plugins
*/
class BehaviorServer : public nav2_util::LifecycleNode
{
public:
/**
* @brief A constructor for behavior_server::BehaviorServer
* @param options Additional options to control creation of the node.
*/
explicit BehaviorServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
~BehaviorServer();
/**
* @brief Loads behavior plugins from parameter file
* @return bool if successfully loaded the plugins
*/
bool loadBehaviorPlugins();
protected:
/**
* @brief Configure lifecycle server
*/
nav2_util::CallbackReturn on_configure(const rclcpp_lifecycle::State & state) override;
/**
* @brief Activate lifecycle server
*/
nav2_util::CallbackReturn on_activate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Deactivate lifecycle server
*/
nav2_util::CallbackReturn on_deactivate(const rclcpp_lifecycle::State & state) override;
/**
* @brief Cleanup lifecycle server
*/
nav2_util::CallbackReturn on_cleanup(const rclcpp_lifecycle::State & state) override;
/**
* @brief Shutdown lifecycle server
*/
nav2_util::CallbackReturn on_shutdown(const rclcpp_lifecycle::State & state) override;
std::shared_ptr<tf2_ros::Buffer> tf_;
std::shared_ptr<tf2_ros::TransformListener> transform_listener_;
// Plugins
pluginlib::ClassLoader<nav2_core::Behavior> plugin_loader_;
std::vector<pluginlib::UniquePtr<nav2_core::Behavior>> behaviors_;
std::vector<std::string> default_ids_;
std::vector<std::string> default_types_;
std::vector<std::string> behavior_ids_;
std::vector<std::string> behavior_types_;
// Utilities
std::unique_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_sub_;
std::unique_ptr<nav2_costmap_2d::FootprintSubscriber> footprint_sub_;
std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker_;
};
} // namespace behavior_server
#endif // NAV2_BEHAVIORS__BEHAVIOR_SERVER_HPP_
@@ -0,0 +1,105 @@
// Copyright (c) 2022 Joshua Wallace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_BEHAVIORS__PLUGINS__ASSISTED_TELEOP_HPP_
#define NAV2_BEHAVIORS__PLUGINS__ASSISTED_TELEOP_HPP_
#include <chrono>
#include <memory>
#include <string>
#include "geometry_msgs/msg/twist.hpp"
#include "std_msgs/msg/empty.hpp"
#include "nav2_behaviors/timed_behavior.hpp"
#include "nav2_msgs/action/assisted_teleop.hpp"
namespace nav2_behaviors
{
using AssistedTeleopAction = nav2_msgs::action::AssistedTeleop;
/**
* @class nav2_behaviors::AssistedTeleop
* @brief An action server behavior for assisted teleop
*/
class AssistedTeleop : public TimedBehavior<AssistedTeleopAction>
{
public:
AssistedTeleop();
/**
* @brief Initialization to run behavior
* @param command Goal to execute
* @return Status of behavior
*/
Status onRun(const std::shared_ptr<const AssistedTeleopAction::Goal> command) override;
/**
* @brief func to run at the completion of the action
*/
void onActionCompletion() override;
/**
* @brief Loop function to run behavior
* @return Status of behavior
*/
Status onCycleUpdate() override;
protected:
/**
* @brief Configuration of behavior action
*/
void onConfigure() override;
/**
* @brief project a position
* @param pose initial pose to project
* @param twist velocity to project pose by
* @param projection_time time to project by
*/
geometry_msgs::msg::Pose2D projectPose(
const geometry_msgs::msg::Pose2D & pose,
const geometry_msgs::msg::Twist & twist,
double projection_time);
/**
* @brief Callback function for velocity subscriber
* @param msg received Twist message
*/
void teleopVelocityCallback(const geometry_msgs::msg::Twist::SharedPtr msg);
/**
* @brief Callback function to preempt assisted teleop
* @param msg empty message
*/
void preemptTeleopCallback(const std_msgs::msg::Empty::SharedPtr msg);
AssistedTeleopAction::Feedback::SharedPtr feedback_;
// parameters
double projection_time_;
double simulation_time_step_;
geometry_msgs::msg::Twist teleop_twist_;
bool preempt_teleop_{false};
// subscribers
rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr vel_sub_;
rclcpp::Subscription<std_msgs::msg::Empty>::SharedPtr preempt_teleop_sub_;
rclcpp::Duration command_time_allowance_{0, 0};
rclcpp::Time end_time_;
};
} // namespace nav2_behaviors
#endif // NAV2_BEHAVIORS__PLUGINS__ASSISTED_TELEOP_HPP_
@@ -0,0 +1,35 @@
// Copyright (c) 2022 Joshua Wallace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_BEHAVIORS__PLUGINS__BACK_UP_HPP_
#define NAV2_BEHAVIORS__PLUGINS__BACK_UP_HPP_
#include <memory>
#include "drive_on_heading.hpp"
#include "nav2_msgs/action/back_up.hpp"
using BackUpAction = nav2_msgs::action::BackUp;
namespace nav2_behaviors
{
class BackUp : public DriveOnHeading<nav2_msgs::action::BackUp>
{
public:
Status onRun(const std::shared_ptr<const BackUpAction::Goal> command) override;
};
}
#endif // NAV2_BEHAVIORS__PLUGINS__BACK_UP_HPP_
@@ -0,0 +1,275 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2022 Joshua Wallace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
#define NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
#include <chrono>
#include <memory>
#include <utility>
#include <limits>
#include "nav2_behaviors/timed_behavior.hpp"
#include "nav2_msgs/action/drive_on_heading.hpp"
#include "nav2_msgs/action/back_up.hpp"
#include "nav2_util/node_utils.hpp"
namespace nav2_behaviors
{
/**
* @class nav2_behaviors::DriveOnHeading
* @brief An action server Behavior for spinning in
*/
template<typename ActionT = nav2_msgs::action::DriveOnHeading>
class DriveOnHeading : public TimedBehavior<ActionT>
{
public:
/**
* @brief A constructor for nav2_behaviors::DriveOnHeading
*/
DriveOnHeading()
: TimedBehavior<ActionT>(),
feedback_(std::make_shared<typename ActionT::Feedback>()),
command_x_(0.0),
command_speed_(0.0),
simulate_ahead_time_(0.0)
{
}
~DriveOnHeading() = default;
/**
* @brief Initialization to run behavior
* @param command Goal to execute
* @return Status of behavior
*/
Status onRun(const std::shared_ptr<const typename ActionT::Goal> command) override
{
if (command->target.y != 0.0 || command->target.z != 0.0) {
RCLCPP_INFO(
this->logger_,
"DrivingOnHeading in Y and Z not supported, will only move in X.");
return Status::FAILED;
}
// Ensure that both the speed and direction have the same sign
if (!((command->target.x > 0.0) == (command->speed > 0.0)) ) {
RCLCPP_ERROR(this->logger_, "Speed and command sign did not match");
return Status::FAILED;
}
command_x_ = command->target.x;
command_speed_ = command->speed;
command_time_allowance_ = command->time_allowance;
end_time_ = this->clock_->now() + command_time_allowance_;
if (!nav2_util::getCurrentPose(
initial_pose_, *this->tf_, this->global_frame_, this->robot_base_frame_,
this->transform_tolerance_))
{
RCLCPP_ERROR(this->logger_, "Initial robot pose is not available.");
return Status::FAILED;
}
return Status::SUCCEEDED;
}
/**
* @brief Loop function to run behavior
* @return Status of behavior
*/
Status onCycleUpdate() override
{
rclcpp::Duration time_remaining = end_time_ - this->clock_->now();
if (time_remaining.seconds() < 0.0 && command_time_allowance_.seconds() > 0.0) {
this->stopRobot();
RCLCPP_WARN(
this->logger_,
"Exceeded time allowance before reaching the DriveOnHeading goal - Exiting DriveOnHeading");
return Status::FAILED;
}
geometry_msgs::msg::PoseStamped current_pose;
if (!nav2_util::getCurrentPose(
current_pose, *this->tf_, this->global_frame_, this->robot_base_frame_,
this->transform_tolerance_))
{
RCLCPP_ERROR(this->logger_, "Current robot pose is not available.");
return Status::FAILED;
}
double diff_x = initial_pose_.pose.position.x - current_pose.pose.position.x;
double diff_y = initial_pose_.pose.position.y - current_pose.pose.position.y;
double distance = hypot(diff_x, diff_y);
feedback_->distance_traveled = distance;
this->action_server_->publish_feedback(feedback_);
if (distance >= std::fabs(command_x_)) {
this->stopRobot();
return Status::SUCCEEDED;
}
auto cmd_vel = std::make_unique<geometry_msgs::msg::Twist>();
cmd_vel->linear.y = 0.0;
cmd_vel->angular.z = 0.0;
bool forward = command_speed_ > 0.0;
if (acceleration_limit_ == 0.0 || deceleration_limit_ == 0.0) {
RCLCPP_INFO_ONCE(this->logger_, "DriveOnHeading: no acceleration or deceleration limits set");
cmd_vel->linear.x = command_speed_;
} else {
double current_speed = last_vel_ == std::numeric_limits<double>::max() ? 0.0 : last_vel_;
double min_feasible_speed, max_feasible_speed;
if (forward) {
min_feasible_speed = current_speed + deceleration_limit_ / this->cycle_frequency_;
max_feasible_speed = current_speed + acceleration_limit_ / this->cycle_frequency_;
} else {
min_feasible_speed = current_speed - acceleration_limit_ / this->cycle_frequency_;
max_feasible_speed = current_speed - deceleration_limit_ / this->cycle_frequency_;
}
cmd_vel->linear.x = std::clamp(command_speed_, min_feasible_speed, max_feasible_speed);
// Check if we need to slow down to avoid overshooting
auto remaining_distance = std::fabs(command_x_) - distance;
double max_vel_to_stop = std::sqrt(-2.0 * deceleration_limit_ * remaining_distance);
if (max_vel_to_stop < std::abs(cmd_vel->linear.x)) {
cmd_vel->linear.x = forward ? max_vel_to_stop : -max_vel_to_stop;
}
}
// Ensure we don't go below minimum speed
if (std::fabs(cmd_vel->linear.x) < minimum_speed_) {
cmd_vel->linear.x = forward ? minimum_speed_ : -minimum_speed_;
}
geometry_msgs::msg::Pose2D pose2d;
pose2d.x = current_pose.pose.position.x;
pose2d.y = current_pose.pose.position.y;
pose2d.theta = tf2::getYaw(current_pose.pose.orientation);
if (!isCollisionFree(distance, cmd_vel.get(), pose2d)) {
this->stopRobot();
RCLCPP_WARN(this->logger_, "Collision Ahead - Exiting DriveOnHeading");
return Status::FAILED;
}
last_vel_ = cmd_vel->linear.x;
this->vel_pub_->publish(std::move(cmd_vel));
return Status::RUNNING;
}
void onCleanup() override {last_vel_ = std::numeric_limits<double>::max();}
void onActionCompletion() override
{
last_vel_ = std::numeric_limits<double>::max();
}
protected:
/**
* @brief Check if pose is collision free
* @param distance Distance to check forward
* @param cmd_vel current commanded velocity
* @param pose2d Current pose
* @return is collision free or not
*/
bool isCollisionFree(
const double & distance,
geometry_msgs::msg::Twist * cmd_vel,
geometry_msgs::msg::Pose2D & pose2d)
{
// Simulate ahead by simulate_ahead_time_ in this->cycle_frequency_ increments
int cycle_count = 0;
double sim_position_change;
const double diff_dist = abs(command_x_) - distance;
const int max_cycle_count = static_cast<int>(this->cycle_frequency_ * simulate_ahead_time_);
geometry_msgs::msg::Pose2D init_pose = pose2d;
bool fetch_data = true;
while (cycle_count < max_cycle_count) {
sim_position_change = cmd_vel->linear.x * (cycle_count / this->cycle_frequency_);
pose2d.x = init_pose.x + sim_position_change * cos(init_pose.theta);
pose2d.y = init_pose.y + sim_position_change * sin(init_pose.theta);
cycle_count++;
if (diff_dist - abs(sim_position_change) <= 0.) {
break;
}
if (!this->collision_checker_->isCollisionFree(pose2d, fetch_data)) {
return false;
}
fetch_data = false;
}
return true;
}
/**
* @brief Configuration of behavior action
*/
void onConfigure() override
{
auto node = this->node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
nav2_util::declare_parameter_if_not_declared(
node,
"simulate_ahead_time", rclcpp::ParameterValue(2.0));
node->get_parameter("simulate_ahead_time", simulate_ahead_time_);
nav2_util::declare_parameter_if_not_declared(
node, this->behavior_name_ + ".acceleration_limit",
rclcpp::ParameterValue(0.0));
nav2_util::declare_parameter_if_not_declared(
node, this->behavior_name_ + ".deceleration_limit",
rclcpp::ParameterValue(0.0));
nav2_util::declare_parameter_if_not_declared(
node, this->behavior_name_ + ".minimum_speed",
rclcpp::ParameterValue(0.0));
node->get_parameter(this->behavior_name_ + ".acceleration_limit", acceleration_limit_);
node->get_parameter(this->behavior_name_ + ".deceleration_limit", deceleration_limit_);
node->get_parameter(this->behavior_name_ + ".minimum_speed", minimum_speed_);
if (acceleration_limit_ < 0.0 || deceleration_limit_ > 0.0) {
RCLCPP_ERROR(this->logger_,
"DriveOnHeading: acceleration_limit and deceleration_limit must be "
"positive and negative respectively");
acceleration_limit_ = std::abs(acceleration_limit_);
deceleration_limit_ = -std::abs(deceleration_limit_);
}
}
typename ActionT::Feedback::SharedPtr feedback_;
geometry_msgs::msg::PoseStamped initial_pose_;
double command_x_;
double command_speed_;
rclcpp::Duration command_time_allowance_{0, 0};
rclcpp::Time end_time_;
double simulate_ahead_time_;
double acceleration_limit_;
double deceleration_limit_;
double minimum_speed_;
double last_vel_ = std::numeric_limits<double>::max();
};
} // namespace nav2_behaviors
#endif // NAV2_BEHAVIORS__PLUGINS__DRIVE_ON_HEADING_HPP_
@@ -0,0 +1,89 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_BEHAVIORS__PLUGINS__SPIN_HPP_
#define NAV2_BEHAVIORS__PLUGINS__SPIN_HPP_
#include <chrono>
#include <string>
#include <memory>
#include "nav2_behaviors/timed_behavior.hpp"
#include "nav2_msgs/action/spin.hpp"
#include "geometry_msgs/msg/quaternion.hpp"
namespace nav2_behaviors
{
using SpinAction = nav2_msgs::action::Spin;
/**
* @class nav2_behaviors::Spin
* @brief An action server behavior for spinning in
*/
class Spin : public TimedBehavior<SpinAction>
{
public:
/**
* @brief A constructor for nav2_behaviors::Spin
*/
Spin();
~Spin();
/**
* @brief Initialization to run behavior
* @param command Goal to execute
* @return Status of behavior
*/
Status onRun(const std::shared_ptr<const SpinAction::Goal> command) override;
/**
* @brief Configuration of behavior action
*/
void onConfigure() override;
/**
* @brief Loop function to run behavior
* @return Status of behavior
*/
Status onCycleUpdate() override;
protected:
/**
* @brief Check if pose is collision free
* @param distance Distance to check forward
* @param cmd_vel current commanded velocity
* @param pose2d Current pose
* @return is collision free or not
*/
bool isCollisionFree(
const double & distance,
geometry_msgs::msg::Twist * cmd_vel,
geometry_msgs::msg::Pose2D & pose2d);
SpinAction::Feedback::SharedPtr feedback_;
double min_rotational_vel_;
double max_rotational_vel_;
double rotational_acc_lim_;
double cmd_yaw_;
double prev_yaw_;
double relative_yaw_;
double simulate_ahead_time_;
rclcpp::Duration command_time_allowance_{0, 0};
rclcpp::Time end_time_;
};
} // namespace nav2_behaviors
#endif // NAV2_BEHAVIORS__PLUGINS__SPIN_HPP_
@@ -0,0 +1,62 @@
// Copyright (c) 2019 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_BEHAVIORS__PLUGINS__WAIT_HPP_
#define NAV2_BEHAVIORS__PLUGINS__WAIT_HPP_
#include <chrono>
#include <string>
#include <memory>
#include "nav2_behaviors/timed_behavior.hpp"
#include "nav2_msgs/action/wait.hpp"
namespace nav2_behaviors
{
using WaitAction = nav2_msgs::action::Wait;
/**
* @class nav2_behaviors::Wait
* @brief An action server behavior for waiting a fixed duration
*/
class Wait : public TimedBehavior<WaitAction>
{
public:
/**
* @brief A constructor for nav2_behaviors::Wait
*/
Wait();
~Wait();
/**
* @brief Initialization to run behavior
* @param command Goal to execute
* @return Status of behavior
*/
Status onRun(const std::shared_ptr<const WaitAction::Goal> command) override;
/**
* @brief Loop function to run behavior
* @return Status of behavior
*/
Status onCycleUpdate() override;
protected:
rclcpp::Time wait_end_;
WaitAction::Feedback::SharedPtr feedback_;
};
} // namespace nav2_behaviors
#endif // NAV2_BEHAVIORS__PLUGINS__WAIT_HPP_
@@ -0,0 +1,275 @@
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef NAV2_BEHAVIORS__TIMED_BEHAVIOR_HPP_
#define NAV2_BEHAVIORS__TIMED_BEHAVIOR_HPP_
#include <memory>
#include <string>
#include <cmath>
#include <chrono>
#include <ctime>
#include <thread>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "tf2_ros/transform_listener.h"
#include "tf2_ros/create_timer_ros.h"
#include "geometry_msgs/msg/twist.hpp"
#include "nav2_util/simple_action_server.hpp"
#include "nav2_util/robot_utils.hpp"
#include "nav2_core/behavior.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include "tf2/utils.h"
#pragma GCC diagnostic pop
namespace nav2_behaviors
{
enum class Status : int8_t
{
SUCCEEDED = 1,
FAILED = 2,
RUNNING = 3,
};
using namespace std::chrono_literals; //NOLINT
/**
* @class nav2_behaviors::Behavior
* @brief An action server Behavior base class implementing the action server and basic factory.
*/
template<typename ActionT>
class TimedBehavior : public nav2_core::Behavior
{
public:
using ActionServer = nav2_util::SimpleActionServer<ActionT>;
/**
* @brief A TimedBehavior constructor
*/
TimedBehavior()
: action_server_(nullptr),
cycle_frequency_(10.0),
enabled_(false),
transform_tolerance_(0.0)
{
}
virtual ~TimedBehavior() = default;
// Derived classes can override this method to catch the command and perform some checks
// before getting into the main loop. The method will only be called
// once and should return SUCCEEDED otherwise behavior will return FAILED.
virtual Status onRun(const std::shared_ptr<const typename ActionT::Goal> command) = 0;
// This is the method derived classes should mainly implement
// and will be called cyclically while it returns RUNNING.
// Implement the behavior such that it runs some unit of work on each call
// and provides a status. The Behavior will finish once SUCCEEDED is returned
// It's up to the derived class to define the final commanded velocity.
virtual Status onCycleUpdate() = 0;
// an opportunity for derived classes to do something on configuration
// if they chose
virtual void onConfigure()
{
}
// an opportunity for derived classes to do something on cleanup
// if they chose
virtual void onCleanup()
{
}
// an opportunity for a derived class to do something on action completion
virtual void onActionCompletion()
{
}
// configure the server on lifecycle setup
void configure(
const rclcpp_lifecycle::LifecycleNode::WeakPtr & parent,
const std::string & name, std::shared_ptr<tf2_ros::Buffer> tf,
std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker) override
{
node_ = parent;
auto node = node_.lock();
logger_ = node->get_logger();
clock_ = node->get_clock();
RCLCPP_INFO(logger_, "Configuring %s", name.c_str());
behavior_name_ = name;
tf_ = tf;
node->get_parameter("cycle_frequency", cycle_frequency_);
node->get_parameter("global_frame", global_frame_);
node->get_parameter("robot_base_frame", robot_base_frame_);
node->get_parameter("transform_tolerance", transform_tolerance_);
action_server_ = std::make_shared<ActionServer>(
node, behavior_name_,
std::bind(&TimedBehavior::execute, this));
collision_checker_ = collision_checker;
vel_pub_ = node->template create_publisher<geometry_msgs::msg::Twist>("cmd_vel", 1);
onConfigure();
}
// Cleanup server on lifecycle transition
void cleanup() override
{
action_server_.reset();
vel_pub_.reset();
onCleanup();
}
// Activate server on lifecycle transition
void activate() override
{
RCLCPP_INFO(logger_, "Activating %s", behavior_name_.c_str());
vel_pub_->on_activate();
action_server_->activate();
enabled_ = true;
}
// Deactivate server on lifecycle transition
void deactivate() override
{
vel_pub_->on_deactivate();
action_server_->deactivate();
enabled_ = false;
}
protected:
rclcpp_lifecycle::LifecycleNode::WeakPtr node_;
std::string behavior_name_;
rclcpp_lifecycle::LifecyclePublisher<geometry_msgs::msg::Twist>::SharedPtr vel_pub_;
std::shared_ptr<ActionServer> action_server_;
std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker_;
std::shared_ptr<tf2_ros::Buffer> tf_;
double cycle_frequency_;
double enabled_;
std::string global_frame_;
std::string robot_base_frame_;
double transform_tolerance_;
rclcpp::Duration elasped_time_{0, 0};
// Clock
rclcpp::Clock::SharedPtr clock_;
// Logger
rclcpp::Logger logger_{rclcpp::get_logger("nav2_behaviors")};
// Main execution callbacks for the action server implementation calling the Behavior's
// onRun and cycle functions to execute a specific behavior
void execute()
{
RCLCPP_INFO(logger_, "Running %s", behavior_name_.c_str());
if (!enabled_) {
RCLCPP_WARN(
logger_,
"Called while inactive, ignoring request.");
return;
}
if (onRun(action_server_->get_current_goal()) != Status::SUCCEEDED) {
RCLCPP_INFO(
logger_,
"Initial checks failed for %s", behavior_name_.c_str());
action_server_->terminate_current();
return;
}
auto start_time = clock_->now();
// Initialize the ActionT result
auto result = std::make_shared<typename ActionT::Result>();
rclcpp::WallRate loop_rate(cycle_frequency_);
while (rclcpp::ok()) {
elasped_time_ = clock_->now() - start_time;
if (action_server_->is_cancel_requested()) {
RCLCPP_INFO(logger_, "Canceling %s", behavior_name_.c_str());
stopRobot();
result->total_elapsed_time = elasped_time_;
action_server_->terminate_all(result);
onActionCompletion();
return;
}
// TODO(orduno) #868 Enable preempting a Behavior on-the-fly without stopping
if (action_server_->is_preempt_requested()) {
RCLCPP_ERROR(
logger_, "Received a preemption request for %s,"
" however feature is currently not implemented. Aborting and stopping.",
behavior_name_.c_str());
stopRobot();
result->total_elapsed_time = clock_->now() - start_time;
action_server_->terminate_current(result);
onActionCompletion();
return;
}
switch (onCycleUpdate()) {
case Status::SUCCEEDED:
RCLCPP_INFO(
logger_,
"%s completed successfully", behavior_name_.c_str());
result->total_elapsed_time = clock_->now() - start_time;
action_server_->succeeded_current(result);
onActionCompletion();
return;
case Status::FAILED:
RCLCPP_WARN(logger_, "%s failed", behavior_name_.c_str());
result->total_elapsed_time = clock_->now() - start_time;
action_server_->terminate_current(result);
onActionCompletion();
return;
case Status::RUNNING:
default:
loop_rate.sleep();
break;
}
}
}
// Stop the robot with a commanded velocity
void stopRobot()
{
auto cmd_vel = std::make_unique<geometry_msgs::msg::Twist>();
cmd_vel->linear.x = 0.0;
cmd_vel->linear.y = 0.0;
cmd_vel->angular.z = 0.0;
vel_pub_->publish(std::move(cmd_vel));
}
};
} // namespace nav2_behaviors
#endif // NAV2_BEHAVIORS__TIMED_BEHAVIOR_HPP_
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>nav2_behaviors</name>
<version>1.1.18</version>
<description>TODO</description>
<maintainer email="carlos.a.orduno@intel.com">Carlos Orduno</maintainer>
<maintainer email="stevenmacenski@gmail.com">Steve Macenski</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>nav2_common</build_depend>
<build_depend>rclcpp</build_depend>
<build_depend>rclcpp_action</build_depend>
<build_depend>rclcpp_lifecycle</build_depend>
<build_depend>nav2_behavior_tree</build_depend>
<build_depend>nav2_util</build_depend>
<build_depend>nav2_msgs</build_depend>
<build_depend>nav_msgs</build_depend>
<build_depend>tf2</build_depend>
<build_depend>tf2_geometry_msgs</build_depend>
<build_depend>geometry_msgs</build_depend>
<build_depend>nav2_costmap_2d</build_depend>
<build_depend>nav2_core</build_depend>
<build_depend>pluginlib</build_depend>
<exec_depend>rclcpp</exec_depend>
<exec_depend>rclcpp_action</exec_depend>
<exec_depend>rclcpp_lifecycle</exec_depend>
<exec_depend>nav2_behavior_tree</exec_depend>
<exec_depend>nav2_util</exec_depend>
<exec_depend>nav2_msgs</exec_depend>
<exec_depend>nav_msgs</exec_depend>
<exec_depend>geometry_msgs</exec_depend>
<exec_depend>nav2_costmap_2d</exec_depend>
<exec_depend>nav2_core</exec_depend>
<exec_depend>pluginlib</exec_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_cmake_gtest</test_depend>
<export>
<build_type>ament_cmake</build_type>
<nav2_core plugin="${prefix}/behavior_plugin.xml" />
</export>
</package>
@@ -0,0 +1,186 @@
// Copyright (c) 2022 Joshua Wallace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <utility>
#include "nav2_behaviors/plugins/assisted_teleop.hpp"
#include "nav2_util/node_utils.hpp"
namespace nav2_behaviors
{
AssistedTeleop::AssistedTeleop()
: TimedBehavior<AssistedTeleopAction>(),
feedback_(std::make_shared<AssistedTeleopAction::Feedback>())
{}
void AssistedTeleop::onConfigure()
{
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
// set up parameters
nav2_util::declare_parameter_if_not_declared(
node,
"projection_time", rclcpp::ParameterValue(1.0));
nav2_util::declare_parameter_if_not_declared(
node,
"simulation_time_step", rclcpp::ParameterValue(0.1));
nav2_util::declare_parameter_if_not_declared(
node,
"cmd_vel_teleop", rclcpp::ParameterValue(std::string("cmd_vel_teleop")));
node->get_parameter("projection_time", projection_time_);
node->get_parameter("simulation_time_step", simulation_time_step_);
std::string cmd_vel_teleop;
node->get_parameter("cmd_vel_teleop", cmd_vel_teleop);
vel_sub_ = node->create_subscription<geometry_msgs::msg::Twist>(
cmd_vel_teleop, rclcpp::SystemDefaultsQoS(),
std::bind(
&AssistedTeleop::teleopVelocityCallback,
this, std::placeholders::_1));
preempt_teleop_sub_ = node->create_subscription<std_msgs::msg::Empty>(
"preempt_teleop", rclcpp::SystemDefaultsQoS(),
std::bind(
&AssistedTeleop::preemptTeleopCallback,
this, std::placeholders::_1));
}
Status AssistedTeleop::onRun(const std::shared_ptr<const AssistedTeleopAction::Goal> command)
{
preempt_teleop_ = false;
command_time_allowance_ = command->time_allowance;
end_time_ = this->clock_->now() + command_time_allowance_;
return Status::SUCCEEDED;
}
void AssistedTeleop::onActionCompletion()
{
teleop_twist_ = geometry_msgs::msg::Twist();
preempt_teleop_ = false;
}
Status AssistedTeleop::onCycleUpdate()
{
feedback_->current_teleop_duration = elasped_time_;
action_server_->publish_feedback(feedback_);
rclcpp::Duration time_remaining = end_time_ - this->clock_->now();
if (time_remaining.seconds() < 0.0 && command_time_allowance_.seconds() > 0.0) {
stopRobot();
RCLCPP_WARN_STREAM(
logger_,
"Exceeded time allowance before reaching the " << behavior_name_.c_str() <<
"goal - Exiting " << behavior_name_.c_str());
return Status::FAILED;
}
// user states that teleop was successful
if (preempt_teleop_) {
stopRobot();
return Status::SUCCEEDED;
}
geometry_msgs::msg::PoseStamped current_pose;
if (!nav2_util::getCurrentPose(
current_pose, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_ERROR_STREAM(
logger_,
"Current robot pose is not available for " <<
behavior_name_.c_str());
return Status::FAILED;
}
geometry_msgs::msg::Pose2D projected_pose;
projected_pose.x = current_pose.pose.position.x;
projected_pose.y = current_pose.pose.position.y;
projected_pose.theta = tf2::getYaw(current_pose.pose.orientation);
geometry_msgs::msg::Twist scaled_twist = teleop_twist_;
for (double time = simulation_time_step_; time < projection_time_;
time += simulation_time_step_)
{
projected_pose = projectPose(projected_pose, teleop_twist_, simulation_time_step_);
if (!collision_checker_->isCollisionFree(projected_pose)) {
if (time == simulation_time_step_) {
RCLCPP_DEBUG_STREAM_THROTTLE(
logger_,
*clock_,
1000,
behavior_name_.c_str() << " collided on first time step, setting velocity to zero");
scaled_twist.linear.x = 0.0f;
scaled_twist.linear.y = 0.0f;
scaled_twist.angular.z = 0.0f;
break;
} else {
RCLCPP_DEBUG_STREAM_THROTTLE(
logger_,
*clock_,
1000,
behavior_name_.c_str() << " collision approaching in " << time << " seconds");
double scale_factor = time / projection_time_;
scaled_twist.linear.x *= scale_factor;
scaled_twist.linear.y *= scale_factor;
scaled_twist.angular.z *= scale_factor;
break;
}
}
}
vel_pub_->publish(std::move(scaled_twist));
return Status::RUNNING;
}
geometry_msgs::msg::Pose2D AssistedTeleop::projectPose(
const geometry_msgs::msg::Pose2D & pose,
const geometry_msgs::msg::Twist & twist,
double projection_time)
{
geometry_msgs::msg::Pose2D projected_pose = pose;
projected_pose.x += projection_time * (
twist.linear.x * cos(pose.theta) +
twist.linear.y * sin(pose.theta));
projected_pose.y += projection_time * (
twist.linear.x * sin(pose.theta) -
twist.linear.y * cos(pose.theta));
projected_pose.theta += projection_time * twist.angular.z;
return projected_pose;
}
void AssistedTeleop::teleopVelocityCallback(const geometry_msgs::msg::Twist::SharedPtr msg)
{
teleop_twist_ = *msg;
}
void AssistedTeleop::preemptTeleopCallback(const std_msgs::msg::Empty::SharedPtr)
{
preempt_teleop_ = true;
}
} // namespace nav2_behaviors
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_behaviors::AssistedTeleop, nav2_core::Behavior)
@@ -0,0 +1,50 @@
// Copyright (c) 2022 Joshua Wallace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "nav2_behaviors/plugins/back_up.hpp"
namespace nav2_behaviors
{
Status BackUp::onRun(const std::shared_ptr<const BackUpAction::Goal> command)
{
if (command->target.y != 0.0 || command->target.z != 0.0) {
RCLCPP_INFO(
logger_,
"Backing up in Y and Z not supported, will only move in X.");
return Status::FAILED;
}
// Silently ensure that both the speed and direction are negative.
command_x_ = -std::fabs(command->target.x);
command_speed_ = -std::fabs(command->speed);
command_time_allowance_ = command->time_allowance;
end_time_ = this->clock_->now() + command_time_allowance_;
if (!nav2_util::getCurrentPose(
initial_pose_, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_ERROR(logger_, "Initial robot pose is not available.");
return Status::FAILED;
}
return Status::SUCCEEDED;
}
} // namespace nav2_behaviors
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_behaviors::BackUp, nav2_core::Behavior)
@@ -0,0 +1,20 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2022 Joshua Wallace
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "nav2_behaviors/plugins/drive_on_heading.hpp"
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_behaviors::DriveOnHeading<>, nav2_core::Behavior)
+192
View File
@@ -0,0 +1,192 @@
// Copyright (c) 2018 Intel Corporation, 2019 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cmath>
#include <thread>
#include <algorithm>
#include <memory>
#include <utility>
#include "nav2_behaviors/plugins/spin.hpp"
#include "tf2/utils.h"
#include "tf2_geometry_msgs/tf2_geometry_msgs.hpp"
#include "nav2_util/node_utils.hpp"
using namespace std::chrono_literals;
namespace nav2_behaviors
{
Spin::Spin()
: TimedBehavior<SpinAction>(),
feedback_(std::make_shared<SpinAction::Feedback>()),
min_rotational_vel_(0.0),
max_rotational_vel_(0.0),
rotational_acc_lim_(0.0),
cmd_yaw_(0.0),
prev_yaw_(0.0),
relative_yaw_(0.0),
simulate_ahead_time_(0.0)
{
}
Spin::~Spin() = default;
void Spin::onConfigure()
{
auto node = node_.lock();
if (!node) {
throw std::runtime_error{"Failed to lock node"};
}
nav2_util::declare_parameter_if_not_declared(
node,
"simulate_ahead_time", rclcpp::ParameterValue(2.0));
node->get_parameter("simulate_ahead_time", simulate_ahead_time_);
nav2_util::declare_parameter_if_not_declared(
node,
"max_rotational_vel", rclcpp::ParameterValue(1.0));
node->get_parameter("max_rotational_vel", max_rotational_vel_);
nav2_util::declare_parameter_if_not_declared(
node,
"min_rotational_vel", rclcpp::ParameterValue(0.4));
node->get_parameter("min_rotational_vel", min_rotational_vel_);
nav2_util::declare_parameter_if_not_declared(
node,
"rotational_acc_lim", rclcpp::ParameterValue(3.2));
node->get_parameter("rotational_acc_lim", rotational_acc_lim_);
}
Status Spin::onRun(const std::shared_ptr<const SpinAction::Goal> command)
{
geometry_msgs::msg::PoseStamped current_pose;
if (!nav2_util::getCurrentPose(
current_pose, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_ERROR(logger_, "Current robot pose is not available.");
return Status::FAILED;
}
prev_yaw_ = tf2::getYaw(current_pose.pose.orientation);
relative_yaw_ = 0.0;
cmd_yaw_ = command->target_yaw;
RCLCPP_INFO(
logger_, "Turning %0.2f for spin behavior.",
cmd_yaw_);
command_time_allowance_ = command->time_allowance;
end_time_ = this->clock_->now() + command_time_allowance_;
return Status::SUCCEEDED;
}
Status Spin::onCycleUpdate()
{
rclcpp::Duration time_remaining = end_time_ - this->clock_->now();
if (time_remaining.seconds() < 0.0 && command_time_allowance_.seconds() > 0.0) {
stopRobot();
RCLCPP_WARN(
logger_,
"Exceeded time allowance before reaching the Spin goal - Exiting Spin");
return Status::FAILED;
}
geometry_msgs::msg::PoseStamped current_pose;
if (!nav2_util::getCurrentPose(
current_pose, *tf_, global_frame_, robot_base_frame_,
transform_tolerance_))
{
RCLCPP_ERROR(logger_, "Current robot pose is not available.");
return Status::FAILED;
}
const double current_yaw = tf2::getYaw(current_pose.pose.orientation);
double delta_yaw = current_yaw - prev_yaw_;
if (abs(delta_yaw) > M_PI) {
delta_yaw = copysign(2 * M_PI - abs(delta_yaw), prev_yaw_);
}
relative_yaw_ += delta_yaw;
prev_yaw_ = current_yaw;
feedback_->angular_distance_traveled = static_cast<float>(relative_yaw_);
action_server_->publish_feedback(feedback_);
double remaining_yaw = abs(cmd_yaw_) - abs(relative_yaw_);
if (remaining_yaw < 1e-6) {
stopRobot();
return Status::SUCCEEDED;
}
double vel = sqrt(2 * rotational_acc_lim_ * remaining_yaw);
vel = std::min(std::max(vel, min_rotational_vel_), max_rotational_vel_);
auto cmd_vel = std::make_unique<geometry_msgs::msg::Twist>();
cmd_vel->angular.z = copysign(vel, cmd_yaw_);
geometry_msgs::msg::Pose2D pose2d;
pose2d.x = current_pose.pose.position.x;
pose2d.y = current_pose.pose.position.y;
pose2d.theta = tf2::getYaw(current_pose.pose.orientation);
if (!isCollisionFree(relative_yaw_, cmd_vel.get(), pose2d)) {
stopRobot();
RCLCPP_WARN(logger_, "Collision Ahead - Exiting Spin");
return Status::FAILED;
}
vel_pub_->publish(std::move(cmd_vel));
return Status::RUNNING;
}
bool Spin::isCollisionFree(
const double & relative_yaw,
geometry_msgs::msg::Twist * cmd_vel,
geometry_msgs::msg::Pose2D & pose2d)
{
// Simulate ahead by simulate_ahead_time_ in cycle_frequency_ increments
int cycle_count = 0;
double sim_position_change;
const int max_cycle_count = static_cast<int>(cycle_frequency_ * simulate_ahead_time_);
geometry_msgs::msg::Pose2D init_pose = pose2d;
bool fetch_data = true;
while (cycle_count < max_cycle_count) {
sim_position_change = cmd_vel->angular.z * (cycle_count / cycle_frequency_);
pose2d.theta = init_pose.theta + sim_position_change;
cycle_count++;
if (abs(relative_yaw) - abs(sim_position_change) <= 0.) {
break;
}
if (!collision_checker_->isCollisionFree(pose2d, fetch_data)) {
return false;
}
fetch_data = false;
}
return true;
}
} // namespace nav2_behaviors
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_behaviors::Spin, nav2_core::Behavior)
@@ -0,0 +1,55 @@
// Copyright (c) 2019 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <memory>
#include "nav2_behaviors/plugins/wait.hpp"
namespace nav2_behaviors
{
Wait::Wait()
: TimedBehavior<WaitAction>(),
feedback_(std::make_shared<WaitAction::Feedback>())
{
}
Wait::~Wait() = default;
Status Wait::onRun(const std::shared_ptr<const WaitAction::Goal> command)
{
wait_end_ = node_.lock()->now() + rclcpp::Duration(command->time);
return Status::SUCCEEDED;
}
Status Wait::onCycleUpdate()
{
auto current_point = node_.lock()->now();
auto time_left = wait_end_ - current_point;
feedback_->time_left = time_left;
action_server_->publish_feedback(feedback_);
if (time_left.nanoseconds() > 0) {
return Status::RUNNING;
} else {
return Status::SUCCEEDED;
}
}
} // namespace nav2_behaviors
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(nav2_behaviors::Wait, nav2_core::Behavior)
@@ -0,0 +1,192 @@
// Copyright (c) 2018 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <memory>
#include <string>
#include <vector>
#include <utility>
#include "nav2_util/node_utils.hpp"
#include "nav2_behaviors/behavior_server.hpp"
namespace behavior_server
{
BehaviorServer::BehaviorServer(const rclcpp::NodeOptions & options)
: LifecycleNode("behavior_server", "", options),
plugin_loader_("nav2_core", "nav2_core::Behavior"),
default_ids_{"spin", "backup", "drive_on_heading", "wait"},
default_types_{"nav2_behaviors/Spin",
"nav2_behaviors/BackUp",
"nav2_behaviors/DriveOnHeading",
"nav2_behaviors/Wait"}
{
declare_parameter(
"costmap_topic",
rclcpp::ParameterValue(std::string("local_costmap/costmap_raw")));
declare_parameter(
"footprint_topic",
rclcpp::ParameterValue(std::string("local_costmap/published_footprint")));
declare_parameter("cycle_frequency", rclcpp::ParameterValue(10.0));
declare_parameter("behavior_plugins", default_ids_);
get_parameter("behavior_plugins", behavior_ids_);
if (behavior_ids_ == default_ids_) {
for (size_t i = 0; i < default_ids_.size(); ++i) {
declare_parameter(default_ids_[i] + ".plugin", default_types_[i]);
}
}
declare_parameter(
"global_frame",
rclcpp::ParameterValue(std::string("odom")));
declare_parameter(
"robot_base_frame",
rclcpp::ParameterValue(std::string("base_link")));
declare_parameter(
"transform_tolerance",
rclcpp::ParameterValue(0.1));
}
BehaviorServer::~BehaviorServer()
{
behaviors_.clear();
}
nav2_util::CallbackReturn
BehaviorServer::on_configure(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Configuring");
tf_ = std::make_shared<tf2_ros::Buffer>(get_clock());
auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
get_node_base_interface(),
get_node_timers_interface());
tf_->setCreateTimerInterface(timer_interface);
transform_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_);
std::string costmap_topic, footprint_topic, robot_base_frame;
double transform_tolerance;
this->get_parameter("costmap_topic", costmap_topic);
this->get_parameter("footprint_topic", footprint_topic);
this->get_parameter("transform_tolerance", transform_tolerance);
this->get_parameter("robot_base_frame", robot_base_frame);
costmap_sub_ = std::make_unique<nav2_costmap_2d::CostmapSubscriber>(
shared_from_this(), costmap_topic);
footprint_sub_ = std::make_unique<nav2_costmap_2d::FootprintSubscriber>(
shared_from_this(), footprint_topic, *tf_, robot_base_frame, transform_tolerance);
collision_checker_ = std::make_shared<nav2_costmap_2d::CostmapTopicCollisionChecker>(
*costmap_sub_, *footprint_sub_, this->get_name());
behavior_types_.resize(behavior_ids_.size());
if (!loadBehaviorPlugins()) {
return nav2_util::CallbackReturn::FAILURE;
}
return nav2_util::CallbackReturn::SUCCESS;
}
bool
BehaviorServer::loadBehaviorPlugins()
{
auto node = shared_from_this();
for (size_t i = 0; i != behavior_ids_.size(); i++) {
behavior_types_[i] = nav2_util::get_plugin_type_param(node, behavior_ids_[i]);
try {
RCLCPP_INFO(
get_logger(), "Creating behavior plugin %s of type %s",
behavior_ids_[i].c_str(), behavior_types_[i].c_str());
behaviors_.push_back(plugin_loader_.createUniqueInstance(behavior_types_[i]));
behaviors_.back()->configure(node, behavior_ids_[i], tf_, collision_checker_);
} catch (const pluginlib::PluginlibException & ex) {
RCLCPP_FATAL(
get_logger(), "Failed to create behavior %s of type %s."
" Exception: %s", behavior_ids_[i].c_str(), behavior_types_[i].c_str(),
ex.what());
return false;
}
}
return true;
}
nav2_util::CallbackReturn
BehaviorServer::on_activate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Activating");
std::vector<pluginlib::UniquePtr<nav2_core::Behavior>>::iterator iter;
for (iter = behaviors_.begin(); iter != behaviors_.end(); ++iter) {
(*iter)->activate();
}
// create bond connection
createBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
BehaviorServer::on_deactivate(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Deactivating");
std::vector<pluginlib::UniquePtr<nav2_core::Behavior>>::iterator iter;
for (iter = behaviors_.begin(); iter != behaviors_.end(); ++iter) {
(*iter)->deactivate();
}
// destroy bond connection
destroyBond();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
BehaviorServer::on_cleanup(const rclcpp_lifecycle::State & /*state*/)
{
RCLCPP_INFO(get_logger(), "Cleaning up");
std::vector<pluginlib::UniquePtr<nav2_core::Behavior>>::iterator iter;
for (iter = behaviors_.begin(); iter != behaviors_.end(); ++iter) {
(*iter)->cleanup();
}
behaviors_.clear();
transform_listener_.reset();
tf_.reset();
footprint_sub_.reset();
costmap_sub_.reset();
collision_checker_.reset();
return nav2_util::CallbackReturn::SUCCESS;
}
nav2_util::CallbackReturn
BehaviorServer::on_shutdown(const rclcpp_lifecycle::State &)
{
RCLCPP_INFO(get_logger(), "Shutting down");
return nav2_util::CallbackReturn::SUCCESS;
}
} // end namespace behavior_server
#include "rclcpp_components/register_node_macro.hpp"
// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(behavior_server::BehaviorServer)
+30
View File
@@ -0,0 +1,30 @@
// Copyright (c) 2018 Intel Corporation
// Copyright (c) 2019 Samsung Research America
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <memory>
#include "nav2_behaviors/behavior_server.hpp"
#include "rclcpp/rclcpp.hpp"
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto recoveries_node = std::make_shared<behavior_server::BehaviorServer>();
rclcpp::spin(recoveries_node->get_node_base_interface());
rclcpp::shutdown();
return 0;
}
@@ -0,0 +1,7 @@
ament_add_gtest(test_behaviors
test_behaviors.cpp
)
ament_target_dependencies(test_behaviors
${dependencies}
)
@@ -0,0 +1,268 @@
// Copyright (c) 2019 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. Reserved.
#include <string>
#include <memory>
#include <chrono>
#include <iostream>
#include <thread>
#include "gtest/gtest.h"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "nav2_behaviors/timed_behavior.hpp"
#include "nav2_msgs/action/dummy_behavior.hpp"
using nav2_behaviors::TimedBehavior;
using nav2_behaviors::Status;
using BehaviorAction = nav2_msgs::action::DummyBehavior;
using ClientGoalHandle = rclcpp_action::ClientGoalHandle<BehaviorAction>;
using namespace std::chrono_literals;
// A behavior for testing the base class
class DummyBehavior : public TimedBehavior<BehaviorAction>
{
public:
DummyBehavior()
: TimedBehavior<BehaviorAction>(),
initialized_(false) {}
~DummyBehavior() = default;
Status onRun(const std::shared_ptr<const BehaviorAction::Goal> goal) override
{
// A normal behavior would catch the command and initialize
initialized_ = false;
command_ = goal->command.data;
start_time_ = std::chrono::system_clock::now();
// onRun method can have various possible outcomes (success, failure, cancelled)
// The output is defined by the tester class on the command string.
if (command_ == "Testing success" || command_ == "Testing failure on run") {
initialized_ = true;
return Status::SUCCEEDED;
}
return Status::FAILED;
}
Status onCycleUpdate() override
{
// A normal behavior would set the robot in motion in the first call
// and check for robot states on subsequent calls to check if the movement
// was completed.
if (command_ != "Testing success" || !initialized_) {
return Status::FAILED;
}
// For testing, pretend the robot takes some fixed
// amount of time to complete the motion.
auto current_time = std::chrono::system_clock::now();
auto motion_duration = 1s;
if (current_time - start_time_ >= motion_duration) {
// Movement was completed
return Status::SUCCEEDED;
}
return Status::RUNNING;
}
private:
bool initialized_;
std::string command_;
std::chrono::system_clock::time_point start_time_;
};
// Define a test class to hold the context for the tests
class BehaviorTest : public ::testing::Test
{
protected:
BehaviorTest() {SetUp();}
~BehaviorTest() = default;
void SetUp() override
{
node_lifecycle_ =
std::make_shared<rclcpp_lifecycle::LifecycleNode>(
"LifecycleBehaviorTestNode", rclcpp::NodeOptions());
node_lifecycle_->declare_parameter(
"costmap_topic",
rclcpp::ParameterValue(std::string("local_costmap/costmap_raw")));
node_lifecycle_->declare_parameter(
"footprint_topic",
rclcpp::ParameterValue(std::string("local_costmap/published_footprint")));
tf_buffer_ = std::make_shared<tf2_ros::Buffer>(node_lifecycle_->get_clock());
auto timer_interface = std::make_shared<tf2_ros::CreateTimerROS>(
node_lifecycle_->get_node_base_interface(),
node_lifecycle_->get_node_timers_interface());
tf_buffer_->setCreateTimerInterface(timer_interface);
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
std::string costmap_topic, footprint_topic;
node_lifecycle_->get_parameter("costmap_topic", costmap_topic);
node_lifecycle_->get_parameter("footprint_topic", footprint_topic);
std::shared_ptr<nav2_costmap_2d::CostmapSubscriber> costmap_sub_ =
std::make_shared<nav2_costmap_2d::CostmapSubscriber>(
node_lifecycle_, costmap_topic);
std::shared_ptr<nav2_costmap_2d::FootprintSubscriber> footprint_sub_ =
std::make_shared<nav2_costmap_2d::FootprintSubscriber>(
node_lifecycle_, footprint_topic, *tf_buffer_);
std::shared_ptr<nav2_costmap_2d::CostmapTopicCollisionChecker> collision_checker_ =
std::make_shared<nav2_costmap_2d::CostmapTopicCollisionChecker>(
*costmap_sub_, *footprint_sub_,
node_lifecycle_->get_name());
behavior_ = std::make_shared<DummyBehavior>();
behavior_->configure(node_lifecycle_, "Behavior", tf_buffer_, collision_checker_);
behavior_->activate();
client_ = rclcpp_action::create_client<BehaviorAction>(
node_lifecycle_->get_node_base_interface(),
node_lifecycle_->get_node_graph_interface(),
node_lifecycle_->get_node_logging_interface(),
node_lifecycle_->get_node_waitables_interface(), "Behavior");
std::cout << "Setup complete." << std::endl;
}
void TearDown() override {}
bool sendCommand(const std::string & command)
{
if (!client_->wait_for_action_server(4s)) {
std::cout << "Server not up" << std::endl;
return false;
}
auto goal = BehaviorAction::Goal();
goal.command.data = command;
auto future_goal = client_->async_send_goal(goal);
if (rclcpp::spin_until_future_complete(node_lifecycle_, future_goal) !=
rclcpp::FutureReturnCode::SUCCESS)
{
std::cout << "failed sending goal" << std::endl;
// failed sending the goal
return false;
}
goal_handle_ = future_goal.get();
if (!goal_handle_) {
std::cout << "goal was rejected" << std::endl;
// goal was rejected by the action server
return false;
}
return true;
}
Status getOutcome()
{
if (getResult().code == rclcpp_action::ResultCode::SUCCEEDED) {
return Status::SUCCEEDED;
}
return Status::FAILED;
}
ClientGoalHandle::WrappedResult getResult()
{
std::cout << "Getting async result..." << std::endl;
auto future_result = client_->async_get_result(goal_handle_);
std::cout << "Waiting on future..." << std::endl;
rclcpp::spin_until_future_complete(node_lifecycle_, future_result);
std::cout << "future received!" << std::endl;
return future_result.get();
}
std::shared_ptr<rclcpp_lifecycle::LifecycleNode> node_lifecycle_;
std::shared_ptr<DummyBehavior> behavior_;
std::shared_ptr<rclcpp_action::Client<BehaviorAction>> client_;
std::shared_ptr<rclcpp_action::ClientGoalHandle<BehaviorAction>> goal_handle_;
std::shared_ptr<tf2_ros::Buffer> tf_buffer_;
std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
};
// Define the tests
TEST_F(BehaviorTest, testingSuccess)
{
ASSERT_TRUE(sendCommand("Testing success"));
EXPECT_EQ(getOutcome(), Status::SUCCEEDED);
SUCCEED();
}
TEST_F(BehaviorTest, testingFailureOnRun)
{
ASSERT_TRUE(sendCommand("Testing failure on run"));
EXPECT_EQ(getOutcome(), Status::FAILED);
SUCCEED();
}
TEST_F(BehaviorTest, testingFailureOnInit)
{
ASSERT_TRUE(sendCommand("Testing failure on init"));
EXPECT_EQ(getOutcome(), Status::FAILED);
SUCCEED();
}
TEST_F(BehaviorTest, testingSequentialFailures)
{
ASSERT_TRUE(sendCommand("Testing failure on run"));
EXPECT_EQ(getOutcome(), Status::FAILED);
SUCCEED();
}
TEST_F(BehaviorTest, testingTotalElapsedTimeIsGratherThanZeroIfStarted)
{
ASSERT_TRUE(sendCommand("Testing success"));
EXPECT_GT(getResult().result->total_elapsed_time.sec, 0.0);
SUCCEED();
}
TEST_F(BehaviorTest, testingTotalElapsedTimeIsZeroIfFailureOnInit)
{
ASSERT_TRUE(sendCommand("Testing failure on init"));
EXPECT_EQ(getResult().result->total_elapsed_time.sec, 0.0);
SUCCEED();
}
TEST_F(BehaviorTest, testingTotalElapsedTimeIsZeroIfFailureOnRun)
{
ASSERT_TRUE(sendCommand("Testing failure on run"));
EXPECT_EQ(getResult().result->total_elapsed_time.sec, 0.0);
SUCCEED();
}
int main(int argc, char ** argv)
{
::testing::InitGoogleTest(&argc, argv);
// initialize ROS
rclcpp::init(argc, argv);
bool all_successful = RUN_ALL_TESTS();
// shutdown ROS
rclcpp::shutdown();
return all_successful;
}