【ROS2实操二】服务通信

news2024/12/29 8:58:27

简介

        服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即:一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景:机器人巡逻过程中,控制系统分析传感器数据发现可疑物体或人,此时需要拍摄照片并留存。

在上述场景中,就使用到了服务通信。数据分析节点A需要向相机相关节点B发送图片存储请求,节点B处理请求,并返回处理结果。与上述应用类似的,服务通信更适用于对实时性有要求、具有一定逻辑处理的应用场景。

       

        概念:服务通信是以请求响应的方式实现不同节点之间数据传输的通信模式。发送请求数据的对象称为客户端,接收请求并发送响应的对象称之为服务端,同话题通信一样,客户端和服务端也通过话题相关联,不同的是服务通信的数据传输是双向交互式的。        

        服务通信中,服务端与客户端是一对多的关系,也即,同一服务话题下,存在多个客户端,每个客户端都可以向服务端发送请求。

        作用:用于偶然的、对实时性有要求、有一定逻辑处理需求的数据传输场景。

一、案例以及案例分析

1.案例需求

        需求:编写服务通信,客户端可以提交两个整数到服务端,服务端接收请求并解析两个整数求和,然后将结果响应回客户端。

2.案例分析

        在上述案例中,需要关注的要素有三个:客户端;服务端;消息载体。

3.流程简介

        案例实现前需要先自定义服务接口,接口准备完毕后,服务实现主要步骤如下:编写服务端;编写客户端;编辑配置文件;编译;执行。

4.准备工作

终端下进入工作空间的src目录,调用如下两条命令分别创建C++功能包和Python功能包。

ros2 pkg create cpp02_service --build-type ament_cmake --dependencies rclcpp base_interfaces_demo
ros2 pkg create py02_service --build-type ament_python --dependencies rclpy base_interfaces_demo

二、服务通信接口消息

        定义服务接口消息与定义话题接口消息流程类似,主要步骤如下:

        1.创建并编辑 .srv 文件

        功能包base_interfaces_demo下新建srv文件夹,srv文件夹下新建AddInts.srv文件。

int32 num1
int32 num2
---
int32 sum

        2.编辑配置文件

         (1)package.xml 文件

        srv文件与msg文件的包依赖一致,如果你是新建的功能包添加srv文件,那么直接参考定义msg文件时package.xml 配置即可。由于我们使用的是base_interfaces_demo该包已经为msg文件配置过了依赖包,所以package.xml不需要做修改。

        (2)CMakeLists.txt 文件

        如果是新建的功能包,与之前定义msg文件同理,为了将.srv文件转换成对应的C++和Python代码,还需要在CMakeLists.txt中添加如下配置:

find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/AddInts.srv"
)

        不过,我们当前使用的base_interfaces_demo包,那么你只需要修改rosidl_generate_interfaces函数即可,修改后的内容如下:

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/Student.msg"
  "srv/AddInts.srv"
)

3.编译

        终端中进入当前工作空间,编译功能包:

colcon build --packages-select base_interfaces_demo

4.测试

        编译完成之后,在工作空间下的 install 目录下将生成AddInts.srv文件对应的C++和Python文件,我们也可以在终端下进入工作空间,通过如下命令查看文件定义以及编译是否正常:

. install/setup.bash
ros2 interface show base_interfaces_demo/srv/AddInts

        正常情况下,终端将会输出与AddInts.srv文件一致的内容。

三、服务通信(C++)

1.服务端实现

        功能包cpp02_service的src目录下,新建C++文件demo01_server.cpp。

/*  
  需求:编写服务端,接收客户端发送请求,提取其中两个整型数据,相加后将结果响应回客户端。
  步骤:
    1.包含头文件;
    2.初始化 ROS2 客户端;
    3.定义节点类;
      3-1.创建服务端;
      3-2.处理请求数据并响应结果。
    4.调用spin函数,并传入节点对象指针;
    5.释放资源。
*/

// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"

using base_interfaces_demo::srv::AddInts;

using std::placeholders::_1;
using std::placeholders::_2;

