Chapter2 ROS通信机制—-基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

内容

1、话题交流

1.1 话题通信案例

1.1.1 话题通信自定义msg

1.1.2 话题通信自定义msg调用(C++实现)

1.1.3话题通信自定义msg调用(python实现)

1.2 话题通信总结

2.服务沟通

2.1 本节学什么

2.2 服务通信理论模型

2.2.1 服务通信理论图示

2.2.2 服务通信理论模型解释

2.3 服务通信自定义服务消息(srv)

2.3.1 需求流程分析

2.3.2 自定义srv文件实现

2.4 使用C++实现服务通信

2.4.1 分析

2.4.2 vscode配置

​2.4.3 服务端实现

2.4.4 客户端实现

2.5 用python实现服务通信

2.5.1  VSCODE配置

2.5.2 服务端实现

2.5.3 客户端实现

2.5.4 服务通信的注意事项

3.参数服务器

3.1 本节学什么

3.2 参数服务器理论模型

3.3 C++实现参数操作

3.3.1 参数新增

3.3.2 参数修改

3.3.3 参数获取

3.3.4 参数删除

3.4 Python实现参数操作

3.4.1 参数新增与修改

3.4.2 参数查询

3.4.3 参数删除

本节将继续介绍三种通信方式。主题交流的上一部分见上一节。

1、话题交流

1.1 话题通信案例

1.1.1 话题通信自定义msg

1.单一数据类型的局限性

ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty、数组…. 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息(扫描角度,障碍距离,强度信息)… std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型。

2.本节学习目的

创建包含人员信息的自定义(人员)消息:姓名、身高、年龄等。

3.自定义msg的具体实现

Ⅰ.定义msg文件

在 plumbing_pub_sub 的功能包下右击新建文件夹msg,msg下新建文件person.msg

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

并为人员设置三个字段

string name
int32 age
float32 height

Ⅱ.编辑配置文件

①配置package.xml,加入两条记录

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

②配置Cmakelist.txt

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
 add_message_files(
   FILES
  Person.msg
 )
 generate_messages(
   DEPENDENCIES
   std_msgs
 )
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES plumbing_pub_sub
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

Ⅲ.编译

编译结果生成多个中间文件:

devel目录下的Person.h、devel/lib/python3/msg目录下的_Person.py文件。

1.1.2 话题通信自定义msg调用(C++实现)

1.需求:

编写发布订阅实现,发布方以10HZ发布自定义信息,接收方订阅自定义信息并输出

2.实现–发布方

Ⅰ.vscode配置:为了方便代码提示以及避免误抛异常,需要先配置vscode,将前面生成的head文件路径配置进 c_cpp_properties.json的includepath属性

**路径名简单获取方式为 pwd 命令行

{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${workspaceFolder}/.vscode/browse.vc.db",
        "limitSymbolsToIncludedHeaders": false
      },
      "includePath": [
        "/opt/ros/melodic/include/**",
        "/home/liuhongwei/demo01_ws/src/helloworld1/include/**",
        "/usr/include/**",
        "/home/liuhongwei/demo03_ws/devel/include/**"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++14"
    }
  ],
  "version": 4
}

Ⅱ.发布方实现:

①在src目录下新建文件 demo03_pub_person.cpp

#include "ros/ros.h"
#include"plumbing_pub_sub/Person.h"

int main(int argc ,char * argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"banZhuren");
    ros::NodeHandle nh;
    ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("liaoTian",10);
    //创建发布数据
    plumbing_pub_sub::Person person;
    person.name = "张三";
    person.age = 1;
    person.height = 180.78;
    //设置发布频率
    ros::Rate rate(1);
    //循环发布数据
    while(ros::ok())
    {
        person.age++;
        //数据发布
        pub.publish(person);
        rate.sleep();
        //建议
        ros::spinOnce();
    }
    return 0;
}

②更改配置文件cmakelist.txt,增加两条信息,修改一条信息

 add_executable(demo03_pub src/demo03_pub.cpp)
target_link_libraries(demo03_pub
   ${catkin_LIBRARIES}
 )
 add_dependencies(demo03_pub ${PROJECT_NAME}_generate_messages_cpp)

③ 编译

