diff --git a/nav2_msgs/CMakeLists.txt b/nav2_msgs/CMakeLists.txt index 7231188f33440d272f1cbc72c9122f18a1af9608..39d31d663be213e716ddcdca93562a40ea782809 100644 --- a/nav2_msgs/CMakeLists.txt +++ b/nav2_msgs/CMakeLists.txt @@ -7,21 +7,29 @@ find_package(builtin_interfaces REQUIRED) find_package(geometry_msgs REQUIRED) find_package(rosidl_default_generators REQUIRED) find_package(std_msgs REQUIRED) +find_package(action_msgs REQUIRED) nav2_package() rosidl_generate_interfaces(${PROJECT_NAME} "msg/Costmap.msg" "msg/CostmapMetaData.msg" - "msg/Path.msg" "msg/MissionPlan.msg" + "msg/Path.msg" "msg/TaskStatus.msg" "msg/VoxelGrid.msg" "srv/GetCostmap.srv" "srv/ClearCostmapExceptRegion.srv" "srv/ClearCostmapAroundRobot.srv" "srv/ClearEntireCostmap.srv" - DEPENDENCIES builtin_interfaces geometry_msgs std_msgs + "action/BackUp.action" + "action/ComputePathToPose.action" + "action/ExecuteMission.action" + "action/FollowPath.action" + "action/NavigateToPose.action" + "action/Spin.action" + "action/Stop.action" + DEPENDENCIES builtin_interfaces geometry_msgs std_msgs action_msgs ) ament_export_dependencies(rosidl_default_runtime) diff --git a/nav2_msgs/action/BackUp.action b/nav2_msgs/action/BackUp.action new file mode 100644 index 0000000000000000000000000000000000000000..a5cf8f3625341b7ace4dd10aaaaaedf46925aac3 --- /dev/null +++ b/nav2_msgs/action/BackUp.action @@ -0,0 +1,7 @@ +#goal definition +geometry_msgs/Point target_location +--- +#result definition +std_msgs/Empty result +--- +#feedback diff --git a/nav2_msgs/action/ComputePathToPose.action b/nav2_msgs/action/ComputePathToPose.action new file mode 100644 index 0000000000000000000000000000000000000000..4af3b3da1345e1da5fd0bcd8fc2083e8770cd609 --- /dev/null +++ b/nav2_msgs/action/ComputePathToPose.action @@ -0,0 +1,7 @@ +#goal definition +geometry_msgs/PoseStamped pose +--- +#result definition +nav2_msgs/Path path +--- +#feedback diff --git a/nav2_msgs/action/ExecuteMission.action b/nav2_msgs/action/ExecuteMission.action new file mode 100644 index 0000000000000000000000000000000000000000..3d3e357854b5604e511affb37a3408880ccc44ec --- /dev/null +++ b/nav2_msgs/action/ExecuteMission.action @@ -0,0 +1,7 @@ +#goal definition +nav2_msgs/MissionPlan mission_plan +--- +#result definition +std_msgs/Empty result +--- +#feedback diff --git a/nav2_msgs/action/FollowPath.action b/nav2_msgs/action/FollowPath.action new file mode 100644 index 0000000000000000000000000000000000000000..33487826176a1b0a88607d308bfc37cfba9151bd --- /dev/null +++ b/nav2_msgs/action/FollowPath.action @@ -0,0 +1,7 @@ +#goal definition +nav2_msgs/Path path +--- +#result definition +std_msgs/Empty result +--- +#feedback diff --git a/nav2_msgs/action/NavigateToPose.action b/nav2_msgs/action/NavigateToPose.action new file mode 100644 index 0000000000000000000000000000000000000000..10c7d5de2cceaa4cfd0096756f2877062cfeb603 --- /dev/null +++ b/nav2_msgs/action/NavigateToPose.action @@ -0,0 +1,7 @@ +#goal definition +geometry_msgs/PoseStamped pose +--- +#result definition +std_msgs/Empty result +--- +#feedback diff --git a/nav2_msgs/action/Spin.action b/nav2_msgs/action/Spin.action new file mode 100644 index 0000000000000000000000000000000000000000..ef3cff2aaa86c411b636ab74dd678bbdeeb5b712 --- /dev/null +++ b/nav2_msgs/action/Spin.action @@ -0,0 +1,7 @@ +#goal definition +geometry_msgs/QuaternionStamped goal +--- +#result definition +std_msgs/Empty result +--- +#feedback diff --git a/nav2_msgs/action/Stop.action b/nav2_msgs/action/Stop.action new file mode 100644 index 0000000000000000000000000000000000000000..b4dc6958c038a7a6983b5de8e80a3124d3697b45 --- /dev/null +++ b/nav2_msgs/action/Stop.action @@ -0,0 +1,7 @@ +#goal definition +std_msgs/Empty goal +--- +#result definition +std_msgs/Empty result +--- +#feedback diff --git a/nav2_msgs/package.xml b/nav2_msgs/package.xml index b0b3dc6200458eed7dbbebb789d2a0101ffac56c..6ec75525337e58d1c2c930eeae825f8e0322849e 100644 --- a/nav2_msgs/package.xml +++ b/nav2_msgs/package.xml @@ -17,12 +17,14 @@ <build_depend>builtin_interfaces</build_depend> <build_depend>rosidl_default_generators</build_depend> <build_depend>geometry_msgs</build_depend> + <build_depend>action_msgs</build_depend> <exec_depend>rclcpp</exec_depend> <exec_depend>std_msgs</exec_depend> <exec_depend>builtin_interfaces</exec_depend> <exec_depend>rosidl_default_runtime</exec_depend> <exec_depend>geometry_msgs</exec_depend> + <exec_depend>action_msgs</exec_depend> <test_depend>ament_lint_common</test_depend> <test_depend>ament_lint_auto</test_depend> diff --git a/nav2_util/CMakeLists.txt b/nav2_util/CMakeLists.txt index 164a90d0e449e6267f665792408677aff714b9db..945b05575bf493dab2b98900c98541fac4b9d299 100644 --- a/nav2_util/CMakeLists.txt +++ b/nav2_util/CMakeLists.txt @@ -12,6 +12,8 @@ find_package(SDL_image REQUIRED) find_package(nav_msgs REQUIRED) find_package(rclcpp REQUIRED) find_package(lifecycle_msgs REQUIRED) +find_package(rclcpp_action REQUIRED) +find_package(test_msgs REQUIRED) nav2_package() @@ -34,6 +36,8 @@ ament_target_dependencies(${library_name} nav_msgs geometry_msgs lifecycle_msgs + rclcpp_action + test_msgs ) add_library(map_lib SHARED diff --git a/nav2_util/include/nav2_util/lifecycle_utils.hpp b/nav2_util/include/nav2_util/lifecycle_utils.hpp index 186ab72ad5e0892ad0915c97696eaeeb3ed5686e..241411d41b578298dd877b5773178293d0068b87 100644 --- a/nav2_util/include/nav2_util/lifecycle_utils.hpp +++ b/nav2_util/include/nav2_util/lifecycle_utils.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018 Intel Corporation +// 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. diff --git a/nav2_util/include/nav2_util/simple_action_server.hpp b/nav2_util/include/nav2_util/simple_action_server.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6d79928028578d2320999217944fba25a6887274 --- /dev/null +++ b/nav2_util/include/nav2_util/simple_action_server.hpp @@ -0,0 +1,106 @@ +// 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. + +#ifndef NAV2_UTIL__SIMPLE_ACTION_SERVER_HPP_ +#define NAV2_UTIL__SIMPLE_ACTION_SERVER_HPP_ + +#include <atomic> +#include <memory> +#include <mutex> +#include <string> + +#include "rclcpp/rclcpp.hpp" +#include "rclcpp_action/rclcpp_action.hpp" + +namespace nav2_util +{ + +template<typename ActionT> +class SimpleActionServer +{ +public: + typedef std::function< + void (const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>>)> ExecuteCallback; + + explicit SimpleActionServer( + rclcpp::Node::SharedPtr node, + const std::string & action_name, + ExecuteCallback execute_callback) + : node_(node), execute_callback_(execute_callback) + { + auto handle_goal = + [](const rclcpp_action::GoalUUID &, std::shared_ptr<const typename ActionT::Goal>) + { + return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; + }; + + auto handle_cancel = [](std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>>) + { + return rclcpp_action::CancelResponse::ACCEPT; + }; + + auto handle_accepted = [this](std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> handle) + { + std::lock_guard<std::mutex> lock(update_mutex_); + + // If we're currently working on a task, set a flag so that the + // action server can grab the pre-empting request in its loop + if (received_handle_ != nullptr && received_handle_->is_active()) { + preempt_requested_ = true; + received_handle_ = handle; + } else { + // Otherwise, safe to start a new task + received_handle_ = handle; + std::thread{execute_callback_, handle}.detach(); + } + }; + + action_server_ = rclcpp_action::create_server<ActionT>( + node_, + action_name, + handle_goal, + handle_cancel, + handle_accepted); + } + + bool preempt_requested() + { + std::lock_guard<std::mutex> lock(update_mutex_); + return preempt_requested_; + } + + const std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> + get_updated_goal_handle() + { + std::lock_guard<std::mutex> lock(update_mutex_); + preempt_requested_ = false; + return received_handle_; + } + +protected: + // The SimpleActionServer isn't itself a node, so needs to know which one to use + rclcpp::Node::SharedPtr node_; + + ExecuteCallback execute_callback_; + + std::mutex update_mutex_; + bool preempt_requested_{false}; + std::shared_ptr<rclcpp_action::ServerGoalHandle<ActionT>> received_handle_; + + typename rclcpp_action::Server<ActionT>::SharedPtr action_server_; +}; + +} // namespace nav2_util + +#endif // NAV2_UTIL__SIMPLE_ACTION_SERVER_HPP_ diff --git a/nav2_util/package.xml b/nav2_util/package.xml index 53873d6560bb2ee7baff6ea932eab68a5b5771e0..efa4fdb9c87b41e33262b1f3a93c4f946020d9de 100644 --- a/nav2_util/package.xml +++ b/nav2_util/package.xml @@ -20,6 +20,8 @@ <build_depend>sdl</build_depend> <build_depend>sdl-image</build_depend> <build_depend>lifecycle_msgs</build_depend> + <build_depend>rclcpp_action</build_depend> + <build_depend>test_msgs</build_depend> <exec_depend>geometry_msgs</exec_depend> <exec_depend>rclcpp</exec_depend> @@ -30,6 +32,7 @@ <exec_depend>sdl</exec_depend> <exec_depend>sdl-image</exec_depend> <exec_depend>lifecycle_msgs</exec_depend> + <build_depend>rclcpp_action</build_depend> <test_depend>ament_lint_common</test_depend> <test_depend>ament_lint_auto</test_depend> @@ -39,6 +42,8 @@ <test_depend>launch_testing</test_depend> <test_depend>std_srvs</test_depend> <test_depend>rclcpp_lifecycle</test_depend> + <test_depend>rclcpp_action</test_depend> + <test_depend>test_msgs</test_depend> <export> <build_type>ament_cmake</build_type> diff --git a/nav2_util/test/CMakeLists.txt b/nav2_util/test/CMakeLists.txt index d786e05b52e4d75602877fa94cdc23eec27b0c0d..c608ad3c71fd84d4f9d5a230ea376be7b546954f 100644 --- a/nav2_util/test/CMakeLists.txt +++ b/nav2_util/test/CMakeLists.txt @@ -16,3 +16,7 @@ find_package(rclcpp_lifecycle REQUIRED) ament_add_gtest(test_lifecycle_utils test_lifecycle_utils.cpp) ament_target_dependencies(test_lifecycle_utils rclcpp_lifecycle) target_link_libraries(test_lifecycle_utils ${library_name}) + +ament_add_gtest(test_actions test_actions.cpp) +ament_target_dependencies(test_actions rclcpp_action) +target_link_libraries(test_actions ${library_name}) diff --git a/nav2_util/test/test_actions.cpp b/nav2_util/test/test_actions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f50ba1570f1af6b1793454e8dbaacbb3b0cb323a --- /dev/null +++ b/nav2_util/test/test_actions.cpp @@ -0,0 +1,247 @@ +// 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. + +#include <chrono> +#include <memory> +#include <thread> + +#include "gtest/gtest.h" +#include "nav2_util/node_utils.hpp" +#include "nav2_util/simple_action_server.hpp" +#include "test_msgs/action/fibonacci.hpp" + +using Fibonacci = test_msgs::action::Fibonacci; +using GoalHandle = rclcpp_action::ServerGoalHandle<Fibonacci>; + +using std::placeholders::_1; +using namespace std::chrono_literals; + +class FibonacciServerNode : public rclcpp::Node +{ +public: + FibonacciServerNode() + : rclcpp::Node("fibonacci_server_node") + { + } + + ~FibonacciServerNode() + { + } + + void on_init() + { + action_server_ = std::make_shared<nav2_util::SimpleActionServer<Fibonacci>>( + shared_from_this(), + "fibonacci", + std::bind(&FibonacciServerNode::execute, this, std::placeholders::_1)); + } + + void on_term() + { + action_server_.reset(); + } + + void execute(const std::shared_ptr<GoalHandle> goal_handle) + { + // The goal may be pre-empted, so keep a pointer to the current goal + std::shared_ptr<GoalHandle> current_goal_handle = goal_handle; + + rclcpp::Rate loop_rate(10); + +preempted: + // Initialize the goal, feedback, and result + auto goal = current_goal_handle->get_goal(); + auto feedback = std::make_shared<Fibonacci::Feedback>(); + auto result = std::make_shared<Fibonacci::Result>(); + + // Fibonacci-specific initialization + auto & sequence = feedback->sequence; + sequence.push_back(0); + sequence.push_back(1); + + for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) { + // Check if this action has been canceled + if (current_goal_handle->is_canceling()) { + result->sequence = sequence; + current_goal_handle->canceled(result); + return; + } + + // Check if we've gotten an new goal, pre-empting the current one + if (action_server_->preempt_requested()) { + current_goal_handle = action_server_->get_updated_goal_handle(); + goto preempted; + } + + // Update the sequence + sequence.push_back(sequence[i] + sequence[i - 1]); + + // Publish feedback + current_goal_handle->publish_feedback(feedback); + loop_rate.sleep(); + } + + // Check if goal is done + if (rclcpp::ok()) { + result->sequence = sequence; + current_goal_handle->succeed(result); + } + } + +private: + std::shared_ptr<nav2_util::SimpleActionServer<Fibonacci>> action_server_; +}; + +class RclCppFixture +{ +public: + RclCppFixture() + { + rclcpp::init(0, nullptr); + server_thread_ = + std::make_unique<std::thread>(std::bind(&RclCppFixture::server_thread_func, this)); + } + + ~RclCppFixture() + { + rclcpp::shutdown(); + server_thread_->join(); + } + + void server_thread_func() + { + auto node = std::make_shared<FibonacciServerNode>(); + node->on_init(); + rclcpp::spin(node->get_node_base_interface()); + node->on_term(); + node.reset(); + } + + std::unique_ptr<std::thread> server_thread_; +}; + +RclCppFixture g_rclcppfixture; + +class ActionTestNode : public rclcpp::Node +{ +public: + ActionTestNode() + : rclcpp::Node(nav2_util::generate_internal_node_name("action_test_node")) + { + } + + void on_init() + { + action_client_ = rclcpp_action::create_client<Fibonacci>(shared_from_this(), "fibonacci"); + action_client_->wait_for_action_server(); + } + + void on_term() + { + action_client_.reset(); + } + + rclcpp_action::Client<Fibonacci>::SharedPtr action_client_; +}; + +class ActionTest : public ::testing::Test +{ +protected: + void SetUp() override + { + node_ = std::make_shared<ActionTestNode>(); + node_->on_init(); + } + + void TearDown() override + { + node_->on_term(); + node_.reset(); + } + + std::shared_ptr<ActionTestNode> node_; +}; + +TEST_F(ActionTest, test_simple_action) +{ + // The goal for this invocation + auto goal = Fibonacci::Goal(); + goal.order = 12; + + // Send the goal + auto future_goal_handle = node_->action_client_->async_send_goal(goal); + ASSERT_EQ(rclcpp::spin_until_future_complete(node_, + future_goal_handle), rclcpp::executor::FutureReturnCode::SUCCESS); + + auto goal_handle = future_goal_handle.get(); + + // Wait for the result + auto future_result = node_->action_client_->async_get_result(goal_handle); + ASSERT_EQ(rclcpp::spin_until_future_complete(node_, future_result), + rclcpp::executor::FutureReturnCode::SUCCESS); + + // The final result + rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult result = future_result.get(); + ASSERT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED); + + // Sum all of the values in the requested fibonacci series + int sum = 0; + for (auto number : result.result->sequence) { + sum += number; + } + + ASSERT_EQ(sum, 376); +} + +TEST_F(ActionTest, test_simple_action_with_feedback) +{ + int feedback_sum = 0; + + // A callback to accumulate the intermediate values + auto feedback_callback = [&feedback_sum]( + rclcpp_action::ClientGoalHandle<Fibonacci>::SharedPtr /*goal_handle*/, + const std::shared_ptr<const Fibonacci::Feedback> feedback) + { + feedback_sum += feedback->sequence.back(); + }; + + // The goal for this invocation + auto goal = Fibonacci::Goal(); + goal.order = 10; + + // Send the goal + auto future_goal_handle = node_->action_client_->async_send_goal(goal, feedback_callback); + ASSERT_EQ(rclcpp::spin_until_future_complete(node_, + future_goal_handle), rclcpp::executor::FutureReturnCode::SUCCESS); + + auto goal_handle = future_goal_handle.get(); + + // Wait for the result + auto future_result = node_->action_client_->async_get_result(goal_handle); + ASSERT_EQ(rclcpp::spin_until_future_complete(node_, + future_result), rclcpp::executor::FutureReturnCode::SUCCESS); + + // The final result + rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult result = future_result.get(); + ASSERT_EQ(result.code, rclcpp_action::ResultCode::SUCCEEDED); + + // Sum all of the values in the requested fibonacci series + int sum = 0; + for (auto number : result.result->sequence) { + sum += number; + } + + ASSERT_EQ(sum, 143); + ASSERT_GE(feedback_sum, 0); // We should have received *some* feedback +} diff --git a/nav2_util/test/test_execution_timer.cpp b/nav2_util/test/test_execution_timer.cpp index 95cec4c38ff2e3bffb9454e88f92777b65ad9d8b..07e4804457979d0fe5e395ab6b4cbb09264a5e8f 100644 --- a/nav2_util/test/test_execution_timer.cpp +++ b/nav2_util/test/test_execution_timer.cpp @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "nav2_util/execution_timer.hpp" #include <chrono> #include <thread> + +#include "nav2_util/execution_timer.hpp" #include "gtest/gtest.h" using nav2_util::ExecutionTimer; diff --git a/nav2_util/test/test_lifecycle_utils.cpp b/nav2_util/test/test_lifecycle_utils.cpp index 98b320a96b762764dc77d6bd15c0fc984ddd2c0a..f69466c163cf0a0a5223849eda61fffc05cf31a5 100644 --- a/nav2_util/test/test_lifecycle_utils.cpp +++ b/nav2_util/test/test_lifecycle_utils.cpp @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "nav2_util/lifecycle_utils.hpp" #include <thread> -#include "rclcpp/rclcpp.hpp" -#include "rclcpp_lifecycle/lifecycle_node.hpp" -#include "lifecycle_msgs/msg/state.hpp" +#include <vector> + #include "gtest/gtest.h" +#include "nav2_util/lifecycle_utils.hpp" +#include "rclcpp_lifecycle/lifecycle_node.hpp" +#include "rclcpp/rclcpp.hpp" using nav2_util::bringup_lifecycle_nodes;