// 3.定义节点类;
class MinimalService: public rclcpp::Node{
  public:
    MinimalService():Node("minimal_service"){
      // 3-1.创建服务端;
      server = this->create_service<AddInts>("add_ints",std::bind(&MinimalService::add, this, _1, _2));
      RCLCPP_INFO(this->get_logger(),"add_ints 服务端启动完毕,等待请求提交...");
    }
  private:
    rclcpp::Service<AddInts>::SharedPtr server;
    // 3-2.处理请求数据并响应结果。
    void add(const AddInts::Request::SharedPtr req,const AddInts::Response::SharedPtr res){
      res->sum = req->num1 + req->num2;
      RCLCPP_INFO(this->get_logger(),"请求数据:(%d,%d),响应结果:%d", req->num1, req->num2, res->sum);
    }
};

int main(int argc, char const *argv[])
{
  // 2.初始化 ROS2 客户端;
  rclcpp::init(argc,argv);

  // 4.调用spin函数,并传入节点对象指针;
  auto server = std::make_shared<MinimalService>();
  rclcpp::spin(server);

  // 5.释放资源。
  rclcpp::shutdown();
  return 0;
}

2.客户端实现

        功能包cpp02_service的src目录下,新建C++文件demo02_client.cpp。

/*  
  需求:编写客户端,发送两个整型变量作为请求数据,并处理响应结果。
  步骤:
    1.包含头文件;
    2.初始化 ROS2 客户端;
    3.定义节点类;
      3-1.创建客户端;
      3-2.等待服务连接;
      3-3.组织请求数据并发送;
    4.创建对象指针调用其功能,并处理响应;
    5.释放资源。

*/
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"

using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;

// 3.定义节点类;
class MinimalClient: public rclcpp::Node{
  public:
    MinimalClient():Node("minimal_client"){
      // 3-1.创建客户端;
      client = this->create_client<AddInts>("add_ints");
      RCLCPP_INFO(this->get_logger(),"客户端创建,等待连接服务端!");
    }
    // 3-2.等待服务连接;
    bool connect_server(){
      while (!client->wait_for_service(1s))
      {
        if (!rclcpp::ok())
        {
          RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"强制退出!");
          return false;
        }

        RCLCPP_INFO(this->get_logger(),"服务连接中,请稍候...");
      }
      return true;
    }
    // 3-3.组织请求数据并发送;
    rclcpp::Client<AddInts>::FutureAndRequestId send_request(int32_t num1, int32_t num2){
      auto request = std::make_shared<AddInts::Request>();
      request->num1 = num1;
      request->num2 = num2;
      return client->async_send_request(request);
    }


  private:
    rclcpp::Client<AddInts>::SharedPtr client;
};

int main(int argc, char ** argv)
{
  if (argc != 3){
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"请提交两个整型数据!");
    return 1;
  }

  // 2.初始化 ROS2 客户端;
  rclcpp::init(argc,argv);

  // 4.创建对象指针并调用其功能;
  auto client = std::make_shared<MinimalClient>();
  bool flag = client->connect_server();
  if (!flag)
  {
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务连接失败!");
    return 0;
  }

  auto response = client->send_request(atoi(argv[1]),atoi(argv[2]));

  // 处理响应
  if (rclcpp::spin_until_future_complete(client,response) == rclcpp::FutureReturnCode::SUCCESS)
  {
    RCLCPP_INFO(client->get_logger(),"请求正常处理");
    RCLCPP_INFO(client->get_logger(),"响应结果:%d!", response.get()->sum);

  } else {
    RCLCPP_INFO(client->get_logger(),"请求异常");
  }

  // 5.释放资源。
  rclcpp::shutdown();
  return 0;
}

3.编辑配置文件

        (1)packages.xml

        在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:

<depend>rclcpp</depend>
<depend>base_interfaces_demo</depend>
        (2)CMakeLists.txt

        CMakeLists.txt 中服务端和客户端程序核心配置如下:

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(base_interfaces_demo REQUIRED)