④执行

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

3.实现–订阅方

Ⅰ.新建订阅方文件 demo04_sub.cpp

Ⅱ.实现代码

#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"

void doPerson(const plumbing_pub_sub::Person::ConstPtr & person)
{
    ROS_INFO("订阅人的信息:%s,%d,%.2f",person->name.c_str(),person->age,person->height);
}

int main(int argc,char * argv[])
{
    setlocale(LC_ALL,"");
    ROS_INFO("订阅方实现");
    ros::init(argc,argv,"jiaZhang");
    ros::NodeHandle nh;
    ros::Subscriber sub = nh.subscribe("liaoTian",10,doPerson);
    ros::spin();
    return 0;
}

Ⅲ.编辑配置文件

add_executable(demo04_sub src/demo04_sub.cpp)

add_dependencies(demo04_sub ${PROJECT_NAME}_generate_messages_cpp)

target_link_libraries(demo04_sub
${catkin_LIBRARIES}
)

Ⅳ.编译

Ⅴ.测试

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

1.1.3话题通信自定义msg调用(python实现)

1.需求

编写发布订阅实现,发布方以10HZ发布自定义信息,接收方订阅自定义信息并输出

2.实现—发布方

Ⅰ。将前面生成的python文件路径配置进 setting.json

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

终端中打开,用pwd命令获取路径

/home/liuhongwei/demo03_ws/devel/lib/python2.7/dist-packages

配置文件配置结果如下

{
    "python.autoComplete.extraPaths": [
        "/opt/ros/melodic/lib/python2.7/dist-packages"
    ],
    "python.analysis.extraPaths": [
        "/opt/ros/melodic/lib/python2.7/dist-packages",
        "/home/liuhongwei/demo03_ws/devel/lib/python2.7/dist-packages"
    ]
}

Ⅱ.发布方实现

①scripts目录下添加 demo03_pub.py 文件

#! usr/bin/env python


import rospy
from plumbing_pub_sub.msg import Person

if __name__ =="__main__":
    rospy.init_node("daMa")
    pub = rospy.Publisher("jiaoshetou",Person,queue_size=10)
    #创建person数据
    p = Person()
    p.name = "aoteman"
    p.age = 8
    p.height = 1.85
    #创建Rate对象
    rate = rospy.Rate(1)
    #循环发布数据
    while not rospy.is_shutdown():
        pub.publish(p)
        rospy.loginfo("发布的消息是:%s,%d,%.2f",p.name,p.age,p.height)
        rate.sleep()

②添加执行权限

chmod +x *.py

③配置配置文件

