阿里云官方镜像站:ROS2源
https://developer.aliyun.com/mirror/?utm_content=g_1000303593
创建工作空间,即编写代码的位置mkdir -p dev_ws/src
进入 dev_ws/src 路径下:
先创建依赖包 tutorial_interfaces:
ros2 pkg create --build-type ament_cmake tutorial_interfaces
进入 dev_ws/src/tutorial_interfaces ,然后创建msg和srv目录存放.msg文件和.srv文件:
mkdir msg mkdir srv
进入dev_ws/src/tutorial_interface/msg目录,新建 Num.msg文件:
int64 num
进入 dev_ws/src/tutorial_interface/srv目录,新建 AddThreeInts.srv文件:
int64 a int64 b int64 c---int64 sum
然后编辑CMakeLists.txt文件:
find_package(rosidl_default_generators REQUIRED)rosidl_generate_interfaces(${PROJECT_NAME} "msg/Num.msg" "srv/AddThreeInts.srv" )
继续编辑package.xml:
<build_depend>rosidl_default_generators</build_depend><exec_depend>rosidl_default_runtime</exec_depend><member_of_group>rosidl_interface_packages</member_of_group>
最后回到工作空间路径下编译构建 tutorial_interfaces 包:
colcon build --packages-select tutorial_interfaces
以上操作步骤详细链接:
Creating custom ROS 2 msg and srv files — ROS 2 Documentation: Galactic documentation
然后在工作空间的src路径下创建自己的包 service,并指定依赖的包:
ros2 pkg create --build-type ament_cmake service --dependencies rclcpp tutorial_interfaces
在 dev_ws/src/service/src目录下新建service.cpp
#include "rclcpp/rclcpp.hpp"#include "tutorial_interfaces/srv/add_three_ints.hpp" // CHANGE#include <memory>void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request, // CHANGE std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response> response) // CHANGE{ response->sum = request->a + request->b + request->c; // CHANGE RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld" " c: %ld", // CHANGE request->a, request->b, request->c); // CHANGE RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);}int main(int argc, char **argv){ rclcpp::init(argc, argv); std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_server"); // CHANGE rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service = // CHANGE node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints", &add); // CHANGE RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints."); // CHANGE rclcpp::spin(node); rclcpp::shutdown();}
进入工作空间的src路径下,执行下面命令生成待测模块client:
ros2 pkg create --build-type ament_cmake client --dependencies rclcpp tutorial_interfaces
进入client路径下,分别新建src,test目录,include目录默认已经存在。
include目录下文件:client.h和params.h
// client.h#ifndef CLIENT_H#define CLIENT_Hclass ClientHandler{ public: ClientHandler(); ~ClientHandler(); bool sendParams(int argc, char **argv);};#endif
// params.h#ifndef PARAMS_H#define PARAMS_Hextern int my_argc;extern char** my_argv;#endif
src目录下文件:client.cpp和main.cpp
// client.cpp#include "rclcpp/rclcpp.hpp"#include "tutorial_interfaces/srv/add_three_ints.hpp" #include "../include/client.h"#include <chrono>#include <cstdlib>#include <memory>#include<vector>using namespace std;using namespace std::chrono_literals;// 构造函数ClientHandler::ClientHandler(){}// 析构函数ClientHandler::~ClientHandler(){}// 普通函数——发送参数bool ClientHandler::sendParams(int argc, char **argv){ rclcpp::init(argc, argv); if (argc != 4) { RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z"); return false; } std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client"); rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client = node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints"); auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>(); request->a = atoll(argv[1]); request->b = atoll(argv[2]); request->c = atoll(argv[3]); while (!client->wait_for_service(1s)) { if (!rclcpp::ok()) { RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting."); return false; } RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again..."); } auto result = client->async_send_request(request); // Wait for the result. if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) { RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum); } else { RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_ints"); } rclcpp::shutdown(); return true;}
// main.cpp#include "../include/client.h"int main(int argc, char **argv){ // 注意这里: C++ 编译器把不带参数的构造函数优先认为是一个函数声明 ClientHandler client{}; client.sendParams(argc, argv);}
test目录下文件:clientTest.cpp和main.cpp
// clientTest.cpp#include "gtest/gtest.h"#include "../include/client.h"#include "../include/params.h"TEST(ClientHandler, sendParams){ // 测试的时候的交互方式也不能改变,既然client实际的效果是在命令行输入参数, // 那这里也是这样的效果 ClientHandler client{}; EXPECT_EQ(true, client.sendParams(my_argc, my_argv));}
// main.cpp#include <gtest/gtest.h> // #include <gmock/gmock.h>int my_argc;char** my_argv;int main(int argc, char** argv) { // ::testing::InitGoogleMock(&argc, argv); // 注意这里使用的是Gtest,不是Gmock ::testing::InitGoogleTest(&argc, argv); // Runs all tests using Google Test. my_argc = argc; my_argv = argv; return RUN_ALL_TESTS();}
CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.8)project(client)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic)endif()# find dependenciesfind_package(ament_cmake REQUIRED)find_package(rclcpp REQUIRED)find_package(tutorial_interfaces REQUIRED)set(SRC src/client.cpp src/main.cpp)add_executable(client ${SRC} )ament_target_dependencies(client rclcpp tutorial_interfaces)# 5. 添加当前项目中的头文件 注意有顺序的要求,不能乱target_include_directories(client PRIVATE ${PROJECT_SOURCE_DIR}/include)# 如果是测试代码if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) # 加入gtest包 find_package(ament_cmake_gtest REQUIRED) # the following line skips the linter which checks for copyrights # uncomment the line when a copyright and license is not present in all source files # set(ament_cmake_copyright_FOUND TRUE) # the following line skips cpplint (only works in a git repo) # uncomment the line when this package is not in a git repo # set(ament_cmake_cpplint_FOUND TRUE) set(TEST test/main.cpp test/clientTest.cpp ) # 生成加入gtest的test执行文件。${PROJECT_NAME}_test为自定义的test执行文件名称;test/demo_test.cpp为test源码路径 # 注意这里导包的时候,不再需要将 .h 文件导入进来,因为在 client.cpp中已经导入了我们需要使用到的.h文件 # 另外,注意这里不能导入开发代码中的 main.cpp,因为已经有了一个测试的main.cpp ament_add_gtest(${PROJECT_NAME}_test ${TEST} src/client.cpp) # 务必注意这里需要添加的依赖包 ament_target_dependencies(${PROJECT_NAME}_test rclcpp tutorial_interfaces) install(TARGETS ${PROJECT_NAME}_test # 将生成的test执行文件安装到DESTINATION后的路径下 DESTINATION lib/${PROJECT_NAME}) ament_lint_auto_find_test_dependencies()endif()install(TARGETS client DESTINATION lib/${PROJECT_NAME})# 设置编译构建类型为 调试 模式set(CMAKE_BUILD_TYPE Debug) # 生成覆盖率文件set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")ament_package()
package.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>client</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="zhi@todo.todo">zhi</maintainer> <license>TODO: License declaration</license> <buildtool_depend>ament_cmake</buildtool_depend> <depend>rclcpp</depend> <depend>tutorial_interfaces</depend> <test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend> <export> <build_type>ament_cmake</build_type> </export></package>
然后回到工作空间执行编译构建命令:
colcon build --packages-select client
进入到 dev_ws/build/client路径下,找到client_test可执行文件即为生成的测试文件
在另外一个终端启动service包:
ros2 run cpp_srvcli server
执行
./client_test 23 3 4
运行测试文件即可。
生成覆盖率的脚本:
#!/usr/bin/bash echo "begin gen coverage file ..."lcov --no-external --capture --initial --directory . --output-file /home/zhi/ros2-gtest-gmock/info/ros2_base.info cd /home/zhi/ros2-gtest-gmock/build/client;./client_test 45 56 56;cd /home/zhi/ros2-gtest-gmock # 注意下面的=号两侧是不可以有空格的,这是个大坑 current_path=$(pwd)echo "当前目录是:" $current_path lcov --no-external --capture --directory . --output-file /home/zhi/ros2-gtest-gmock/info/ros2.info lcov --add-tracefile /home/zhi/ros2-gtest-gmock/info/ros2_base.info --add-tracefile /home/zhi/ros2-gtest-gmock/info/ros2.info --output-file /home/zhi/ros2-gtest-gmock/info/ros2_coverage.info mkdir -p coverage && genhtml /home/zhi/ros2-gtest-gmock/info/ros2_coverage.info --output-directory coverage
程序封装改写:
如果不知道返回类型,直接断点下去看下类型:
联系客服