add_executable(demo01_server src/demo01_server.cpp)
ament_target_dependencies(
  demo01_server
  "rclcpp"
  "base_interfaces_demo"
)
add_executable(demo02_client src/demo02_client.cpp)
ament_target_dependencies(
  demo02_client
  "rclcpp"
  "base_interfaces_demo"
)

install(TARGETS 
  demo01_server
  demo02_client
  DESTINATION lib/${PROJECT_NAME})

4.编译

        终端中进入当前工作空间,编译功能包:

colcon build --packages-select cpp02_service

5.执行

        当前工作空间下,启动两个终端,终端1执行服务端程序,终端2执行客户端程序。

        终端1输入如下指令:

. install/setup.bash
ros2 run cpp02_service demo01_server

        终端2输入如下指令:

. install/setup.bash
ros2 run cpp02_service demo02_client 100 200

四、服务通信(Python)

1.服务端实现

        功能包py02_service的py02_service目录下,新建Python文件demo01_server_py.py。

"""  
    需求:编写服务端,接收客户端发送请求,提取其中两个整型数据,相加后将结果响应回客户端。
    步骤:
        1.导包;
        2.初始化 ROS2 客户端;
        3.定义节点类;
            3-1.创建服务端;
            3-2.处理请求数据并响应结果。
        4.调用spin函数,并传入节点对象;
        5.释放资源。

"""

# 1.导包;
import rclpy
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts

# 3.定义节点类;
class MinimalService(Node):

    def __init__(self):
        super().__init__('minimal_service_py')
        # 3-1.创建服务端;
        self.srv = self.create_service(AddInts, 'add_ints', self.add_two_ints_callback)
        self.get_logger().info("服务端启动!")

    # 3-2.处理请求数据并响应结果。
    def add_two_ints_callback(self, request, response):
        response.sum = request.num1 + request.num2
        self.get_logger().info('请求数据:(%d,%d),响应结果:%d' % (request.num1, request.num2, response.sum))
        return response


def main():
    # 2.初始化 ROS2 客户端;
    rclpy.init()
    # 4.调用spin函数,并传入节点对象;
    minimal_service = MinimalService()
    rclpy.spin(minimal_service)
    # 5.释放资源。
    rclpy.shutdown()


if __name__ == '__main__':
    main()

2.客户端实现

        功能包py02_service的py02_service目录下,新建Python文件demo02_client_py.py。

"""  
    需求:编写客户端,发送两个整型变量作为请求数据,并处理响应结果。
    步骤:
        1.导包;
        2.初始化 ROS2 客户端;
        3.定义节点类;
            3-1.创建客户端;
            3-2.等待服务连接;
            3-3.组织请求数据并发送;
        4.创建对象调用其功能,处理响应结果;
        5.释放资源。

"""
# 1.导包;
import sys
import rclpy
from rclpy.node import Node
from base_interfaces_demo.srv import AddInts

# 3.定义节点类;
class MinimalClient(Node):

    def __init__(self):
        super().__init__('minimal_client_py')
        # 3-1.创建客户端;
        self.cli = self.create_client(AddInts, 'add_ints')
        # 3-2.等待服务连接;
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('服务连接中,请稍候...')
        self.req = AddInts.Request()

    # 3-3.组织请求数据并发送;
    def send_request(self):
        self.req.num1 = int(sys.argv[1])
        self.req.num2 = int(sys.argv[2])
        self.future = self.cli.call_async(self.req)


def main():
    # 2.初始化 ROS2 客户端;
    rclpy.init()

    # 4.创建对象并调用其功能;
    minimal_client = MinimalClient()
    minimal_client.send_request()

    # 处理响应
    rclpy.spin_until_future_complete(minimal_client,minimal_client.future)
    try:
        response = minimal_client.future.result()
    except Exception as e:
        minimal_client.get_logger().info(
            '服务请求失败:%r' % (e,))
    else:
        minimal_client.get_logger().info(
            '响应结果:%d + %d = %d' %
            (minimal_client.req.num1, minimal_client.req.num2, response.sum))

    # 5.释放资源。
    rclpy.shutdown()


if __name__ == '__main__':
    main()

