由于这一章东西比较多,我分了上下两篇,下部分可以在《 ROS 入门跟着我就够了》专辑中查看

ROS 中的基本通信机制主要有如下三种实现策略:

话题通信(发布订阅模式)

服务通信(请求响应模式)

参数服务器(参数共享模式)

发布订阅中,发布方,订阅方通过话题把二者订阅到一起,订阅者受到发布方的话题(类似于公众号与关注者的关系,公众号发布有关信息的话题给关注者)

服务通信中,服务端(server)与客户端(client),客户端先请求,服务端再反应(类似于输入网址,网址响应的过程)

参数服务器,(公司里的小零食,你可以拿也可以放,共同享用,共同维护)

本章的主要内容就是是介绍各个通信机制的应用场景、理论模型、代码实现以及相关操作命令。本章预期达成学习目标如下:

  • 能够熟练介绍ROS中常用的通信机制
  • 能够理解ROS中每种通信机制的理论模型
  • 能够以代码的方式实现各种通信机制对应的案例
  • 能够熟练使用ROS中的一些操作命令
  • 能够独立完成相关实操案例

三种通信机制要重点掌握,重中之重,要做到如数家珍

常用命令,实操,预期达成的目标,操作命令,数据交互,非常快捷

2.1 话题通信

话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式

老师举的例子:

机器人在执行导航功能,使用的传感器是激光雷达,机器人会采集激光雷达感知到的信息并计算,然后生成运动控制信息驱动机器人底盘运动。

在上述场景中,就不止一次使用到了话题通信。

以激光雷达信息的采集处理为例,在 ROS 中有一个节点需要实时的发布当前雷达采集到的数据,导航模块中也有节点会订阅并解析雷达数据。
再以运动消息的发布为例,导航模块会根据传感器采集的数据时时的计算出运动控制信息并发布给底盘,底盘也可以有一个节点订阅运动信息并最终转换成控制电机的脉冲信号。

概念

以发布订阅的方式实现不同节点之间数据交互的通信模式。

作用

用于不断更新的、少逻辑处理的数据传输场景。

案例

  • 1.实现最基本的发布订阅模型,发布方以固定频率发送一段文本,订阅方接收文本并输出。(2.1.2 – 2.1.3)
  • 2.实现对自定义消息的发布与订阅。(2.1.4 – 2.1.6)

另请参考:

http://wiki.ros.org/ROS/Tutorials/CreatingMsgAndSrv

2.1.1 理论模型

话题通信
在这里插入图片描述

角色 ---------流程--------- 注意事项

角色:

  1. master ----->管理者 (媒婆)
  2. talker ----->发布者 (男方)
  3. listener----->订阅者 (女方)

流程:

master起到了一个撮合的作用,可以话题建立发布者和订阅者之间的连接

这里老师拿婚介的例子非常生动形象

第0步 狗子提交自身信息,话题(房子——手机号)

第1步 翠花提交自身信息,话题(房子)

第2步 把狗子的电话发送给翠花

第3步 翠花打电话给狗子

第4步 狗子响应,加了微信

第5步 翠花加了微信(通过TCP)

第6步 发布消息,翠花直接订阅到了

注意事项:

1.使用的协议有RPC以及TCP
2.步骤0和步骤1没有顺序关系
3.发布方和订阅方可以存在多个
4.发布方和订阅方建立连接后,master就可以关闭了
5.上述实现流程已经封装了,以后直接调用即可

整个流程由以下步骤实现:

**0.Talker注册**

Talker启动后,会通过RPC在 ROS Master 中注册自身信息,其中包含所发布消息的话题名称。ROS Master 会将节点的注册信息加入到注册表中。

**1.Listener注册**

Listener启动后,也会通过RPC在 ROS Master 中注册自身信息,包含需要订阅消息的话题名。ROS Master 会将节点的注册信息加入到注册表中。

**2.ROS Master实现信息匹配**

ROS Master 会根据注册表中的信息匹配Talker 和 Listener,并通过 RPC 向 Listener 发送 Talker 的 RPC 地址信息。

**3.Listener向Talker发送请求**

Listener 根据接收到的 RPC 地址,通过 RPC 向 Talker 发送连接请求,传输订阅的话题名称、消息类型以及通信协议(TCP/UDP)。

**4.Talker确认请求**

Talker 接收到 Listener 的请求后,也是通过 RPC 向 Listener 确认连接信息,并发送自身的 TCP 地址信息。

**5.Listener与Talker建立连接**

Listener 根据步骤4 返回的消息使用 TCP 与 Talker 建立网络连接。

**6.Talker向Listener发送消息**

连接建立后,Talker 开始向 Listener 发布消息。

注意1:上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议

注意2: Talker 与 Listener 的启动无先后顺序要求

注意3: Talker 与 Listener 都可以有多个

注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。

话题通信应用时的关注点:

0.大部分实现已经被封装了
1.话题设置
2.关注发布者实现
3.关注订阅者实现
4.关注消息载体

master可以管理和匹配话题

2.1.2 话题通信基本操作A(C++)

需求:

编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。

分析:

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

发布方
接收方
数据(此处为普通文本)
流程:(c++版本)
编写发布方实现;
编写订阅方实现;
编辑配置文件;
编译并执行。

发布方cpp文件框架

实现流程:
        1.包含头文件             //包含std_msgs
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄        //NodeHandle
        4.实例化 发布者 对象      //advertise+泛型+话题+队列长度
        5.组织被发布的数据,并编写逻辑发布数据      //运用while循环,发布msg的字段

在这里插入图片描述

运用rostopic echo fang来验证 (fang是cpp程序中的话题名字)

进阶

添加了任务所要求的发布频率,设置编号,字符串拼接,日志输出

普通发布者代码


    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 发布者 对象
        5.组织被发布的数据,并编写逻辑发布数据

*/
// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>

int main(int argc, char  *argv[])
{   
    //设置编码
    setlocale(LC_ALL,"");

    //2.初始化 ROS 节点:命名(唯一)
    // 参数1和参数2 后期为节点传值会使用
    // 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    ros::init(argc,argv,"talker");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能

    //4.实例化 发布者 对象
    //泛型: 发布的消息类型
    //参数1: 要发布到的话题
    //参数2: 由于网络阻塞,不是所有信息都能及时发出去,会存进这个队列,队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);


    //5.组织被发布的数据,并编写逻辑发布数据
    //数据(动态组织)
    std_msgs::String msg;
    // msg.data = "你好啊!!!";
    std::string msg_front = "Hello 你好!"; //消息前缀
    int count = 0; //消息计数器

    //逻辑(一秒10次)
    ros::Rate r(1);

    //节点不死
    while (ros::ok())//按下ctrl+c会返回false
    {
        //使用 stringstream 拼接字符串与编号
        std::stringstream ss;
        ss << msg_front << count;
        msg.data = ss.str();
        //发布消息
        pub.publish(msg);
        //加入调试,打印发送的消息
        ROS_INFO("发送的消息:%s",msg.data.c_str());

        //根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
        r.sleep();
        count++;//循环结束前,让 count 自增
        //暂无应用
        ros::spinOnce();
    }


    return 0;
}
订阅者cpp文件框架