catkin_install_python(PROGRAMS
   scripts/demo01_pub_p.py
   scripts/demo02_sub_p.py
   scripts/demo03_pub.py

   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

④编译

⑤测试

#! /usr/bin/env python

import rospy
from plumbing_pub_sub.msg import Person


if __name__ == "__main__":
    rospy.init_node('daMa')
    pub = rospy.Publisher("jiaoshetou",Person,queue_size=10)
    p = Person()
    p.name = "huluwa"
    p.age = 18
    p.height = 0.75

    rate = rospy.Rate(1)
    while not rospy.is_shutdown():
        pub.publish(p) 
        rate.sleep() 
        rospy.loginfo("name:%s, age:%d, height:%.2f",p.name, p.age, p.height)

3.实现—订阅方

①添加文件demo04_sub.py并编写

#coding=utf-8
#! /usr/bin/env python

import rospy
from plumbing_pub_sub.msg import Person

def doPer(p):
    rospy.loginfo("小伙子的数据:%s,%d,%.2f",p.name,p.age,p.height)

if __name__ == "__main__":
    rospy.init_node("daYe")
    sub = rospy.Subscriber("jiaoshetou",Person,doPer)
    rospy.spin()

②添加可执行权限

③ 更改配置文件

④执行

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

1.2 话题通信总结

1.本章学了什么

话题通信的概念、作用、案例、理论模型、基本操作、C++调用接口、python调用接口、自定义msg 、配置文件更改、linux基础命令、赋予文件执行权限

2.服务沟通

2.1 本节学什么

1.服务通信的地位

服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景:

机器人在巡逻过程中,控制系统分析传感器数据,发现可疑物体或人……这时候需要拍照保存。

在上述场景中,就使用到了服务通信。类似于计算机网络的C/S模型

  • 一个节点需要向摄像头节点发送拍照请求,摄像头节点处理请求,并返回处理结果

2.服务通信的应用场景

服务通信更适用于对时效性要求高、有一定逻辑处理的应用场景。用于偶尔需要时效性和一定逻辑处理要求的数据传输场景。

3.能实现的经典案例

为了实现两个数字之和,客户端节点running会将两个数字发送给服务器,服务器节点接收两个数字之和并将结果返回给客户端。

2.2 服务通信理论模型

2.2.1 服务通信理论图示

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.2.2 服务通信理论模型解释

1.服务通信理论模型的三个角色(为方便理解,举个例子)

Ⅰ.管理者:roscore核心(中介平台)

Ⅱ.服务端:(马桶修理者)

Ⅲ.客户端 :(我)

2.服务通信具体流程

master根据话题实现client和server之间的连接的过程,master起到媒介的应用

Ⅰ.马桶修理平台(server)向中介平台(master)注册自己的信息(话题–疏通下水道+公司联系方式–RPC地址)

Ⅱ.我(client)向中介平台(master)注册自己所需要的服务(话题–疏通下水道)

Ⅲ.中介公司(master)进行话题匹配,并将服务端的联系方式(RPC地址)相应给我(客户端)

Ⅳ.我打电话给保洁公司

Ⅴ.保洁公司到我家修马桶(TCP)

3.注意事项

Ⅰ.和话题通信相比较,应该保证服务端是先启动的,客户端是后请求的

Ⅱ.服务端和客户端都可以有多个

Ⅲ.流程被封装,只需要调用即可

Ⅳ.也用到了话题,要保证话题相同

2.3 服务通信自定义服务消息(srv)

2.3.1 需求流程分析

Ⅰ.需求:服务通信中,客户端提交两个整数至服务端,服务端求和并响应结果到客户端,请创建服务器与客户端通信的数据载体。

Ⅱ.流程:

srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:

  1. 按照固定格式创建srv文件
  2. 编辑配置文件
  3. 编译生成中间文件

2.3.2 自定义srv文件实现

1.步骤

Ⅰ.在demo03_ws下新建一个功能包,包名为plumbing_server_client,包的依赖为roscpp、rospy、std_msgs

Ⅱ.创建自定义的服务消息

在功能包下新建一个文件夹srv,在里面新建一个文件addints.srv,这个文件就是自定义的服务消息载体,前面说到服务消息分为两部分:请求部分(2个整形数字)和相应部分(1个整形数字)。三个杠是请求与响应的交界线

int32 num1
int32 num2
---
int32 sum

Ⅲ.编辑配置文件

①package.xml加入一条

  <build_depend>message_runtime</build_depend>
  <exec_depend>message_runtime</exec_depend>

②cMakeList.txt修改四处信息

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
## Generate services in the 'srv' folder
 add_service_files(
   FILES
    addints.srv
 )
## Generate added messages and services with any dependencies listed here
 generate_messages(
   DEPENDENCIES
   std_msgs
 )
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES plumbing_server_client
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

③ 编译

④ 观察生成的文件

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.4 使用C++实现服务通信

2.4.1 分析

在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:

  1. 服务器
  2. 客户
  3. 数据

过程:

  1. 编写服务器端实现;
  2. 编写客户端实现;
  3. 编辑配置文件;
  4. 编译并执行。

2.4.2 vscode配置

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Ⅰ.获取位置:/home/liuhongwei/demo03_ws/devel/include

Ⅱ.添加到此

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.4.3 服务端实现

1.具体步骤

Ⅰ.在plumbing_server_client 功能包下的 src 目录下建立源文件 demo01_server.cpp

Ⅱ.搭建框架

服务端实现:解析客户端提交的数据,计算后生成响应
1.包含相关头文件
2.初始化ros节点
3.创建节点句柄
4.创建服务对象
5.处理请求并产生响应
6.spin()

Ⅲ.编程实现

#include "ros/ros.h"
#include"plumbing_server_client/addints.h"
/*
服务端实现:解析客户端提交的数据,并运算再产生响应
1.包含相关头文件
2.初始化ros节点
3.创建节点句柄
4.创建服务对象
5.处理请求并产生响应
6.spin()
*/

bool doNums(plumbing_server_client::addints::Request & request,plumbing_server_client::addints::Response &response)             //返回bool值,表明成功或者失败
{
    //1.处理请求
    int num1 = request.num1;
    int num2 = request.num2;
    ROS_INFO("收到的请求数据为:num1=%d,num2=%d",num1,num2);
    //2.组织响应
    int sum = num1+num2;
    response.sum = sum;
    ROS_INFO("求和结果为%d",sum);
    return true;
}

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"heishui");     //节点名称唯一
    ros::NodeHandle nh;
    ros::ServiceServer server = nh.advertiseService("addInts",doNums);  //话题名称、回调函数
    ros::spin();
    return 0;
}

