# ROS 2 Intermediate ###### tags: `ROS2` `Intermediate` `tutorials` ## rosdep **rosdep** 是 ROS 用來管理 **dependencies** 的實用程序,可以與 ROS packages 和 external libraries 一起使用。 **rosdep** 是一個 command-line utility,用於識別和檢查 **dependencies** 以創建或安裝 package 。 在以下情況下可以或被調用: * **Building a workspace** 需要適當的 **dependencies** ,我們利用他來 build packages。 * **Install packages** (e.g. `sudo apt install ros-foxy-demo-nodes-cpp`)我們利用他來檢查執行所需的 **dependencies**。 它能夠處理單個 package 或包目錄(e.g. workspace)。 ### 使用rosdep 如果這是第一次使用,則必須通過以下方式對其進行初始化: ``` sudo rosdep init rosdep update rosdep install --from-paths src -y --ignore-src ``` ## Creating an action 創建一個工作區名為`action_tutorials_interfaces`: ``` mkdir -p action_ws/src cd action_ws/src ros2 pkg create action_tutorials_interfaces ``` ### Defining an action ``` cd action_tutorials_interfaces mkdir action ``` 然後到`action`目錄中,創建一個名為的文件`Fibonacci.action`,其內容如下: ```a int32 order --- int32[] sequence --- int32[] partial_sequence ``` **order** 是我們要計算的斐波那契數列 ,他的 result 會傳到 **sequence** 的 ,而他的 feedback 是 **partial_sequence** 。 ### Building an action 使用斐波那契 action 之前,我們必須先把定義傳遞給 rosidl code 。 在`action_tutorials_interfaces`資料夾中找到`CMakeLists.txt`,並在 `ament_package()` 前放入以下程式: ```c find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "action/Fibonacci.action" ) ``` 修改完成的` CMakeLists.txt` 應該長這樣: ```c cmake_minimum_required(VERSION 3.8) project(action_tutorials_interfaces) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() # find dependencies find_package(ament_cmake REQUIRED) # uncomment the following section in order to fill in # further dependencies manually. # find_package(<dependency> REQUIRED) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) # the following line skips the linter which checks for copyrights # comment the line when a copyright and license is added to all source files set(ament_cmake_copyright_FOUND TRUE) # the following line skips cpplint (only works in a git repo) # comment the line when this package is in a git repo and when # a copyright and license is added to all source files set(ament_cmake_cpplint_FOUND TRUE) ament_lint_auto_find_test_dependencies() endif() find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "action/Fibonacci.action" ) ament_package() ``` Add the required dependencies to our `package.xml`: ```xml <buildtool_depend>rosidl_default_generators</buildtool_depend> <depend>action_msgs</depend> <member_of_group>rosidl_interface_packages</member_of_group> ``` 整體像這樣 ```xml <?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>action_tutorials_interfaces</name> <version>0.0.0</version> <description>Examples of action using interfaces</description> <maintainer email="you@email.com">Enor</maintainer> <license>Apache License 2.0</license> <buildtool_depend>rosidl_default_generators</buildtool_depend> <depend>action_msgs</depend> <member_of_group>rosidl_interface_packages</member_of_group> <buildtool_depend>ament_cmake</buildtool_depend> <test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend> <export> <build_type>ament_cmake</build_type> </export> </package> ``` Build the package containing the **Fibonacci** **action** definition: ``` # Change to the root of the workspace cd ~/action_ws # Build colcon build ``` ![](https://i.imgur.com/i9cLTSu.png) ``` . install/setup.bash ros2 interface show action_tutorials_interfaces/action/Fibonacci ``` ![](https://i.imgur.com/L01RwF2.png) ## Writing an action server and client (C++) 建立一個新的 package: ``` cd ~/action_ws/src ros2 pkg create --dependencies action_tutorials_interfaces rclcpp rclcpp_action rclcpp_components -- action_tutorials_cpp ``` 開啟`action_tutorials_cpp/include/action_tutorials_cpp/visibility_control.h`並編輯: ```h #ifndef ACTION_TUTORIALS_CPP__VISIBILITY_CONTROL_H_ #define ACTION_TUTORIALS_CPP__VISIBILITY_CONTROL_H_ #ifdef __cplusplus extern "C" { #endif // This logic was borrowed (then namespaced) from the examples on the gcc wiki: // https://gcc.gnu.org/wiki/Visibility #if defined _WIN32 || defined __CYGWIN__ #ifdef __GNUC__ #define ACTION_TUTORIALS_CPP_EXPORT __attribute__ ((dllexport)) #define ACTION_TUTORIALS_CPP_IMPORT __attribute__ ((dllimport)) #else #define ACTION_TUTORIALS_CPP_EXPORT __declspec(dllexport) #define ACTION_TUTORIALS_CPP_IMPORT __declspec(dllimport) #endif #ifdef ACTION_TUTORIALS_CPP_BUILDING_DLL #define ACTION_TUTORIALS_CPP_PUBLIC ACTION_TUTORIALS_CPP_EXPORT #else #define ACTION_TUTORIALS_CPP_PUBLIC ACTION_TUTORIALS_CPP_IMPORT #endif #define ACTION_TUTORIALS_CPP_PUBLIC_TYPE ACTION_TUTORIALS_CPP_PUBLIC #define ACTION_TUTORIALS_CPP_LOCAL #else #define ACTION_TUTORIALS_CPP_EXPORT __attribute__ ((visibility("default"))) #define ACTION_TUTORIALS_CPP_IMPORT #if __GNUC__ >= 4 #define ACTION_TUTORIALS_CPP_PUBLIC __attribute__ ((visibility("default"))) #define ACTION_TUTORIALS_CPP_LOCAL __attribute__ ((visibility("hidden"))) #else #define ACTION_TUTORIALS_CPP_PUBLIC #define ACTION_TUTORIALS_CPP_LOCAL #endif #define ACTION_TUTORIALS_CPP_PUBLIC_TYPE #endif #ifdef __cplusplus } #endif #endif // ACTION_TUTORIALS_CPP__VISIBILITY_CONTROL_H_ ``` 開啟 `action_tutorials_cpp/src/fibonacci_action_server.cpp`並編輯: ```cpp #include <functional> #include <memory> #include <thread> #include "action_tutorials_interfaces/action/fibonacci.hpp" #include "rclcpp/rclcpp.hpp" #include "rclcpp_action/rclcpp_action.hpp" #include "rclcpp_components/register_node_macro.hpp" #include "action_tutorials_cpp/visibility_control.h" namespace action_tutorials_cpp { //創建一個rclcpp::Node class FibonacciActionServer : public rclcpp::Node { public: using Fibonacci = action_tutorials_interfaces::action::Fibonacci; using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>; ACTION_TUTORIALS_CPP_PUBLIC //初始化節點名稱 explicit FibonacciActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) : Node("fibonacci_action_server", options) { using namespace std::placeholders; //實體化 action server /* 一個 action server 需要 6 個東西: 操作類型名稱:Fibonacci。 將操作添加到的 ROS 2 節點:this. 動作名稱:'fibonacci'. 處理目標的回傳函數:handle_goal 處理取消的回傳函數:handle_cancel. 用於處理目標的回傳接受函數:handle_accept。 */ this->action_server_ = rclcpp_action::create_server<Fibonacci>( this, "fibonacci", std::bind(&FibonacciActionServer::handle_goal, this, _1, _2), std::bind(&FibonacciActionServer::handle_cancel, this, _1), std::bind(&FibonacciActionServer::handle_accepted, this, _1)); } private: rclcpp_action::Server<Fibonacci>::SharedPtr action_server_; //處理新目標的回傳(從這開始) rclcpp_action::GoalResponse handle_goal( const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const Fibonacci::Goal> goal) { RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order); (void)uuid; //他實現有ACCEPT_AND_EXECUTE的目標 return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; } //處理取消的回傳 rclcpp_action::CancelResponse handle_cancel( const std::shared_ptr<GoalHandleFibonacci> goal_handle) { //然後會告訴客戶端他接受取消 RCLCPP_INFO(this->get_logger(), "Received request to cancel goal"); (void)goal_handle; return rclcpp_action::CancelResponse::ACCEPT; } //最後接收到回傳,並接受一個新目標並回傳 //由於該執行是一段長時間運型的操作,因此獨立 handle_accepted 出來讓他能快速給予回饋 void handle_accepted(const std::shared_ptr<GoalHandleFibonacci> goal_handle) { using namespace std::placeholders; // this needs to return quickly to avoid blocking the executor, so spin up a new thread std::thread{std::bind(&FibonacciActionServer::execute, this, _1), goal_handle}.detach(); } //所有的處理與更新都在 execute 中完成 void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle) { RCLCPP_INFO(this->get_logger(), "Executing goal"); rclcpp::Rate loop_rate(1); const auto goal = goal_handle->get_goal(); auto feedback = std::make_shared<Fibonacci::Feedback>(); auto & sequence = feedback->partial_sequence; sequence.push_back(0); sequence.push_back(1); auto result = std::make_shared<Fibonacci::Result>(); for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) { // Check if there is a cancel request if (goal_handle->is_canceling()) { result->sequence = sequence; goal_handle->canceled(result); RCLCPP_INFO(this->get_logger(), "Goal canceled"); return; } // Update sequence sequence.push_back(sequence[i] + sequence[i - 1]); // Publish feedback goal_handle->publish_feedback(feedback); RCLCPP_INFO(this->get_logger(), "Publish feedback"); loop_rate.sleep(); } //這邊每秒會處理一個斐波那契數列的序號,為每一步發布一個feedback。當它完成處理時,它將標記goal_handle為succeed,然後退出。 // Check if goal is done if (rclcpp::ok()) { result->sequence = sequence; goal_handle->succeed(result); RCLCPP_INFO(this->get_logger(), "Goal succeeded"); } } }; // class FibonacciActionServer } // namespace action_tutorials_cpp RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::FibonacciActionServer) ``` ### compile the package: ``` cd ~/action_ws colcon build ``` ![](https://i.imgur.com/znuzcJ3.png) ### Writing the action client code 打開`action_tutorials_cpp/src/fibonacci_action_client.cpp`,將code放入: ```cpp #include <functional> #include <future> #include <memory> #include <string> #include <sstream> #include "action_tutorials_interfaces/action/fibonacci.hpp" #include "rclcpp/rclcpp.hpp" #include "rclcpp_action/rclcpp_action.hpp" #include "rclcpp_components/register_node_macro.hpp" namespace action_tutorials_cpp { class FibonacciActionClient : public rclcpp::Node { public: using Fibonacci = action_tutorials_interfaces::action::Fibonacci; using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle<Fibonacci>; explicit FibonacciActionClient(const rclcpp::NodeOptions & options) : Node("fibonacci_action_client", options) { this->client_ptr_ = rclcpp_action::create_client<Fibonacci>( this, "fibonacci"); this->timer_ = this->create_wall_timer( std::chrono::milliseconds(500), std::bind(&FibonacciActionClient::send_goal, this)); } void send_goal() { using namespace std::placeholders; this->timer_->cancel(); if (!this->client_ptr_->wait_for_action_server()) { RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting"); rclcpp::shutdown(); } auto goal_msg = Fibonacci::Goal(); goal_msg.order = 10; RCLCPP_INFO(this->get_logger(), "Sending goal"); auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions(); send_goal_options.goal_response_callback = std::bind(&FibonacciActionClient::goal_response_callback, this, _1); send_goal_options.feedback_callback = std::bind(&FibonacciActionClient::feedback_callback, this, _1, _2); send_goal_options.result_callback = std::bind(&FibonacciActionClient::result_callback, this, _1); this->client_ptr_->async_send_goal(goal_msg, send_goal_options); } private: rclcpp_action::Client<Fibonacci>::SharedPtr client_ptr_; rclcpp::TimerBase::SharedPtr timer_; void goal_response_callback(std::shared_future<GoalHandleFibonacci::SharedPtr> future) { auto goal_handle = future.get(); if (!goal_handle) { RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server"); } else { RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result"); } } void feedback_callback( GoalHandleFibonacci::SharedPtr, const std::shared_ptr<const Fibonacci::Feedback> feedback) { std::stringstream ss; ss << "Next number in sequence received: "; for (auto number : feedback->partial_sequence) { ss << number << " "; } RCLCPP_INFO(this->get_logger(), ss.str().c_str()); } void result_callback(const GoalHandleFibonacci::WrappedResult & result) { switch (result.code) { case rclcpp_action::ResultCode::SUCCEEDED: break; case rclcpp_action::ResultCode::ABORTED: RCLCPP_ERROR(this->get_logger(), "Goal was aborted"); return; case rclcpp_action::ResultCode::CANCELED: RCLCPP_ERROR(this->get_logger(), "Goal was canceled"); return; default: RCLCPP_ERROR(this->get_logger(), "Unknown result code"); return; } std::stringstream ss; ss << "Result received: "; for (auto number : result.result->sequence) { ss << number << " "; } RCLCPP_INFO(this->get_logger(), ss.str().c_str()); rclcpp::shutdown(); } }; // class FibonacciActionClient } // namespace action_tutorials_cpp RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::FibonacciActionClient) ``` 打開action_tutorials_cpp/CMakeLists.txt 添加以下內容在 find_package 後: ```c add_library(action_client SHARED src/fibonacci_action_client.cpp) target_include_directories(action_client PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>) target_compile_definitions(action_client PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL") ament_target_dependencies(action_client "action_tutorials_interfaces" "rclcpp" "rclcpp_action" "rclcpp_components") rclcpp_components_register_node(action_client PLUGIN "action_tutorials_cpp::FibonacciActionClient" EXECUTABLE fibonacci_action_client) install(TARGETS action_client ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) ``` 完成後的 CMakeLists.txt 應該長這樣: ```c cmake_minimum_required(VERSION 3.8) project(action_tutorials_cpp) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() # find dependencies find_package(ament_cmake REQUIRED) find_package(action_tutorials_interfaces REQUIRED) find_package(rclcpp REQUIRED) find_package(rclcpp_action REQUIRED) find_package(rclcpp_components REQUIRED) if(BUILD_TESTING) add_library(action_server SHARED src/fibonacci_action_server.cpp) target_include_directories(action_server PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>) target_compile_definitions(action_server PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL") ament_target_dependencies(action_server "action_tutorials_interfaces" "rclcpp" "rclcpp_action" "rclcpp_components") rclcpp_components_register_node(action_server PLUGIN "action_tutorials_cpp::FibonacciActionServer" EXECUTABLE fibonacci_action_server) install(TARGETS action_server ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) find_package(ament_lint_auto REQUIRED) add_library(action_client SHARED src/fibonacci_action_client.cpp) target_include_directories(action_client PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>) target_compile_definitions(action_client PRIVATE "ACTION_TUTORIALS_CPP_BUILDING_DLL") ament_target_dependencies(action_client "action_tutorials_interfaces" "rclcpp" "rclcpp_action" "rclcpp_components") rclcpp_components_register_node(action_client PLUGIN "action_tutorials_cpp::FibonacciActionClient" EXECUTABLE fibonacci_action_client) install(TARGETS action_client ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) # the following line skips the linter which checks for copyrights # comment the line when a copyright and license is added to all source files set(ament_cmake_copyright_FOUND TRUE) # the following line skips cpplint (only works in a git repo) # comment the line when this package is in a git repo and when # a copyright and license is added to all source files set(ament_cmake_cpplint_FOUND TRUE) ament_lint_auto_find_test_dependencies() endif() ament_package() ``` ``` colcon build ``` ![](https://i.imgur.com/WTRIqE5.png) ### Running the action server ``` ros2 run action_tutorials_cpp fibonacci_action_server ``` ![](https://i.imgur.com/EElfkSY.png) ### Running the action client ``` ros2 run action_tutorials_cpp fibonacci_action_client ``` ![](https://i.imgur.com/RFVJqgZ.png)