在同一个包内src创建demo02_sub.cpp的文件
基本架构和发布者类似,主要的细节都已经放在程序的注释里供参考(以下cpp和上述不匹配,后一个cpp匹配,注意话题的匹配性)

#include"ros/ros.h"
#include"std_msgs/String.h"
/*
    订阅方实现
        1.包含头文件
            ROS中文本的类型---->std_msgs/String.h
        2.初始化ROS节点
        3.创建节点句柄
        4.创建订阅者对象
        5.处理订阅到的数据
        6.声明spin函数
*/
void  doMsg(const std_msgs::String::ConstPtr &msg)//回调函数,类似于地雷,先埋好,等着小鬼子过来,不同于普通函数调用可以自己在任意时间触发
{
   //通过msg获取并操作订阅到的数据
   ROS_INFO("翠花订阅到的数据:%s",msg->data.c_str());

}
int main(int argc,char* argv[])
{
     //2.初始化ROS节点
     ros::init(argc,argv,"cuihua");//节点不能重名,必须保证唯一性
        //3.创建节点句柄
     ros::NodeHandle nh;
       // 4.创建订阅者对象
     ros::Subscriber sub=nh.subscribe("fang",10,doMsg);//多了一个回调函数,范性函数不用写,可以自动推导
        //5.处理订阅到的数据

     ros::spin();//重新调用回调函数
    return 0;
}

订阅者标准代码

/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)


    消息订阅方:
        订阅话题并打印接收到的消息

    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 订阅者 对象
        5.处理订阅的消息(回调函数)
        6.设置循环调用回调函数

*/
// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h"

void doMsg(const std_msgs::String::ConstPtr& msg_p){//回调函数,类似于地雷,先埋好,等着小鬼子过来,不同于普通函数调用可以自己在任意时间触发
    ROS_INFO("我听见:%s",msg_p->data.c_str());
    // ROS_INFO("我听见:%s",(*msg_p).data.c_str());
}
int main(int argc, char  *argv[])
{
    setlocale(LC_ALL,"");
    //2.初始化 ROS 节点:命名(唯一)
    ros::init(argc,argv,"listener");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;

    //4.实例化 订阅者 对象
    ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
    //5.处理订阅的消息(回调函数) 

    //     6.设置循环调用回调函数
    ros::spin();//循环读取接收的数据,并调用回调函数处理

    return 0;
}
 

接着修改Cmakelists文件如下图所示
在这里插入图片描述
接着:

1.启动 roscore;

2.启动发布节点;

3.启动订阅节点。

在这里插入图片描述

这次出现的问题:
1

[rosrun] Couldn't find executable named demo01_pub.cpp below /home/zhangyuanbo/somo01/src/plumbing_pubsub
[rosrun] Found the following, but they're either not files,
[rosrun] or not executable:
[rosrun]   /home/zhangyuanbo/somo01/src/plumbing_pubsub/src/demo01_pub.cpp

这里直接到根目录给两个文件权限就可以了
在这里插入图片描述
注意(这个地方我没出现错误,但这也是值得注意的点)

补充0:

vscode 中的 main 函数 声明 int main(int argc, char const *argv[]){},默认生成 argv 被 const 修饰,需要去除该修饰符

补充1:

ros/ros.h No such file or directory …

检查 CMakeList.txt find_package 出现重复,删除内容少的即可

参考资料:https://answers.ros.org/question/237494/fatal-error-rosrosh-no-such-file-or-directory/

补充2:

find_package 不添加一些包,也可以运行啊, ros.wiki 答案如下

You may notice that sometimes your project builds fine even if you did not call find_package with all dependencies. This is because catkin combines all your projects into one, so if an earlier project calls find_package, yours is configured with the same values. But forgetting the call means your project can easily break when built in isolation.

补充3:

订阅时,第一条数据丢失

原因: 发送第一条数据时, publisher 还未在 roscore 注册完毕

解决: 注册后,加入休眠 ros::Duration(3.0).sleep(); 延迟第一条数据的发送

在这里插入图片描述
紧接着,先启动订阅方,再启动发布方,可以实现同步

在这里插入图片描述

PS:可以使用 rqt_graph 查看节点关系(计算图)
在这里插入图片描述

talker将 chatter有关的信息发给了listener

接下来,用

PYTHON方式完成话题通信

2.1.3 话题通信基本操作B(Python)

需求:

编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布文本消息,订阅方订阅消息并将消息内容打印输出。

分析:

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

发布方
接收方
数据(此处为普通文本)

流程:

编写发布方实现;
编写订阅方实现;
为python文件添加可执行权限;
编辑配置文件;
编译并执行。

发布者简单PY文件实现

#! /usr/bin/env  python
#-*-coding:UTF-8-*-
#导包
import rospy
from std_msgs.msg import String#发布消息的类型(导包)
"""
使用python实现消息发布
    1.导包
    2.初始化ros节点
    3.创建发布者对象:
    4 .编写发布逻辑并发布数据
"""
if __name__=="__main__":  #编写主入口
     #2.初始化ros节点:
     rospy.init_node("sandai")#传入节点名称,发布者名称
     #3.创建发布者对象:
     pub=rospy.Publisher("che",String,queue_size=10)#话题名称,发布数据类型,队列长度
     #4 .编写发布逻辑并发布数据
    #创建数据
     msg=String()
    #使用循环发布数据
     while not rospy.is_shutdown():
        msg.data="hello"
    #发布数据
        pub.publish(msg)

PY文件需要给文件添加可执行权限

修改Cmakelists
主要需要修改的部分是scripts后面的文件命,需要带上py
在这里插入图片描述
如下图所示

在这里插入图片描述
接着启动roscore 执行发布者py程序,注意这里文件名需要添加上.py不然会报错

在这里插入图片描述

rostopic echo che

可以死循环发布话题 che是话题

下面加上所需要的功能
这里添加了频率,计数等功能

#! /usr/bin/env  python
#-*-coding:UTF-8-*-
#导包
import rospy
from std_msgs.msg import String#发布消息的类型(导包)
"""
使用python实现消息发布
    1.导包
    2.初始化ros节点
    3.创建发布者对象:
    4 .编写发布逻辑并发布数据
"""
if __name__=="__main__":  #编写主入口
     #2.初始化ros节点:
     rospy.init_node("sandai")#传入节点名称,发布者名称
     #3.创建发布者对象:
     pub=rospy.Publisher("che",String,queue_size=10)#话题名称,发布数据类型,队列长度
     #4 .编写发布逻辑并发布数据
    #创建数据
     msg=String()
    #指定发布频率
     rate=rospy.Rate(1)
    #设置计数器
     count=0
    #使用循环发布数据
     while not rospy.is_shutdown():
        count += 1
        msg.data="hello"+str(count)
    #发布数据
        pub.publish(msg)
        rospy.loginfo("发布的数据是:%s",msg.data)
        rate.sleep()#与发布频率配套