Ⅳ.配置CMakeLists.txt

# Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
 add_executable(demo01_server src/demo01_server.cpp)
## Add cmake target dependencies of the executable
## same as for the library above
 add_dependencies(demo01_server ${PROJECT_NAME}_gencpp)

## Specify libraries to link a library or executable target against
 target_link_libraries(demo01_server
   ${catkin_LIBRARIES}
 )

Ⅴ.编译

Ⅵ.执行

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Ⅶ.关于测试

①rosservice的格式:

rosservice + call + 话题名称 + 空格 + table补齐

2.总结

与话题通信相比,服务通信创建服务对象调用的api不同,回调函数参数不同,需要注意下!

2.4.4 客户端实现

1.具体步骤

Ⅰ.当前工能包下(plumbing_server_client)下新建文件 demo02_client.cpp

Ⅱ.搭建框架

客户端:提交两个数据,处理响应结果
1.包含相关头文件
2.初始化ros节点
3.创建节点句柄
4.创建客户端对象
5.提交请求并产生响应

Ⅲ.编程实现

#include "ros/ros.h"
#include "plumbing_server_client/addints.h"
/*
客户端:提交两个数据,处理响应结果
1.包含相关头文件
2.初始化ros节点
3.创建节点句柄
4.创建客户端对象
5.提交请求并产生响应
*/

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"daBao");
    ros::NodeHandle nh;
    ros::ServiceClient client = nh.serviceClient<plumbing_server_client::addints>("addInts");    //话题
    //5-1组织请求
    plumbing_server_client::addints ai;
    ai.request.num1 = 100;
    ai.request.num2 = 200;

    //5-2处理响应
    bool flag = client.call(ai);   //客户端带着ai对象访问服务器
    if(flag)
    {
        ROS_INFO("响应成功");
        //获取sum
        ROS_INFO("响应结果 = %d",ai.response.sum);    //传进去是引用变量
    }
    else
    {
        ROS_INFO("处理失败.........");
    }
    return 0;
}

Ⅳ.编写配置文件

add_executable(demo02_client src/demo02_client.cpp)
 add_dependencies(demo02_client ${PROJECT_NAME}_gencpp)
target_link_libraries(demo02_client
   ${catkin_LIBRARIES}
 )

Ⅴ.编译

Ⅵ.测试

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.优化

Ⅰ.为什么优化:用户输入两个数实现交互

Ⅱ.实现参数的动态提交:

①格式:rosrun 包名 节点名称 12 34(两个参数)

②节点执行时需要获取命令中的参数并组织进request,怎么获取命令中的参数

Ⅲ.代码实现

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

3.思考

Ⅰ.—在上面的执行中,先启动服务端,再启动客户端—

如果反过来呢?会抛出异常

Ⅱ.如果先启动客户端,会抛出异常,若有需求先启动客户端也不抛出异常,而是挂起等服务器启动后再正常请求。

解决方案:ROS内置函数,可以让客户端启动后挂起等待服务器启动

在client处理相应之前加入这样一行代码

    client.waitForExistence();    //如果服务器没启动,服务器就会挂起

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

服务器启动,客户端启动

Ⅲ.最终code

