Robot Operating System——自定义Service/Client通信消息结构

《Robot Operating System——自定义订阅/发布的消息结构》一文中,我们讲解了如何自定义消息结构。这个消息是发布者向订阅者发送的消息,具有单向性。但是Service/Client类型的消息是具有双向性的。Client向Service发送的是Request消息,Service向Client发送的是Response消息。

初始化环境

《Robot Operating System——深度解析自动隐式加载动态库的运行模式》一文中,我们展现了ROS2可执行文件的链接指令。可以看到它依赖了很多ROS2环境相关的动态库,所以我们在创建工程之前也要初始化环境。

source /opt/ros/jazzy/setup.bash

关于环境的安装可以参见《Robot Operating System——Ubuntu上以二进制形式安装环境》

生成自定义服务的工程

创建包

ros2 pkg create --build-type ament_cmake --license Apache-2.0 custom_service

在这里插入图片描述
其目录结构如下
在这里插入图片描述

自定义消息

我们创建一个目录srv,然后在其下新建一个文件NavSatPose.srv,填入下面的内容

sensor_msgs/NavSatStatus nav
---
geometry_msgs/PoseWithCovariance pose
std_msgs/Bool boolvalue

nav是Request消息结构体中的内容;pose和boolvalue是Response消息结构体中的内容。
在这里插入图片描述

package.xml

在该文件中新增如下内容

  <depend>sensor_msgs</depend>
  <depend>geometry_msgs</depend>
  <depend>std_msgs</depend>
  <buildtool_depend>rosidl_default_generators</buildtool_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

sensor_msgs、geometry_msgs和std_msgs是消息体中用的三种ROS2提供的基础消息类型;
rosidl_default_generators用于将上述msg文件生成代码;
rosidl_default_runtime是运行时依赖;
后三者是一定要加的,前面的三个根据自定义消息的类型酌情添加。
此时的目录结构如下

完整文件

<?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>custom_service</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="f304646673@gmail.com">fangliang</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>
  
  <depend>sensor_msgs</depend>
  <depend>geometry_msgs</depend>
  <depend>std_msgs</depend>
  <buildtool_depend>rosidl_default_generators</buildtool_depend>
  <exec_depend>rosidl_default_runtime</exec_depend>
  <member_of_group>rosidl_interface_packages</member_of_group>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

CMakeLists.txt

新增如下内容

find_package(sensor_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/NavSatPoseBool.srv"
  DEPENDENCIES sensor_msgs geometry_msgs std_msgs 
)

DEPENDENCIES 中添加的是我们自定义消息的基础类型。

完整文件

cmake_minimum_required(VERSION 3.8)
project(custom_service)

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)

find_package(sensor_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/NavSatPoseBool.srv"
  DEPENDENCIES sensor_msgs geometry_msgs std_msgs 
)

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()

ament_package()

编译

colcon build --packages-select custom_service

在这里插入图片描述

注册

source install/setup.sh 

经过注册后,我们可以在ROS2中查看这个自定义服务

ros2 interface show custom_service/srv/NavSatPoseBool

在这里插入图片描述

使用自定义服务的工程

创建包

ros2 pkg create --build-type ament_cmake --license Apache-2.0 custom_service_nodes --dependencies rclcpp custom_service

在这里插入图片描述
–dependencies参数可以帮我们在package.xml和CMakeLists.txt中自动添加相应依赖。此时我们添加上一步创建的custom_service。
此时目录结构如下
在这里插入图片描述

代码

在src目录下新建server.cpp和client.cpp,分别填入以下内容

#include "rclcpp/rclcpp.hpp"
#include "custom_service/srv/nav_sat_pose_bool.hpp"

#include <memory>
#include <string>
#include <sstream>


std::string serializeRequest(const custom_service::srv::NavSatPoseBool::Request &request) {
    std::ostringstream oss;
    oss << request.nav.service << " " 
        << request.nav.status << " ";

    return oss.str();
}

std::string serializeResponse(const custom_service::srv::NavSatPoseBool::Response &response) {
    std::ostringstream oss;
    oss << response.pose.pose.position.x << " " 
        << response.pose.pose.position.y << " " 
        << response.pose.pose.position.z << " " 
        << response.pose.pose.orientation.x << " " 
        << response.pose.pose.orientation.y << " " 
        << response.pose.pose.orientation.z << " " 
        << response.pose.pose.orientation.w << " " 
        << response.boolvalue.data;

    return oss.str();
}