最后实现的结果如下图所示

在这里插入图片描述
订阅者PY文件实现

#! /usr/bin/env python   #shebang行不能错,易错
#-*-coding:UTF-8-*-
#1.导包
import rospy
from std_msgs.msg import String
"""
    订阅流程:
        1.导包
        2.初始化ros节点
        3.创建订阅者对象
        4.回调函数处理数据
        5.spin()
"""
#回调函数中传入msg
def doMsg(msg):rospy.loginfo("我的订阅到的数据:%s",msg.data)
if __name__=="__main__":
        #1.导包
        #2.初始化ros节点
        rospy.init_node("huahua")
        #3.创建订阅者对象
        sub=rospy.Subscriber("che",String,doMsg,queue_size=10)
        #4.回调函数处理数据
        #5.spin()
        rospy.spin()

下面修改Cmakelists,在scripts所在目录中端添加可执行权限(ll后如果无法添加,那么必须右键属性修改PY文件的可执行权限)
在这里插入图片描述

1.启动 roscore;

2.启动发布节点;

3.启动订阅节点。

运行结果与引言部分的演示案例1类似。

PS:可以使用 rqt_graph 查看节点关系。

如下图所示,运行成功
在这里插入图片描述
问题:
1
和cpp一样的问题需要,解决少数据的问题
在发布之前加一个休眠

rospy.sleep(3)

在这里插入图片描述
最后启动计算图rqt_graph 查看节点关系

箭头是发布的方向
che是发布的话题名称

在这里插入图片描述
最后官方实现更为严谨,使用到了try防止出错,建议自行打一遍
具体网址

http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber%28python%29

在这里插入图片描述
因为是TCP通信,所以不同语言编写的发布方和订阅方可以连接在一起

注意记得要把话题改为一致

在这里插入图片描述

2.1.4 话题通信自定义msg

在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息… std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型

msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:

int8, int16, int32, int64 (或者无符号类型: uint*)

float32, float64

string

time, duration(时间)

other msg files(其他文件)

variable-length array[] and fixed-length array[C](变长数组,定长数组)

ROS中还有一种特殊类型:Header(现在暂且用不到),标头包含时间戳(消息被发布出去的时间值)和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。

需求:创建自定义消息,该消息包含人的信息:姓名、身高、年龄等

流程:

   1 按照固定格式创建 msg 文件
   2 编辑配置文件(package.xml,cakelists)
   3 编译生成可以被 Python 或 C++ 调用的中间文件

1.定义msg文件(类似于C中的结构体)