#include "ros/ros.h"
#include "plumbing_server_client/addints.h"
/*
客户端:提交两个数据,处理响应结果
1.包含相关头文件
2.初始化ros节点
3.创建节点句柄
4.创建客户端对象
5.提交请求并产生响应
*/

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    //优化实现,获取命令中的参数
    if(argc!=3)
    {
        ROS_INFO("提交的参数个数不对");
        return 1 ;
    }


    
    ros::init(argc,argv,"daBao");
    ros::NodeHandle nh;
    ros::ServiceClient client = nh.serviceClient<plumbing_server_client::addints>("addInts");    //话题
    //5-1组织请求
    plumbing_server_client::addints ai;
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);

    //调用判断服务器状态的函数
    client.waitForExistence();    //如果服务器没启动,服务器就会挂起

    //5-2处理响应
    bool flag = client.call(ai);   //客户端带着ai对象访问服务器
    if(flag)
    {
        ROS_INFO("响应成功");
        //获取sum
        ROS_INFO("响应结果 = %d",ai.response.sum);    //传进去是引用变量
    }
    else
    {
        ROS_INFO("处理失败.........");
    }
    return 0;
}

2.5 用python实现服务通信

2.5.1  VSCODE配置

1.文件目录

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

/home/liuhongwei/demo03_ws/devel/lib/python2.7/dist-packages/plumbing_server_client

2.添加至配置文件 setting.json

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.5.2 服务端实现

1.具体步骤

Ⅰ.在功能包plumbing_server_client下,创建scripts文件夹,创建demo01_server.py文件

Ⅱ.框架搭建

Ⅲ.代码实现

#! /usr/bin/env python  
# encoding:utf-8


import rospy
from plumbing_server_client.srv import addints,addintsRequest,addintsResponse

"""
        服务端:解析客户请求,产生响应
            1.导包
            2.初始化ros节点
            3.创建服务端ros对象
            4.处理逻辑(回调函数)
            5.spin()
"""
#参数:封装了请求数据;返回值封装了响应数据
def doNums(request):
    #1.解析提交的两个证书
    num1 = request.num1
    num2 = request.num2
    #2.求和
    sum = num1+ num2
    #3.将结果封装进响应对象
    response = addintsResponse()
    response.sum = sum
    rospy.loginfo("服务器解析的数据 num1 = %d,num2 = %d ,相应的结果 sum = %d",num1,num2,sum)
    return response

if __name__ == "__main__":
    rospy.init_node("heishui")
    #第二个参数是参数类型 即srv下面的 addints.srv
    server = rospy.Service("addintss",addints,doNums)
    rospy.loginfo("服务器已启动")
    rospy.spin()

Ⅳ.赋予执行权限

Ⅴ.配置文件