3.编辑配置文件

        (1)package.xml

        在创建功能包时,所依赖的功能包已经自动配置了,配置内容如下:

<depend>rclpy</depend>
<depend>base_interfaces_demo</depend>
        (2)setup.py

   entry_points字段的console_scripts中添加如下内容:

entry_points={
    'console_scripts': [
        'demo01_server_py = py02_service.demo01_server_py:main',
        'demo02_client_py = py02_service.demo02_client_py:main'
    ],
},

4.编译

        终端中进入当前工作空间,编译功能包:

colcon build --packages-select py02_service

5.执行

        当前工作空间下,启动两个终端,终端1执行服务端程序,终端2执行客户端程序。

        终端1输入如下指令:

. install/setup.bash
ros2 run py02_service demo01_server_py

        终端2输入如下指令:

. install/setup.bash
ros2 run py02_service demo02_client_py 100 200

下期动作通信action见! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211255.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cuda入门学习

最近接触cuda 编程&#xff0c;记录一下。 1 工作实现一个【0-100&#xff09;的加法 如果用python sum 0 for i in range(200):sumi print(sum)2 cuda 的一些简单的概念 一维情况下大概是这样的 (1个grid * 2个blocks * 4个thread) 3 代码直接上代码 我把100分为20个b…

Jenkins---01

什么是敏捷开发 敏捷开发以用户的需求进化为核心&#xff0c;采用迭代、循序渐进的方法进行软件开发。在敏捷开 发中&#xff0c;软件项目在构建初期被切分成多个子项目&#xff0c;各个子项目的成果都经过测试&#xff0c;具备可视、 可集成和可运行使用的特征。换言之&…

2024年编程资料【9月份部分】

资料列表 「CSDN会员免费电子书1000本」 https://pan.quark.cn/s/5019390a751a 【黑马程序员】年度钻石会员-人工智能AI进阶 https://pan.quark.cn/s/1d14a2a179c2 JavaScript从入门到高级教程 - 带源码课件 https://pan.quark.cn/s/c16ed07eac93 【马哥教育】云原生微服务治理…

测试常用插件: ModHeader - Modify HTTP headers插件进行IP模拟/IP欺骗

由于公司是做海外项目的&#xff0c;所以付款时有要求进行模拟不同IP登录进去时会优先显示该地区的支付方式。 1.安装插件 这里以Microsoft Edge为例&#xff0c;打开扩展 搜索&#xff1a;ModHeader - Modify HTTP headers&#xff0c;进行获取安装即可 安装完成后&#xff…

CVESearch部署、使用与原理分析

文章目录 前言1、概述2、安装与使用2.1、源码安装2.1.1、部署系统依赖组件2.1.1.1、下载安装基础组件2.1.1.2、下载安装MongoDB Community Edition 7.0 2.1.2、使用源码安装系统2.1.2.1、安装CVESearch2.1.2.2、填充MongoDB数据库2.1.2.3、填充Redis数据库 2.2、使用方法 3、测…

LeetCode | 704.二分查找

标准的二分查找&#xff0c;直接上模板&#xff01; class Solution(object):def search(self, nums, target):""":type nums: List[int]:type target: int:rtype: int"""l 0r len(nums) - 1while l < r:mid (l r 1) / 2if nums[mid] …

Telnet命令详解:安装、用法及应用场景解析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…

笔试算法总结

文章目录 题目1题目2题目3题目4 题目1 使用 StringBuilder 模拟栈的行为&#xff0c;通过判断相邻2个字符是否相同&#xff0c;如果相同就进行删除 public class Main {public static String fun(String s) {if (s null || s.length() < 1) return s;StringBuilder builde…

EventLoop模块 --- 事件循环模块

目录 1 设计思想 eventfd 创建eventfd 2 实现 3 联合调试 4 整合定时器模块 5 联合超时模块调试 1 设计思想 EventLoop 模块是和线程一一绑定的&#xff0c;每一个EventLoop模块内部都管理了一个Poller对象进行事件监控&#xff0c;同时管理着多个Connection对象&…