在功能包src中新建msg文件夹,并新建msg后缀的文件!(注意文件创建的位置
在这里插入图片描述

2.编辑配置文件
编辑package.xml文件

添加message_generation以及message_runtime

修改Cmakelists

a 编译功能包需要 message_generation(编译时依赖
findpackage的意思是如果你想要编译plumbing_pubsub自定义功能包的话,你必须依赖于findpackage里面的这些包

需要加入 message_generation,必须有 std_msgs

在这里插入图片描述b 添加自己注释的message文件,也就是message的名字

配置 msg 源文件

在这里插入图片描述c 如果编译person.msg 必须得依赖std_msgs(标准消息)

生成消息时依赖于 std_msgs

在这里插入图片描述
d findpackage里的功能包依赖于catkin_package里的功能包,属于层层依赖
这里需要添加message_runtime这个功能包(运行时依赖

执行时依赖

在这里插入图片描述紧接着编译
编译成功

在这里插入图片描述

编译后的中间文件查看:
至此可以看到如果用C++编译,在工作空间中生成的person.h头文件,其中包含有关于person的结构体函数,可以调用(…/工作空间/devel/include/包名/xxx.h)

也可以看到如果用python编译,在工作空间中生成的_person.py头文件,其中包含有关于person的函数,可以调用(…/工作空间/devel/lib/python3/dist-packages/包名/msg)

在这里插入图片描述

2.1.5 话题通信自定义msg调用A(C++)

需求:

编写发布订阅实现,要求发布方以10HZ(每秒10次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。

分析:

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

发布方
接收方
**数据(此处为自定义消息)**
流程:
编写发布方实现;
编辑配置文件;
编写订阅方实现;
编辑配置文件;
编译并执行。
首先是vscode 的操作

如果不配置,可能会抛异常,不给代码提示

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

具体流程:
1.
在Include 下 找到 plumbing_pubsub,右键进入该地址下的终端
在这里插入图片描述

2
使用指令pwd 输出当前地址,右键复制地址
3
找到工作空间下的c_cpp_properties.json,添加刚才复制的路径,记得在后面加上两个**这个代表着包括这个目录下所有的路径

一定要注意这里的路径只能到include为止,博主往后写到包名直接卡一个多小时找错误(很惨)

{
    "configurations": [
        {
            "browse": {
                "databaseFilename": "",
                "limitSymbolsToIncludedHeaders": true
            },
            "includePath": [
                "/opt/ros/noetic/include/**",
                "/usr/include/**",
                "/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径 
            ],
            "name": "ROS",
            "intelliSenseMode": "gcc-x64",
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

我的截图如下所示:

在这里插入图片描述
紧接着修改Cmakelists文件,这里需要多修改一个add_dependencies 文件,这是为了保证先对person.msg 编译再对 cpp文件编译,防止cpp无法识别所对应在msg中的person结构体函数

这里一定要仔细,不然后面编译不出来,很头疼,建议新建一个工作空间,也可以复习前面的知识
在这里插入图片描述
1.发布方

*/
int main(int argc, char * argv[])
{
    //1中文防止乱码
    setlocale(LC_ALL,"");
    //2初始化ROS节点
    ros::init(argc,argv,"banzhuren");
    //3创建节点句柄
    ros::NodeHandle nh;
    //4编写发布者对象 pub 话题叫做liaotian
    ros::Publisher pub =nh.advertise<plumbing_pubsub::person>("liaotian",10);
    //5编写发布逻辑,发布数据
    plumbing_pubsub::person pub1;//设置了一个pub1的对象,在plumbing_pubsub 域内
    pub1.name="张三";
    pub1.age=19;
    pub1.height=180;

    //有被发布的数据
    //发布频率
    ros::Rate rate(1);
    //循环发布数据
    while (ros::ok())
    {
        //修改被发布的数据
        pub1.age+=1;
        pub.publish(pub1);
        //休眠
        rate.sleep();
        //回头函数
        ros::spinOnce();
    }
    return 0;
}

编译后
在这里插入图片描述
2.订阅方

新建一个文件:demo04_sub_person.cpp

//包含头文件
#include "ros/ros.h"
#include "demo02/Person.h"
void doPerson(const demo02::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,"listener");
    ros::NodeHandle nh;
    ros::Subscriber sub=nh.subscribe("chatter_person",1000,doPerson);
    ros::spin();
    return 0;
}
 

修改Cmakelists 文件,一共要修改 3 处
在这里插入图片描述
这次出现的问题:ROSRUN 报未预期的符号‘(‘附近有语法错误

解决:
例如 rosrun pub_sub_test sub_string.cpp(错误) 节点名后去掉.cpp
血的教训,希望避免,ROSRUN cpp不要加上去!

rosrun pub_sub_test sub_string.py (正确)

最终输出如下所示
在这里插入图片描述
在这里插入图片描述使用rqt_graph 计算图查看节点关系
在这里插入图片描述
至此,使用C++完成话题通信的自定义信息发布与订阅完成!

2.1.6 话题通信自定义msg调用B(Python)

需求:

编写发布订阅实现,要求发布方以1HZ(每秒1次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。

分析:

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

发布方
接收方
数据(此处为自定义消息)

流程:

编写发布方实现;
编写订阅方实现;
为python文件添加可执行权限;
编辑配置文件;
编译并执行。

首先是完成setting.json的配置
在devel/lib/python2.7 复制路径,添加到.json文件夹中,这一步是为了后面编写python程序得到提示

在这里插入图片描述

"/home/zhangyuanbo/somo02/devel/lib/python2.7/dist-packages"

{
    "python.autoComplete.extraPaths": [
        "/opt/ros/noetic/lib/python3/dist-packages",
        "/xxx/yyy工作空间/devel/lib/python3/dist-packages"
    ]
}
1.发布方(注意中文字符问题,解释器错误)
#! /usr/bin/env python
#coding=utf-8
from audioop import ratecv
import rospy
from demo02.msg import Person
# 发布方实现:
# 发布人的消息
#  1. 导包
#  2. 初始化ros节点
#  3. 创建发布者对象
#  4. 组织发布逻辑并且发布数据
if __name__=="__main__":
    rospy.init_node("dama")
    pub=rospy.Publisher("liaotian",Person,queue_size=10)
    # 创建person数据
    p = Person()
    
    p.name="aotoman"
    p.age=8
    p.height=1.85
    # 创建一个rate对象
    rate=rospy.Rate(1)#1秒钟1次
    # 循环发布数据
    while not rospy.is_shutdown():
        pub.publish(p)
        rospy.loginfo("发布的消息:%s,%d,%.2f",p.name,p.age,p.height)
        rate.sleep()

设定权限
在这里插入图片描述
修改CMakeLists

在这里插入图片描述

最后结果,实验完成

在这里插入图片描述
2 订阅方实现

#! /usr/bin/env python
#coding=utf-8
import rospy
from demo02.msg import Person
# 订阅者实现:
# 订阅者的消息
#  1. 导包
#  2. 初始化ros节点
#  3. 创建订阅者对象
#  4. 通过回调函数处理订阅者数据
#  5. spin()
def doPerson(p):
    rospy.loginfo("小伙子的数据:%s,%d,%.2f",p.name,p.age,p.height)
if __name__=="__main__":
    rospy.init_node("daye")
    sub=rospy.Subscriber("liaotian",Person,doPerson,queue_size=10)
    rospy.spin()
添加CMakelists文件

实现实验

在这里插入图片描述

查看计算图
在这里插入图片描述
总结:

话题通信是以发布订阅的方式实现不同节点之间数据交互的通信模式。

至此话题2.1通信结束

2.2 服务通信

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

机器人巡逻过程中,控制系统分析传感器数据发现可疑物体或人... 此时需要拍摄照片并留存。

在上述场景中,就使用到了服务通信。

一个节点需要向相机节点发送拍照请求,相机节点处理请求,并返回处理结果

与上述应用类似的,服务通信更适用于对实时性有要求、具有一定逻辑处理的应用场景。

概念

以请求响应的方式实现不同节点之间数据交互的通信模式。

作用

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

案例

实现两个数字的求和,客户端节点,运行会向服务器发送两个数字,服务器端节点接收两个数字求和并将结果响应回客户端。

相当于上网输入URL,获得网页反馈的数据

2.2.1 服务通信理论模型

在这里插入图片描述角色:

master    			管理者  (114平台)
Server/talker       服务端	(服务公司)
Client/listener     客户端	(我)

具体实现流程:

master 会根据话题实现 Server 和 Client 的连接

0步	保洁公司在114平台注册自身信息,提交地址
第1步   我需要访问114平台,注册自己想要的服务(疏通下水道)
第2114平台进行话题匹配并将服务端的连接方式响应给客户端(电话号码)
第3步   我打电话给保洁公司
第4步   保洁公司说可以

注意事项:

1 必须保证顺序,客户端发起请求时,得保证服务端已经启动
2 服务端和客户端可以不只有一个(需要通下水道的有多个,提供服务的也有多个)

关注点:

1 流程已经被封装了,直接调用就可
2 话题
3 服务端
4 客户端
5 数据载体(实现服务通信,可能需要自定义数据载体)
6 话题一致

2.2.2 服务通信自定义srv

需求:

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

流程:

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

1按照固定格式创建srv文件

2编辑配置文件

3编译生成中间文件

srv=请求+响应

自定义服务消息

实践:
1.首先创建新的功能包,并且在这个功能包下创建新的文件夹srv,创建类似于msg的文件AddInts.srv
在这里插入图片描述

int32 num1
int32 num2
---
int32 sum

三条杠用于区分srv文件中的请求和响应

2 编辑配置文件
package.xml
在这里插入图片描述

CMakeList

srv虽然是自定义的,但是里面用到的数据都是基本类型的,需要依赖std_msgs

在这里插入图片描述

注意: 官网没有在 catkin_package 中配置 message_runtime,经测试配置也可以

find package 是当前功能包所依赖的功能包 catkin package 是依赖包所依赖的其他功能包!!!!

编译成功就没什么问题

在这里插入图片描述

查看中间文件
C++
在这里插入图片描述
Python
在这里插入图片描述

2.2.3 服务通信自定义srv调用A(C++)

需求:

编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。

分析:

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

1服务端
2客户端
3数据

流程:

编写服务端实现;
配置文件,测试;
编写客户端实现;
编辑配置文件;
编译并执行。

1 首先配置vscode,防止抛出异常

添加路径

"/home/zhangyuanbo/somo02/devel/include/**"

2 服务端C++程序如下
在src下添加cpp文件(所有要注意的事项都在文件的注释里)

#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"

// 服务端实现:解析客户端提交的数据运算并产生响应
// 1.包含一些相关的头文件 头文件名字和srv下的文件名字一致
// 2.初始化ros节点
// 3.创建节点句柄
// 4.创建服务对象
// 5.处理请求并产生响应
// 6.spin() 
bool doNums(plumbing_server_client::AddInts::Request &request,plumbing_server_client::AddInts::Response &responce)
//处理成功或者处理失败,所以返回的是布尔类型的值;传入引用请求和引用响应
{
    //a处理请求
    int num1=request.num1;
    int num2=request.num2;
    ROS_INFO("受到的请求数据:num1=%d,num2=%d",num1,num2);
    //b组织响应
    int sum=num1+num2;
    responce.sum=sum;
    ROS_INFO("求得的结果是:sum=%d",sum);

    return true;
}
int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化ros节点
    ros::init(argc,argv,"heishui");//节点名字需要保证唯一
    // 3.创建节点句柄
    ros::NodeHandle nh;
    // 4.创建服务对象
    ros::ServiceServer server=nh.advertiseService("addInts",doNums);//有回调函数,泛型可以自动推导,所以无需泛型
    ROS_INFO("服务器端启动");
    // 6.spin() 
    ros::spin();
    return 0;
}
}

3 修改CMakeLists
(注意参数2 项目名_gencpp)

实验结果

使用rosservice call addInts addInts 为话题的名字
空一格后用双tab补齐发现出现num1,num2
修改num1,2的值 回车后发现出现了sum,因为之前在程序中编写了日志输出,所以在服务器端出现了结果30

结果如下图所示

4 客户端实现

在src下新建cpp文件,这里不多说了,后面也不会说了!
如果看了我之前的笔记+实践应该能得心应手了

#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"

//客户端提交俩个数据,并处理响应的结果

// 1.包含一些相关的头文件 头文件名字和srv下的文件名字一致
// 2.初始化ros节点
// 3.创建节点句柄
// 4.创建客户端对象
// 5.提交请求并处理响应
//没有回调函数那么spin可有可无 主动发起不需要回调 并不是和server一样需要出发
//有回调函数的不用写泛型
int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    // 2.初始化ros节点
    ros::init(argc,argv,"dabao");
    // 3.创建节点句柄
    ros::NodeHandle nh;
    // 4.创建客户端对象
    ros::ServiceClient client= nh.serviceClient<plumbing_server_client::AddInts>("addInts");
    // 5.提交请求并处理响应
    // 5.1 组织请求
    plumbing_server_client::AddInts ai;
    ai.request.num1=100;
    ai.request.num2=200;
    // 5.2 处理响应
    bool flag =client.call(ai);
    if(flag)
    {
        ROS_INFO("响应成功!");
        //获取结果
        ROS_INFO("响应结果=%d",ai.response.sum);
    }else
    {
        ROS_INFO("响应失败!");
    }

    return 0;
}

这里一直在vscode界面内无法实现函数重载的完整展示,不知道老师是怎么做的,这个问题先放在这,后面来研究下

5 调整配置文件
在这里插入图片描述
6执行
结果如下所示(先启动服务端,在启动客户端)

在这里插入图片描述
7 优化
实现参数的动态提交
需要修改client 中的提交方式:格式:rosrun xxxx xxxxxx 12 34
其中:argc代表传入参数的总数量,第一个参数是程序名,第二个参数是12,第三个参数是34,所以argc是3

#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"

//客户端提交俩个数据,并处理响应的结果

// 1.包含一些相关的头文件 头文件名字和srv下的文件名字一致
// 2.初始化ros节点
// 3.创建节点句柄
// 4.创建客户端对象
// 5.提交请求并处理响应
//没有回调函数那么spin可有可无 主动发起不需要回调 并不是和server一样需要出发



//实现参数的动态提交
// 1.格式:rosrun xxxx   xxxxxx  12 34
// 2.节点执行时需要获取命令中的参数并组织进request

//argc代表传入参数的总数量,第一个参数是程序名,第二个参数是12,第三个参数是34,所以argc是3
int main(int argc,char *argv[])
{
     setlocale(LC_ALL,"");
    // 优化实现,获取命令中的参数
    if(argc!=3)
    {
        ROS_INFO("提交参数的个数不对");
        return 1;
    }   
    
    // 2.初始化ros节点
    ros::init(argc,argv,"dabao");
    // 3.创建节点句柄
    ros::NodeHandle nh;
    // 4.创建客户端对象
    ros::ServiceClient client= nh.serviceClient<plumbing_server_client::AddInts>("addInts");
    // 5.提交请求并处理响应
    // 5.1 组织请求
    plumbing_server_client::AddInts ai;
    ai.request.num1=atoi(argv[1]);//变为整形值
    ai.request.num2=atoi(argv[2]);//变为整形值
    // 5.2 处理响应
    bool flag =client.call(ai);
    if(flag)
    {
        ROS_INFO("响应成功!");
        //获取结果
        ROS_INFO("响应结果=%d",ai.response.sum);
    }else
    {
        ROS_INFO("响应失败!");
    }

    return 0;
}

8 调试

在这里插入图片描述在这里插入图片描述
9 注意事项

问题:如果先启动客户端,会报错

需求:如果县启动客户端,不能直接抛出异常,而是挂起,等服务器启动后,再正常请求响应

解决:ros中内置了相关函数,这些函数可以让客户端启动后,挂起,等待服务器启动
方法1: client.waitForExistence();

在这里插入图片描述
先启动客户端

在这里插入图片描述
实现成功
在这里插入图片描述
方法2:

 ros::service::waitForService("addInts"); //传入话题名字

在这里插入图片描述
结果类似

在这里插入图片描述

至此用C++完成了服务通信的工作

2.2.4 服务通信自定义srv调用B(Python)

需求:

编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。

分析:

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

服务端
客户端
数据

流程:

编写服务端实现;
编写客户端实现;
为python文件添加可执行权限;
编辑配置文件;
编译并执行。

0 vscode setting.json配置,防止代码抛出异常

{
    "python.autoComplete.extraPaths": [
        "/opt/ros/noetic/lib/python3/dist-packages",
    ]
}

1 服务端实现
在这里插入图片描述

看到addInts.py文件中的 addIntsRequest 以及addIntsResponse两个类
后面会调用

新建scripts 目录新建.py文件
(这些步骤后面会省略不说了,如果无法掌握建议看我之前的笔记)

代码如下

#! /usr/bin/env python
#coding=utf-8
import rospy
#from plumbing_server_client.srv import AddInts,AddIntsRequest,AddIntsResponse
#也可以直接替换成*
from plumbing_server_client.srv import *
# 服务端解析客户端请求,产生响应
# 具体流程:
#     1 导包;_srv 两种方式都能成功
#     2 初始化ros节点
#     3 创建服务端对象
#     4 处理请求,产生响应
#     5 处理逻辑(回调函数)
#     6 spin()

#回调函数参数:封装了请求request数据
#返回值:响应数据response
def doNum(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__":
#     2 初始化ros节点
    rospy.init_node("heishui")
#     3 创建服务端对象
    server=rospy.Service("addInts",AddInts,doNum)
    rospy.loginfo("服务器已经启动了!")
#     4 处理请求,产生响应
#     5 处理逻辑(回调函数)
#     6 spin()
    rospy.spin()
关于rospy.service()函数的理解:

需要添加的参数如下

(class) Service(name, service_class, handler, buff_size: int = DEFAULT_BUFF_SIZE, error_handler: Any | None = None)

name是话题名称
service_class是消息载体类型
handler是指向回调函数

修改CMakeLists
在这里插入图片描述

添加可执行权限
在这里插入图片描述
结果如下

在这里插入图片描述

调用一个仿真来看这一部分

rosservice call +话题名字 空一格+双tab

在这里插入图片描述
2 客户端实现

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

# 客户端 组织并提交请求,处理服务端响应
# 具体流程:
#     1 导包;_srv
#     2 初始化ros节点
#     3 创建客户端对象
#     4 组织请求的数据,并发送请求
#     5 处理响应
# 不需要spin 因为是主动提出

import rospy
from plumbing_server_client.srv import *

if __name__=="__main__":
#     2 初始化ros节点
    rospy.init_node("erhei")
#     3 创建客户端对象
    client=rospy.ServiceProxy("addInts",AddInts)
#     4 组织请求的数据,并发送请求
    response=client.call(12,34)
#     5 处理响应  
    rospy.loginfo("响应的数据:%d",response.sum)

修改CMakeLists
在这里插入图片描述
测试服务端和客户端的结合

在这里插入图片描述
令我有点不解的是ros对于c++和python所提供的接口名字为啥不一样哈哈

3 优化实现
修改client文件

运用sys下的argv数组

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

# 客户端 组织并提交请求,处理服务端响应
# 具体流程:
#     1 导包;_srv
#     2 初始化ros节点
#     3 创建客户端对象
#     4 组织请求的数据,并发送请求
#     5 处理响应

import rospy
from plumbing_server_client.srv import *
#优化动态传入
import sys

if __name__=="__main__":
#判断传入的参数是不是3个
    if len(sys.argv)!=3:
        rospy.logerr("传入的参数个数不对")
        sys.exit(1)
        
#     2 初始化ros节点
    rospy.init_node("erhei")
#     3 创建客户端对象
    client=rospy.ServiceProxy("addInts",AddInts)
#     4 组织请求的数据,并发送请求
#       解析传入的参数
    num1=int(sys.argv[1])
    num2=int(sys.argv[2])
    response=client.call(num1,num2)
#     5 处理响应  
    rospy.loginfo("响应的数据:%d",response.sum)

实现这一部分

在这里插入图片描述
4 注意事项

当客户端先启动时,会报错,所以要挂起,让其先等待
方法1:加入client.wait_for_service()
在这里插入图片描述
先启动客户端让其挂起,再启动服务器

在这里插入图片描述

方法2:rospy.wait_for_service("addInts")注意这种方法需要输入话题名字
加了一个客户端对象的rospy.loginfo

在这里插入图片描述
测试下

在这里插入图片描述

至此,服务通信的python实现完成

2.3 参数服务器

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

导航实现时,会进行路径规划,比如: 全局路径规划,设计一个从出发点到目标点的大致路径。本地路径规划,会根据当前路况生成时时的行进路径

上述场景中,全局路径规划和本地路径规划时,就会使用到参数服务器

路径规划时,需要参考小车的尺寸,我们可以将这些尺寸信息存储到参数服务器,全局路径规划节点与本地路径规划节点都可以从参数服务器中调用这些参数

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

概念

以共享的方式实现不同节点之间数据交互的通信模式。

作用

存储一些多节点共享的数据,类似于全局变量。

案例

实现参数增删改查操作。

2.3.1 参数服务器理论模型

参数服务器实现是最为简单的,该模型如下图所示,该模型中涉及到三个角色:

ROS Master (管理者)
Talker    (参数设置者)
Listener  (参数调用者)

ROS Master 作为一个公共容器保存参数,Talker 可以向容器中设置参数,Listener 可以获取参数。
1 管理者把Talker设置的参数放进一个参数列表
2 调用者向管理者发起请求调用参数
3 管理者在参数列表中查找,把参数列表中的值返回给调用者
在这里插入图片描述
整个流程由以下步骤实现:

1.Talker 设置参数

Talker 通过 RPC 向参数服务器发送参数(包括参数名与参数值),ROS Master 将参数保存到参数列表中。

2.Listener 获取参数

Listener 通过 RPC 向参数服务器发送参数查找请求,请求中包含要查找的参数名。

3.ROS Master 向 Listener 发送参数值

ROS Master 根据步骤2请求提供的参数名查找参数值,并将查询结果通过 RPC 发送给 Listener。

参数可使用数据类型:

32-bit integers (4个字节的int)

booleans

strings

doubles	(浮点类型)

iso8601 dates (时间类型时间数据,百度可查)

lists

base64-encoded binary data (以base64编码的二进制数据)

字典

注意:参数服务器不是为高性能而设计的,因此最好用于存储静态的非二进制的简单数据

2.3.2 参数操作A(C++)

需求:实现参数服务器参数的增删改查操作。

在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:

ros::NodeHandle

ros::param     (parameter)

1.参数服务器新增(修改)参数
创建新的包,src下编写

#include "ros/ros.h"

// 需要实现参数的新增与修改
// 需求: 首先设置机器人的共享参数,机器人的类型,机器人的半径
//     设置完毕后再次修改半径的参数
//     0.15-》0.2
// 实现:
//     通过两套api来实现
//     1 ros::NodeHandle
//         setParam()
//     2 ros::param
//         set()


int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    //初始化ros节点
    ros::init(argc,argv,"set_param_c");
    //创建ROS节点句柄
    ros::NodeHandle nh;
    //参数增----------------------------------------------------
    //方案1:nh
    nh.setParam("type","xiaohuang");
    nh.setParam("radius",0.15);
    //方案2: ros::param
    ros::param::set("type_param","xiaobai");
    ros::param::set("radius_param",0.15);
    //参数改----------------------------------------------------
    return 0;
}

roscore的时候发现参数中已经有一些已知的值了
在这里插入图片描述

这里不会出现什么
在这里插入图片描述
不过可以使用rosparam list 查看当前参数服务器所有内容

在这里插入图片描述

发现已经把两种方法的半径传入了

使用

rosparam get /type

查询参数后的信息

在这里插入图片描述
修改值代码:

#include "ros/ros.h"

// 需要实现参数的新增与修改
// 需求: 首先设置机器人的共享参数,机器人的类型,机器人的半径
//     设置完毕后再次修改半径的参数
//     0.15-》0.2
// 实现:
//     通过两套api来实现
//     1 ros::NodeHandle
//         setParam()
//     2 ros::param
//         set()


int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    //初始化ros节点
    ros::init(argc,argv,"set_param_c");
    //创建ROS节点句柄
    ros::NodeHandle nh;
    //参数增----------------------------------------------------
    //方案1:nh
    nh.setParam("type","xiaohuang");
    nh.setParam("radius",0.15);
    //方案2: ros::param
    ros::param::set("type_param","xiaobai");
    ros::param::set("radius_param",0.15);
    //参数改----------------------------------------------------
    //方案1:nh
    nh.setParam("radius",0.2);
    //方案2: ros::param
    ros::param::set("radius_param",0.25);
    return 0;
}

两种方法都是直接覆盖原来的值

两种方法的修改
setParam(“键”,值)
set(“键”,值)

在这里插入图片描述
2.使用C++参数服务器获取参数

在这里插入图片描述

问题描述

使用catkin_make编译一个功能包,第一次编译由于编译的cpp文件命名与camkefile中的不一致,所以编译失败了。修改相应的名字之后再次编译还是失败。显示如下报错:

*** 没有规则可制作目标"auto_alignment/CMakeFiles/alignment_+server.dir/build"。停止
recipe for target 'auto_alignment/CMakeFiles/alignment_+server.dir/all' failed
recipe for target 'all' failed

解决办法

这个是因为上次编译失败,导致编译环境部分缺失。
解决办法其实很简单。去到工作空间的build文件夹,找到相应的pkg包,然后删掉重新编译就好啦。

实验代码:

#include "ros/ros.h"

// 在该文件中实现参数的查询
// 实现:
//     通过两套api来实现
//     1 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存储搜索结果的变量
         
//     2 ros::param--------------------------------------------------
//        
int main(int argc,char* argv[])
{
    //设置编码
    setlocale(LC_ALL,"");
    //初始化ROS节点
    ros::init(argc,argv,"get_param_c");
    //创建节点句柄
    ros::NodeHandle nh;
    //1 param  查询,如果没有的话返回0.5
    double radius=nh.param("radius",0.5);
    ROS_INFO("radius=%.2f",radius);
    //2 getparam 
    double radius2=0.0;
    bool result= nh.getParam("radius",radius2);
    //3 getParamCached 结果与getparam类似,只是底层性能上会有提升,一般测试性能测试看不出来
    if(result)
    {
       ROS_INFO("获取的半径是:%.2f",radius2);
    }else
    {
        ROS_INFO("被查询的变量不存在");
    }
    //4.getParamNames
    std::vector<std::string> names;//制作names容器
    nh.getParamNames(names);
    for (auto &&name :names)//遍历
    {
        ROS_INFO("遍历到的元素:%s",name.c_str());
    }
    //5 hasParam
    bool flag1=nh.hasParam("radius");
    bool flag2=nh.hasParam("asdasdasd");
    ROS_INFO("radius存在吗? %d",flag1);//1
    ROS_INFO("asdsdsd存在吗? %d",flag2);//0

    //6 searchParam
    std::string key;
    nh.searchParam("radius",key);
    ROS_INFO("搜索结果:%s",key.c_str());

    //ros::param ------------------------------------------------------------------------------------
    double radius_param=ros::param::param("radius",100.5);
    ROS_INFO("radius_param=%.2f",radius_param);

    std::vector<std::string> names_param;
    ros::param::getParamNames(names_param);
    for(auto &&name :names_param)
    {
        ROS_INFO("键:%s",name.c_str());
    }
    return 0;
}

用实验1输入参数
实验2获取参数

param函数获取结果
结果如下图所示
存在,返回对应结果,否则返回默认值

在这里插入图片描述

如果是键不存在的情况,会返回0.5

getparam函数获取结果
存在,返回 true,且将值赋值给参数2
若果键不存在,那么返回值为 false,且不为参数2赋值

在这里插入图片描述

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

alt+上下键可以将当前行上下移动

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

结果如下,所有键都被遍历出来了,有些是自己设置的,有些不是

在这里插入图片描述

hasParam函数获取结果
(键)
是否包含某个键,存在返回 true (1),否则返回 false (0)
在这里插入图片描述

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

在这里插入图片描述

方法2:使用ros::param
使用ros::param::param()结果如下所示

在这里插入图片描述
使用ros::param::getParamNames()结果如下所示

在这里插入图片描述
其它请自己尝试这里就不过多介绍了

3 使用C++ 删参数

两套API各有一套函数
新建cpp文件
总代码如下所示:

#include "ros/ros.h"
//  演示参数删除:
//  实现:
//         ros::NodeHandle
//             delParam()
//         ros::param
//             del()
int main(int argc,char *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"param_del_c");
    ros::NodeHandle nh;


//删除: NodeHandle----------------------------------------------------------
    bool flag1=nh.deleteParam("radius");
    if(flag1)
    {
      ROS_INFO("删除成功");
    }else
    {
      ROS_INFO("删除失败");
    }
//删除 ros::param------------------------------------------------------------
    bool flag2=ros::param::del("radius_param");
     if(flag2)
    {
      ROS_INFO("radius_param删除成功");
    }else
    {
      ROS_INFO("radius_param删除失败");
    }
    return 0;
}

修改CMakelists
在这里插入图片描述

两边基本弄好了才能编译,不然会报错,编译完之后会有提示!

rosparam list查看当前所有参数

在这里插入图片描述
删除 radius 以及 radius_param

结果如下
运用nh 句柄删radius
在这里插入图片描述
运用ros::param删除 radius_param
在这里插入图片描述

至此使用c++完成参数服务器的增删改查完成

2.3.3 参数操作B(Python)

需求:实现参数服务器参数的增删改查操作。

1 增,改

#!  /usr/bin/env python
#coding=utf-8
# 演示参数的新增与修改
#     需求:在参数服务器中设置机器人的属性,型号,半径
#     实现:
#     rospy.set_param()
    
import rospy

if __name__=="__main__":
    rospy.init_node("param_set_p")
    
    #新增参数
    rospy.set_param("type_p","xiaohaungche")
    rospy.set_param("radius_p",0.15)
 
 
   #覆盖参数
    rospy.set_param("radius_p",0.3)
 

在这里插入图片描述
使用 rosparam get radius_p

在这里插入图片描述

修改参数,直接在程序里覆盖就可以了

在这里插入图片描述
2 查

完整代码

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

# 演示参数的查询
# 查询的相关实现比较多
        # get_param当键存在时,返回对应的值,不存在返回默认值
        
        # get_param_cached与上类似,只是效率高
        
        # get_param_names获取所有的参数的键的集合
        
        # has_param判断是否包含某个键
        
        # search_param查找某个键,并返回完整的键名

import rospy
if __name__=="__main__":
    rospy.init_node("get_param_p")
    
    #1. get_param
    radius=rospy.get_param("radius_p",0.5)
    radius2=rospy.get_param("radius_p_xxx",0.5)
    rospy.loginfo("radius=%.2f",radius)
    rospy.loginfo("radius2=%.2f",radius2)
    
    #2. # get_param_cached  效率比上面这个高
    radius3=rospy.get_param_cached("radius_p",0.5)
    radius4=rospy.get_param_cached("radius_p_xxx",0.5)
    rospy.loginfo("radius3=%.2f",radius3)
    rospy.loginfo("radius4=%.2f",radius4)
    
    #3.get_param_names
    names= rospy.get_param_names()
    for name in names:#遍历
        rospy.loginfo("name=%s",name)
        
    #4. has_param
    flag1=rospy.has_param("radius_p")
    if flag1:
        rospy.loginfo("radius_p 存在")
    else:
        rospy.loginfo("radius_p 不存在")
    flag2=rospy.has_param("radius_pxx")
    if flag2:
        rospy.loginfo("radius_pxx 存在")
    else:
        rospy.loginfo("radius_pxx 不存在")
        
    #5. search_param 查找是否存在,存在返回键名
    key=rospy.search_param("radius_p")
    rospy.loginfo("k=%s",key)
3 删

代码如下

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

if __name__=="__main__":
    rospy.init_node("del_param_p")
    #使用try 捕获一下异常
    try:
        #删除参数
        rospy.delete_param("radius_p")
    except Exception as e:
        rospy.loginfo("被删除的参数不存在")

    到这里运用Python完成了参数服务器中参数的增删改查

    2.4 常用命令

    机器人系统中启动的节点少则几个,多则十几个、几十个,不同的节点名称各异,通信时使用话题、服务、消息、参数等等都各不相同,一个显而易见的问题是: 当需要自定义节点和其他某个已经存在的节点通信时,如何获取对方的话题、以及消息载体的格式呢?

    在 ROS 同提供了一些实用的命令行工具,可以用于获取不同节点的各类信息,常用的命令如下:

    rosnode :  操作节点
    rostopic : 操作话题
    rosservice : 操作服务
    rosmsg : 操作msg消息
    rossrv : 操作srv消息
    rosparam : 操作参数

    作用

    和之前介绍的文件系统操作命令比较,文件操作命令是静态的,操作的是磁盘上的文件,而上述命令是动态的,在ROS程序启动后,可以动态的获取运行中的节点或参数的相关信息。且可以用于测试

    2.4.1 rosnode

    rosnode 是用于获取节点信息的命令

    rosnode ping
    
    测试到节点的连接状态
    
    rosnode list
    
    列出活动节点
    
    rosnode info
    
    打印节点信息
    
    rosnode machine
    
    列出指定设备上的节点
    
    rosnode kill
    
    杀死某个节点
    
    rosnode cleanup
    
    清除无用节点,启动乌龟节点,然后 ctrl + c 关闭,该节点并没被彻底清除,可以使用 cleanup 清除节点
      rosnode

    在这里插入图片描述

    rosnodelist

    在这里插入图片描述

    rosnode ping

    在这里插入图片描述

    rosnode info +节点名字

    在这里插入图片描述

    rosnode machine +主机名字

    在这里插入图片描述

    rosnode kill +节点

    在这里插入图片描述
    在这里插入图片描述

    rosnode cleanup

    启动小海龟
    在这里插入图片描述
    关闭小海龟
    发现rosnode list中小海龟还在,说明这是一个僵尸节点
    使用rosnode cleanup 完成对僵尸节点的清除

    在这里插入图片描述

    2.4.2 rostopic

    rostopic包含rostopic命令行工具,用于显示有关ROS 主题的调试信息,包括发布者,订阅者,发布频率和ROS消息。它还包含一个实验性Python库,用于动态获取有关主题的信息并与之交互。

    rostopic bw     显示主题使用的带宽
    rostopic delay  显示带有 header 的主题延迟
    rostopic echo   打印消息到屏幕  (自定义消息需要先到工作空间下)
    rostopic find   根据类型查找主题
    rostopic hz     显示主题的发布频率
    rostopic info   显示主题相关信息
    rostopic list   显示所有活动状态下的主题
    rostopic pub    将数据发布到主题
    rostopic type   打印主题类型

    显示所有活动状态下的主题

    在这里插入图片描述

    打印消息到屏幕

    在这里插入图片描述

    发布话题

    在这里插入图片描述

    以一定的频率发布话题

    在这里插入图片描述

    在这里插入图片描述

    显示主题相关信息

    在这里插入图片描述

    显示主题的发布频率

    在这里插入图片描述

    在这里插入图片描述

    2.4.3 rosservice

    rosservice包含用于列出和查询ROSServices的rosservice命令行工具。

    调用部分服务时,如果对相关工作空间没有配置 path,需要进入工作空间调用 source ./devel/setup.bash

    rosservice args    打印服务参数
    rosservice call    使用提供的参数调用服务
    rosservice find    按照服务类型查找服务
    rosservice info    打印有关服务的信息
    rosservice list    列出所有活动的服务
    rosservice type    打印服务类型
    rosservice uri     打印服务的 ROSRPC uri

    在这里插入图片描述

    rosservice call 使用提供的参数调用服务

    在这里插入图片描述
    在这里插入图片描述

    rosservice info 打印有关服务的信息

     节点名字 路径 服务消息类型 请求参数

    在这里插入图片描述

    rosservice type 打印服务类型

    在这里插入图片描述

    rosservice args 打印服务参数

    rosservice args /spawn
    x y theta name

    2.4.4 rosmsg

    rosmsg是用于显示有关 ROS消息类型的 信息的命令行工具。

    rosmsg 演示

    rosmsg show    		 显示消息描述
    rosmsg info    		 显示消息信息
    rosmsg list   		 列出所有消息
    rosmsg md5   		 显示 md5 加密后的消息
    rosmsg package     显示某个功能包下的所有消息
    rosmsg packages    列出包含消息的功能包

    rosmsg list 列出所有消息

    在这里插入图片描述
    rosmsg list | grep -i person 筛选

    在这里插入图片描述

    rosmsg show 显示消息描述

    在这里插入图片描述

    rosmsg info 显示消息信息

    在这里插入图片描述

    rosmsg md5 显示 md5 加密后的消息

    rosmsg packages

    列出包含消息的所有包

    rosmsg package

    列出某个包下的所有msg

    //rosmsg package 包名 
    rosmsg package turtlesim

    2.4.5 rossrv

    rossrv是用于显示有关ROS服务类型的信息的命令行工具,与 rosmsg 使用语法高度雷同。

    rossrv show       显示服务消息详情
    rossrv info       显示服务消息相关信息
    rossrv list       列出所有服务信息
    rossrv md5        显示 md5 加密后的服务消息
    rossrv package    显示某个包下所有服务消息
    rossrv packages   显示包含服务消息的所有包

    在这里插入图片描述
    rossrv list 列出所有服务信息

    在这里插入图片描述

    筛选

    在这里插入图片描述

    rossvr info

    在这里插入图片描述

    rossrv show 显示服务消息详情

    在这里插入图片描述

    2.4.6 rosparam

    rosparam包含rosparam命令行工具,用于使用YAML编码文件在参数服务器上获取和设置ROS参数。

    rosparam set     设置参数
    rosparam get     获取参数
    rosparam load    从外部文件加载参数
    rosparam dump    将参数写出到外部文件
    rosparam delete  删除参数
    rosparam list    列出所有参数

      在这里插入图片描述

      rosparam list    列出所有参数

      在这里插入图片描述

      rosparam get     获取参数

      在这里插入图片描述

      rosparam dump params.yaml	将参数写出到外部文件

      在这里插入图片描述

      重新启动roscore会发现部分参数没有了(被释放了)

      rosparam load(先准备 yaml 文件)

      从外部文件加载参数

      rosparam load xxx.yaml

      在这里插入图片描述

      至此,几个常用命令介绍结束