①CMakeLists.txt

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
 catkin_install_python(PROGRAMS
   scripts/demo01_server.py
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

Ⅵ.编译

Ⅶ.执行

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.5.3 客户端实现

1.实现

Ⅰ.建立文件 demo02_client.py

Ⅱ.编程

#! /usr/bin/env python  
# encoding:utf-8

from http import client
from urllib import response
import rospy
from plumbing_server_client.srv import addints,addintsRequest,addintsResponse

"""
        客户端:组织并提交请求,处理服务端响应
            1.导包
            2.初始化ros节点
            3.创建客户端ros对象
            4.组织请求数据并发送请求
            5.处理响应
"""


if __name__ ==  "__main__":
    rospy.init_node("erHei")
    #ServiceProxy负责产生一个客户端对象
    client = rospy.ServiceProxy("addintss",addints)
    response =  client.call(12,34)
    rospy.loginfo("响应的数据是%d",response.sum)
    pass

Ⅲ.赋予权限

Ⅳ.更改配置文件

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
 catkin_install_python(PROGRAMS
   scripts/demo01_server.py
   scripts/demo02_client.py
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

Ⅴ.编译

Ⅵ.测试

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.针对客户端的优化

Ⅰ.怎么实现参数的动态提交

①需要导入 import sys 模块,此模块下有一个argv数组可以包含传入参数

②代码实现

#! /usr/bin/env python  
# encoding:utf-8

import sys
import rospy
from plumbing_server_client.srv import addints,addintsRequest,addintsResponse

"""
        客户端:组织并提交请求,处理服务端响应
            1.导包
            2.初始化ros节点
            3.创建客户端ros对象
            4.组织请求数据并发送请求
            5.处理响应
"""


if __name__ ==  "__main__":
    #判断参数长度
    if len(sys.argv)!=3:
        rospy.logerr("传入的参数个数错误")
        sys.exit(1)
    

    rospy.init_node("erHei")
    #ServiceProxy负责产生一个客户端对象
    client = rospy.ServiceProxy("addintss",addints)

    #解析传入的参数
    num1 = int(sys.argv[1])
    num2 = int(sys.argv[2])
    response =  client.call(num1,num2)
    rospy.loginfo("响应的数据是%d",response.sum)
    pass

③测试

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.5.4 服务通信的注意事项

1.先启动客户端再启动服务端会抛出异常,如何让客户端”等一等”服务端呢(挂起)

Ⅰ.需求:需要客户端先于服务端启动,不要抛出异常,挂起,等待服务启动后再次发动请求

Ⅱ.方法:利用ROS内置函数,可以用来判断服务器状态,如果服务没启动,就让客户端挂起

Ⅲ.实现:在调用服务器前加入此行代码

   #等待服务启动,服务器没启动则挂起
    client.wait_for_service()

Ⅳ.测试

①先启动客户端,不启动服务器,不报异常

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

②启动服​​务器,暂停部分恢复正常

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现③还有一种方法可以达到上述目的

rospy.wait_for_service("话题名")

3.参数服务器

3.1 本节学什么

1.参数服务器的应用场景

参数服务器在ROS中主要用于实现不同节点之间的数据共享(P2P)。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据,关于参数服务器的典型应用场景如下:

实现导航时,会进行路径规划,如:全局路径规划,设计一条从起点到目标点的粗略路径。局部路径规划,会根据当前路况不时生成出行路径

在上述场景中,参数服务器用于全局路径规划和局部路径规划:

  • 在规划路径时,我们需要参考汽车的大小。我们可以将这些尺寸信息存储在参数服务器中。全局路径规划节点和本地路径规划节点都可以从参数服务器调用这些参数(有些路径有宽度和高度限制)

参数服务器一般适用于一些存在数据共享的应用场景。

我们将台车尺寸放在一个参数服务器中,这样每个节点都可以通过台车尺寸来判断是否可以通过,直接从参数服务器中获取即可

2. 本章案例

实现参数的增删改查操作。

3.2 参数服务器理论模型

1.理论模型图示

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

2.理论模型三个角色

Ⅰ.ROS管理者(其中有一个管理参数的公共容器)

Ⅱ.参数的设置方

三、参数调用者

3.理论模型的流程

Ⅰ.参数的设置方提交参数到管理者,管理者将参数保存至一个参数列表

Ⅱ.参数的调用者向管理者发送一个参数调用请求

Ⅲ.管理者到参数列表中查找相关参数,将参数值返回给调用者

4.参数服务器的注意事项

参数服务器不是为高性能而设计的,所以最好用来存储静态的非二进制简单数据

5.参数服务器可以使用的数据类型

  • 32-bit integers
  • booleans
  • strings
  • doubles
  • iso8601 dates
  • lists
  • base64-encoded binary data
  • 字典

3.3 C++实现参数操作

3.3.1 参数新增

Ⅰ.建立功能包 plumbing_param_server,并建立依赖包 roscpp、rospy,std_msgs

Ⅱ.定位到功能包下的src文件,新建文件 demo01_param_set.cpp

Ⅲ.编写代码

#include "ros/ros.h"

/*
需要实现参数的新增和修改
1.需求:首先设置机器人的共享参数,类型、半径、再修改半径
2.通过两套api实现  ros::NodeHandle  setParam    ros::param set
*/

int main(int argc, char  *argv[])
{
    ros::init(argc,argv,"setpara_c");
    ros::NodeHandle nh;

    //参数增加
    //方案1:参数名字,参数值
    nh.setParam("type","xiaoHuang");
    nh.setParam("radius",0.15);

    //方案2:参数名字,参数值
    ros::param::set("type_param","xiaobai");
    ros::param::set("radius_param",0.1);
    return 0;
}

Ⅳ.修改配置文件

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
 add_executable(demo01_param_set src/demo01_param_set.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
 target_link_libraries(demo01_param_set
   ${catkin_LIBRARIES}
 )

Ⅴ.编译

Ⅵ.执行:执行完毕之后参数已经到参数服务器了

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

rosparam list命令:列出所有参数列表

rosparam get /type 命令 :获取type的参数值

3.3.2 参数修改

1.修改代码,采用覆盖思想

   //修改 方案一:参数覆盖
    nh.setParam("radius",0.2);
    
    //修改 方案二
    ros::param::set("radius_param",0.25);

3.3.3 参数获取

1.查询步骤

Ⅰ.在功能包下的src目录创建 demo02_param_get.cpp

Ⅱ.了解修改API

/*
    参数服务器操作之查询_C++实现:
    在 roscpp 中提供了两套 API 实现参数操作
    ros::NodeHandle

        param(键,默认值) 
            存在,返回对应结果,否则返回默认值

        getParam(键,存储结果的变量)
            存在,返回 true,且将值赋值给参数2
            若果键不存在,那么返回值为 false,且不为参数2赋值

        getParamCached键,存储结果的变量)--提高变量获取效率
            存在,返回 true,且将值赋值给参数2
            若果键不存在,那么返回值为 false,且不为参数2赋值

        getParamNames(std::vector<std::string>)
            获取所有的键,并存储在参数 vector 中 

        hasParam(键)
            是否包含某个键,存在返回 true,否则返回 false

        searchParam(参数1,参数2)
            搜索键,参数1是被搜索的键,参数2存储搜索结果的变量

    ros::param ----- 与 NodeHandle 类似

*/

Ⅲ.代码实现

#include "ros/ros.h"

/*
演示参数查询
*/

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"get_param_c");
    ros::NodeHandle nh;

    //ros::NodeHandle----------------------------------------
    //1.param(键,默认值)   存在,返回对应结果,否则返回默认值(0.5)
    double radius = nh.param("radius",0.5);
    ROS_INFO("radius = %.2f",radius);

    //2.getParam(键,存储结果的变量):键存在返回值是true,并且将值赋给参数2。不存在为false,不为参数二赋值
    double radius2;
    bool result = nh.getParam("radius",radius2);
    if(result)
    {
        ROS_INFO("获取的半径是 radius = %.2f",radius2);
    }
    else
    {
        ROS_INFO("被查询的变量不存在");
    }

    //3.getParamCached(键,存储结果的变量)  --从缓存获取,效率更高,结果与3一样的

    //4.getParamNames(std::vector<std::string>) 获取所有的键名称,并存储在参数 vector 中
    std::vector<std::string> names;
    nh.getParamNames(names);
    for(auto && name:names)
    {
        ROS_INFO("遍历到的元素:%s",name.c_str());
    }
    
    //5.hasParam(键) 是否包含某个键 是返回true 否则返回false
    bool flag1  = nh.hasParam("radius");
    bool flag2 = nh.hasParam("sjsjsjs");
    ROS_INFO("参数一存在吗?%d",flag1);
    ROS_INFO("参数二存在吗?%d",flag2);

    //6.searchParam(参数一,参数二)  参数一是搜索的键,参数二存储键的名称
    std::string key ;
    nh.searchParam("radius",key);
    ROS_INFO("搜索结果%s",key.c_str());
    return 0;
}

Ⅳ.修改配置文件

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
 add_executable(demo01_param_set src/demo01_param_set.cpp)
 add_executable(demo02_param_get src/demo02_param_get.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
 target_link_libraries(demo01_param_set
   ${catkin_LIBRARIES}
 )
 target_link_libraries(demo02_param_get
   ${catkin_LIBRARIES}
 )

3.3.4 参数删除

1.删除步骤

Ⅰ.新建文件 demo04_param_del.cpp,修改配置文件

Ⅱ.编写代码

#include "ros/ros.h"
/*
仍然两种方式实现
*/

int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"param_del");
    ros::NodeHandle nh;

    //删除一
    bool flag1 = nh.deleteParam("radius");
    if(flag1)
    {
        ROS_INFO("success");
    }
    else{
        ROS_INFO("Failed");
    }
    //删除二
    bool flag2 = ros::param::del("radius_param");
     if(flag2)
    {
        ROS_INFO("success");
    }
    else{
        ROS_INFO("Failed");
    }
    return 0;
}

