您现在的位置是:首页 >学无止境 >【ROS】ROS2编程示例:服务和客户端-C++版网站首页学无止境

【ROS】ROS2编程示例:服务和客户端-C++版

郭老二 2024-07-23 06:01:02
简介【ROS】ROS2编程示例:服务和客户端-C++版

1、准备

1)安装ROS2
【ROS】Ubuntu22.04安装ROS2(Humble Hawksbill)

2)ROS2命令
【ROS】ROS2命令行工具详解

3)配置工作空间
【ROS】ROS2中的概念和名词解释中第一节:工作空间 workspace

4)ROS2源码及官方示例:

https://github.com/ros2
https://github.com/ros2/examples

2、编辑

2.1 创建功能包

1)ros2 pkg create

cd ~/ros/src
mkdir laoerExample
cd laoerExample
ros2 pkg create --build-type ament_cmake cSrvCli --dependencies rclcpp example_interfaces

注意:创建功能包时一定要先创建好目录,进入相应的目录后创建,不要使用下面的方法,否则会在编译时报错

cd ~/ros/src
ros2 pkg create --build-type ament_cmake laoerExample/cSrvCli

2)–dependencies
–dependencies将会自动在package.xml和CMakeLists.txt中添加必要的依赖项
例如:–dependencies rclcpp example_interfaces,将会在package.xml中自动添加:

<depend>rclcpp</depend>
<depend>example_interfaces</depend>

CMakeLists.txt中自动添加:

find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

3)example_interfaces
example_interfaces功能包是ROS2中自带的功能包,不需要自己创建
功能包目录:/opt/ros/humble/share/example_interfaces
这个博文的示例中将会用到其中的一个服务接口,两个数相加后,将结果返回,接口文件路径及内容如下:

/opt/ros/humble/share/example_interfaces/srv$ cat AddTwoInts.srv 
int64 a
int64 b
---
int64 sum

2.2 编辑源码

2.2.1 服务端

~/ros/src/laoerExample/cSrvCli$ vi srv.cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <memory>

/* 求和函数 */
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
          std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
  /* 从请求中获取两个整数,并将相加的结果赋值给响应 */
  response->sum = request->a + request->b;

  /* RCLCPP_INFO:ROS2自带的log输出,分等级,带颜色,输出格式与printf相同,需要标明数据的类型。*/
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request
a: %ld" " b: %ld", request->a, request->b);
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}

int main(int argc, char **argv)
{
  /* 初始化ROS2 */
  rclcpp::init(argc, argv);

  /* 定义服务端节点add_two_ints_server */
  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

  /* 创建服务名为add_two_ints,服务函数为add的service服务端 */
  rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
    node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);

  /* 通知准备就绪 */
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");

  /* 运行节点 */
  rclcpp::spin(node);

  /* 退出ROS2 */
  rclcpp::shutdown();
}

2.2.2 客户端

~/ros/src/laoerExample/cSrvCli$ vi cli.cpp
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

#include <chrono>
#include <cstdlib>
#include <memory>

using namespace std::chrono_literals;

int main(int argc, char **argv)
{
  /* 初始化ROS2 */
  rclcpp::init(argc, argv);

  /* 需要两个命令行参数:x和y */
  if (argc != 3) {
      RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
      return 1;
  }

  /* 定义客户端节点add_two_ints_client */
  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");

  /* 创建服务名为add_two_ints的client客户端 */
  rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
    node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");

  /* 创建请求request */
  auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
  request->a = atoll(argv[1]);
  request->b = atoll(argv[2]);

  /* 搜索服务节点,间隔1s */
  while (!client->wait_for_service(1s)) {
    if (!rclcpp::ok()) {
      RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
      return 0;
    }
    /* 如果找不到,将会继续等待 */
    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_two_ints");
  }

  /* 退出ROS2 */
  rclcpp::shutdown();
  return 0;
}

2.3 添加依赖配置 package.xml

1)自动添加依赖
在创建功能包时,已经通过参数–dependencies指定了依赖

ros2 pkg create --build-type ament_cmake cSrvCli --dependencies rclcpp example_interfaces

2)手动添加依赖
依赖已自动添加在package.xml中,如果没有则需要手动修改添加即可

<depend>rclcpp</depend>
<depend>example_interfaces</depend>

3)完整的依赖 package.xml 如下:

~/ros/src/laoerExample/cSrvCli$ cat 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>cSrvCli</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="lesen@todo.todo">lesen</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>example_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>

2.4 修改编译配置

1)依赖相关的编译配置
在创建功能包时,已经通过参数–dependencies指定了依赖,CMakeLists.txt中也会自动添加依赖相关的编译配置,如果没有则手动添加

find_package(rclcpp REQUIRED)
find_package(example_interfaces REQUIRED)

2)添加需要编译的源码文件

add_executable(server src/srv.cpp)
ament_target_dependencies(server rclcpp example_interfaces)

add_executable(client src/cli.cpp)
ament_target_dependencies(client rclcpp example_interfaces)

3)生成安装规则

install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME})

4)完整 CMakeLists.txt 内容如下:

~/ros/src/laoerExample/cSrvCli$ cat CMakeLists.txt 
cmake_minimum_required(VERSION 3.8)
project(cSrvCli)

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(rclcpp REQUIRED)
find_package(example_interfaces 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()

add_executable(server src/srv.cpp)
ament_target_dependencies(server rclcpp example_interfaces)

add_executable(client src/cli.cpp)
ament_target_dependencies(client rclcpp example_interfaces)

install(TARGETS
  server
  client
  DESTINATION lib/${PROJECT_NAME})
  
ament_package()

3、编译

在工作空间根目录中编译:

cd ~/ros
~/ros$ colcon build

4、运行

在终端1中运行服务端:

~/ros$ source install/setup.sh 
~/ros$ ros2 run  cSrvCli server 

在终端2中运行客户端:

~/ros$ source install/setup.sh 
~/ros$ ros2 run cSrvCli client 123 456
[INFO] [1685671664.664459306] [rclcpp]: Sum: 579

注意:运行客户端时,需要传输两个参数值,否则会报错

[INFO] [1685671646.305281649] [rclcpp]: usage: add_two_ints_client X Y
[ros2run]: Process exited with failure 1

终端2输出打印信息:

[INFO] [1685671664.664459306] [rclcpp]: Sum: 579

终端1输出打印信息:

[INFO] [1685671604.946255679] [rclcpp]: Ready to add two ints.
[INFO] [1685671664.664030229] [rclcpp]: Incoming request
a: 123 b: 456
[INFO] [1685671664.664096508] [rclcpp]: sending back response: [579]

5、测试

1)查看节点信息
在终端3中查看节点信息,运行命令:ros2 node list

~$ ros2 node list 
/add_two_ints_server

对应服务端源码中定义服务节点时:

std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");

2)查看服务信息
在终端3中查看话题信息,运行命令:ros2 service list

~$ ros2 service list
/add_two_ints
/add_two_ints_server/describe_parameters
……
/add_two_ints_server/set_parameters_atomically

对应接口定义:

/opt/ros/humble/share/example_interfaces/srv/AddTwoInts.srv

以及服务端和客户端中定义节点时,使用的接口:

node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。