void handle(const std::shared_ptr<custom_service::srv::NavSatPoseBool::Request> request,     
          std::shared_ptr<custom_service::srv::NavSatPoseBool::Response> response)  
{
  response->pose.pose.position.x = 1.0;
  response->pose.pose.position.y = 2.0;
  response->pose.pose.position.z = 3.0;
  response->pose.pose.orientation.x = 4.0;
  response->pose.pose.orientation.y = 5.0;
  response->pose.pose.orientation.z = 6.0;
  response->pose.pose.orientation.w = 7.0;
  response->boolvalue.data = true;

  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %s", serializeRequest(*request).c_str()); 
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%s]", serializeResponse(*response).c_str());
}

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);

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

  rclcpp::Service<custom_service::srv::NavSatPoseBool>::SharedPtr service =               
    node->create_service<custom_service::srv::NavSatPoseBool>("handle",  &handle);   

  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to start service.");                     

  rclcpp::spin(node);
  rclcpp::shutdown();
}
#include "rclcpp/rclcpp.hpp"
#include "custom_service/srv/nav_sat_pose_bool.hpp"

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

using namespace std::chrono_literals;

std::string serializeRequest(const custom_service::srv::NavSatPoseBool::Request &request) {
    std::ostringstream oss;
    oss << request.nav.service << " " 
        << request.nav.status << " ";

    return oss.str();
}

std::string serializeResponse(const custom_service::srv::NavSatPoseBool::Response &response) {
    std::ostringstream oss;
    oss << response.pose.pose.position.x << " " 
        << response.pose.pose.position.y << " " 
        << response.pose.pose.position.z << " " 
        << response.pose.pose.orientation.x << " " 
        << response.pose.pose.orientation.y << " " 
        << response.pose.pose.orientation.z << " " 
        << response.pose.pose.orientation.w << " " 
        << response.boolvalue.data;

    return oss.str();
}

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);

  std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("custom_client");  
  rclcpp::Client<custom_service::srv::NavSatPoseBool>::SharedPtr client =                
    node->create_client<custom_service::srv::NavSatPoseBool>("handle");          

  auto request = std::make_shared<custom_service::srv::NavSatPoseBool::Request>();
  request->nav.service = 1;
  request->nav.status = 2;                                                         

  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);
  RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending request: [%s]", serializeRequest(*request).c_str());
  // Wait for the result.
  if (rclcpp::spin_until_future_complete(node, result) ==
    rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "receiving back response: [%s]", serializeResponse(*result.get()).c_str());
  } else {
    RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service custom_server");    
  }

  rclcpp::shutdown();
  return 0;
}

CMakeLists.txt

然后分别编译这两个文件成为可自行文件。

add_executable(server src/server.cpp)
ament_target_dependencies(server rclcpp custom_service)

add_executable(client src/client.cpp)
ament_target_dependencies(client rclcpp custom_service)

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

编译

 colcon build --packages-select custom_service_nodes

在这里插入图片描述

运行

在新的终端中,需要先初始化环境

source custom_service_nodes/install/setup.sh

然后执行server和client
在这里插入图片描述
在这里插入图片描述

工程地址

参考资料

在ROS(Robot Operating System)中创建和使用自定义消息和服务主要包括以下几个步骤: 1. **创建自定义消息**: - 使用`rosmsg`工具:首先,在终端中导航到你的工作空间的`src`目录下,然后输入`rosmsg gen msg [MSG_NAME] MESSAGE_DEFINITION`,其中`MSG_NAME`是你新消息的名称,`MESSAGE_DEFINITION`是一段XML格式的描述,定义了消息的数据结构。例如,如果你想定义一个表示点云的消息,可能会像这样: ```sh rosmsg gen msg PointCloud2 <your_message_definition_file>.xml ``` - 编辑消息定义文件:完成后,编辑生成的`.msg`文件,添加你需要的数据字段。 2. **编译自定义消息**: - 修改`package.xml`文件,将新的消息文件加入`<messages>`标签下。 - 更新`CMakeLists.txt`,添加对新消息的编译指令,如`find_package( rospy REQUIRED )` 和 `add_message_files( ... )`. - 执行`catkin_make`编译自定义消息。 3. **使用自定义消息**: - 在代码中,你可以通过`std_msgs/Header`, `sensor_msgs/Image`等标准ROS消息类型引入你的自定义消息类型。 - 发布者使用`Publisher`发布消息,订阅者使用`Subscriber`接收消息。 4. **创建自定义服务**: - 使用`rosservice`工具生成服务模板:`rosservice gen srv [SERVICE_NAME] SERVICE_DEFINITION`。 - 编辑服务定义文件,定义请求和响应的消息类型,以及操作函数的行为。 5. **编译和使用自定义服务**: - 同样在`package.xml`和`CMakeLists.txt`中添加新服务,编译后就可以通过`rosservice call`调用服务,或者在客户端代码中使用`ServiceClient`。 6. **发布和消费服务**: - 创建服务发布器(Server):在服务提供者的代码中,使用`service_server`函数开始监听。 - 调用服务:在服务消费者(Client)的代码中,使用`service_client`函数发送请求并获取响应。 记得每次修改完源代码后都要重新编译,以便更新生成的库和头文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值