3.4 Python实现参数操作

3.4.1 参数新增与修改

1.步骤

Ⅰ.在功能包下新建scripts文件夹,新建文件demo01_param_set.py

Ⅱ.编写代码逻辑

Ⅲ.添加可执行权限

Ⅳ.修改配置文件

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
 catkin_install_python(PROGRAMS
   scripts/demo01_param_set.py
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

Ⅴ.执行效果

命令:rosparam list 列出所有参数

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

命令:rosparam get 参数名:获取参数值

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

修改后

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

3.4.2 参数查询

Ⅰ.新建demo02_param_get.py文件,赋予其可执行权限,修改配置文件

Ⅱ.逻辑:demo01_param_set.py 主要负责设置参数,demo02_param_get.py 负责查询参数

Ⅲ.# get_param : 根据键获取参数的值,第二个参数是默认值

radius =  rospy.get_param("radius_p",0.5)
rospy.loginfo("radius_p = %.2f",radius)
    
radius2 =  rospy.get_param("radius_pxxx",0.5)
rospy.loginfo("radius_pxxx = %.2f",radius2)

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Ⅳ.    #get_param_cached 与上述结果一样,由于从高速缓冲区取数据,效率更高

Ⅴ.    #get_param_names 获取所有参数键的集合,无需参数

    names = rospy.get_param_names()
    for name in names:
        rospy.loginfo("name = %s",name)

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Ⅵ.    #has_param 判断是否有键

    flag1 = rospy.has_param("radius_p")
    if(flag1):
        rospy.loginfo("键存在")
    else:
        rospy.loginfo("键不存在")

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

Ⅶ.    #search_param 存在返回键名

    key = rospy.search_param("radius_p")
    rospy.loginfo("key = %s",key)

Ⅷ.所有代码

#! /usr/bin/env python
#encoding:utf-8

import rospy

"""
演示参数的查询
"""

if __name__ =="__main__":
    rospy.init_node("get_param_g")
    # get_param : 根据键获取参数的值,第二个参数是默认值
    radius =  rospy.get_param("radius_p",0.5)
    rospy.loginfo("radius_p = %.2f",radius)

    radius2 =  rospy.get_param("radius_pxxx",0.5)
    rospy.loginfo("radius_pxxx = %.2f",radius2)

    #get_param_cached
    #get_param_names 获取所有参数键的集合,无需参数
    names = rospy.get_param_names()
    for name in names:
        rospy.loginfo("name = %s",name)

    #has_param 判断是否有键
    flag1 = rospy.has_param("radius_p")
    if(flag1):
        rospy.loginfo("键存在")
    else:
        rospy.loginfo("键不存在")

    #search_param 存在返回键名
    key = rospy.search_param("radius_p")
    rospy.loginfo("key = %s",key)
    pass

3.4.3 参数删除

1.实现

Ⅰ.新建demo03_param_del.py,赋予执行权限,修改配置文件

Ⅱ.实现逻辑及编译

#! /usr/bin/env python
#encoding:utf-8

import rospy

if __name__ == "__main__":
    rospy.init_node("del_param")
    #用delete_param()实现参数删除
    rospy.delete_param("radius_p")
    pass

Ⅲ.实现结果:可以看到,成功删除了参数 radius_p ,另外,重复执行会抛出异常

Chapter2 ROS通信机制----基础篇(Ⅱ)&ROS三种通信机制原理及案例实现

如果不想抛出异常(即去重不想抛出错误,改代码逻辑)

#! /usr/bin/env python
#encoding:utf-8

import rospy

if __name__ == "__main__":
    rospy.init_node("del_param_s")
    #用delete_param()实现参数删除
    try:
        rospy.delete_param("radius_p")
    except Exception as e:
        rospy.loginfo("被删除的参数不存在")  
    pass

这样就不会重复删除参数,也不会抛出异常!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2022年4月13日 下午2:58
下一篇 2022年4月13日 下午3:44

相关推荐