python 使用faker库 生成数据

Welcome to Faker’s documentation! — Faker 30.3.0 documentationVersion1: Example from docs:from faker import Faker from faker.providers import internet for i in range(2): #批量生成数据fake Faker()name fake.name()address fake.address()text f…

el-动态表单的校验不触发/只触发了部分项

参考&#xff1a; 深入了解Element Form表单动态验证问题 转载vue elementUI组件表单动态验证失效的问题与解决办法 在别人的代码上开发新功能时&#xff0c;发现动态表单的校验功能突然出现问题&#xff1a; 重构前,只有两步&#xff0c;通过type来判断当前显示内容 <el-f…

Cesium.js(SuperMap iClient3D for Cesium)进行三维场景展示和图层动画

1&#xff09;&#xff1a;参考API文档&#xff1a;SuperMap iClient3D for Cesium 开发指南 2&#xff09;&#xff1a;官网示例&#xff1a;support.supermap.com.cn:8090/webgl/Cesium/examples/webgl/examples.html#layer 3&#xff09;&#xff1a;SuperMap iServer&…

自定义类型 - 结构体

2024 - 10 - 13 - 笔记 - 26 作者(Author): 郑龙浩 / 仟濹(CSDN账号名) 自定义类型 - 结构体 平时用的数组是一组相同类型的数据&#xff0c;如果想表示一组不同类型的数据&#xff0c;那么就可以结构体了。 ① 结构体的声明&#xff08;重要&#xff09; 自己起的名字&…

[论文阅读]: Detecting Copyrighted Content in Language Models Training Data

发布链接&#xff1a;http://arxiv.org/abs/2402.09910 核心目标&#xff1a;检测语言模型的训练过程中是否使用了受版权保护的内容 基于假设&#xff1a;语言模型有可能识别训练文本中的逐字节选 工作&#xff1a;提出了 DE-COP&#xff0c;一种确定训练中是否包含受版权保…

如何在Android平板上使用谷歌浏览器进行网页缩放

在使用Android平板时&#xff0c;我们经常会浏览各种网页&#xff0c;但有时网页内容可能无法适应屏幕大小&#xff0c;这时就需要用到网页缩放功能。本文将为您详细介绍如何在Android平 板上的谷歌浏览器中进行网页缩放&#xff0c;帮助您更好地浏览网页。&#xff08;本文由h…

Cursor 平替项目 bolt.new

Cursor 是一个全新的编程工具&#xff0c;旨在帮助开发者更高效地写代码。它不仅能提升编程速度&#xff0c;还能让代码更干净、更智能。无论你是编程新手还是经验丰富的开发者&#xff0c;Cursor AI都能为你提供智能辅助&#xff0c;显著提高编程效率。 但是目前 Cursor 免费…

QT开发--文件的读写操作

第十三章 文件的读写操作 Qt提供两种读写纯文本文件的方法&#xff1a; 1、直接使用 QFile 类的IO功能&#xff1b; 2、结合 QFile 和 QTextStream&#xff0c;利用流(Stream)进行操作。 13.1 文件读操作 13.1.1 使用QFile类 Qt封装了QFile类&#xff0c;方便我们对文件进行操…

物联网直播技术揭秘:如何保证超高可用性?

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! Hello,大家好!我是小米,一个29岁超爱分享技术的码农。今天跟大家聊一聊物联网时代下直播高可用方案的那些事儿。 随着物联网的快速发展,直播技术已…

针对考研的C语言学习(循环队列-链表版本以及2019循环队列大题)

题目 【注】此版本严格按照数字版循环队列的写法&#xff0c;rear所代表的永远是空数据 图解 1.初始化部分和插入部分 2出队 3.分部代码解析 初始化 void init_cir_link_que(CirLinkQue& q) {q.rear q.front (LinkList)malloc(sizeof(LNode));q.front->next NULL…

【宝可梦】游戏

pokemmo https://pokemmo.com/zh/ 写在最后&#xff1a;若本文章对您有帮助&#xff0c;请点个赞啦 ٩(๑•̀ω•́๑)۶