机器人是一种高度复杂的系统性实现,在机器人上可能集成各种传感器(雷达、摄像头、GPS…)以及运动控制实现,为了解耦合,在ROS中每一个功能点都是一个单独的进程,每一个进程都是独立运行的。更确切的讲,ROS是进程(也称为Nodes)的分布式框架。
ROS 中的基本通信机制主要有如下三种实现策略:
- 话题通信(发布订阅模式)
- 服务通信(请求响应模式)
- 参数服务器(参数共享模式)
一、话题通信

1、概念
以发布订阅的方式实现不同节点之间数据交互的通信模式
2、话题通信理论模型
话题通信流程

对于话题通信的流可以理解为,起初tallker在master处注册自身信息以及自身的rpc地址,接着当listener在master处注册自身信息时,master比对已注册的tallker信息,对于匹配复合的tallker信息,将该tallker的rpc地址发送给listener,listener根据rpc地址链接tallker地址,tallker并把自身tcp地址传给listener,listener根据tcp地址连接tallker,连接成功后tallker即可与listener发送信息。
3、话题通信基本操作(C++)
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
- 发布方
- 接收方
- 数据(此处为普通文本)
流程:
- 编写发布方实现;
- 编写订阅方实现;
- 编辑配置文件;
- 编译并执行。
(1)编写发布方实现
1、首先打开vs,找到自己的工作目录,新建功能包,命名为plumbing_pub_sub,回车后输入依赖roscpp rospy std_msgs回车
2、这样工作空间就创建完成了,找到创建好的文件下的src目录,新建文件demo01_pub.cpp回车
3、编写c++文件
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
发布实现方式:
1、包含头文件;
2、初始化RSO节点;
3、创建节点句柄;
4、创建发布者对象;
5、编写发布逻辑并发布数据。
*/
int main(int argc, char *argv[])
{
// 2、初始化RSO节点;
ros::init(argc,argv,"erGouZi");
// 3、创建节点句柄;
ros::NodeHandle nh;
// 4、创建发布者对象;
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10);
// 5、编写发布逻辑并发布数据。
//先创建被发布的消息
std_msgs::String msg;
//编写循环,循环中发布数据
while (ros::ok())
{
msg.data = "hello";
pub.publish(msg);
}
return 0;
}
ctrl+shift+b编译运行查看是否报错
4、修改CMakeList.txt文件

ctrl+shift+b编译运行查看是否报错
5、打开三个终端
第一个终端输入roscore,第二个终端输入
cd demo02_ws/
source ./devel/setup.bash
rosrun plumbing_pub_sub demo01_pub
第三个终端输入
rostopic echo fang

6、发送数据添加编号
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "sstream"
/*
发布实现方式:
1、包含头文件;
2、初始化RSO节点;
3、创建节点句柄;
4、创建发布者对象;
5、编写发布逻辑并发布数据。
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
// 2、初始化RSO节点;
ros::init(argc,argv,"erGouZi");
// 3、创建节点句柄;
ros::NodeHandle nh;
// 4、创建发布者对象;
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10);
// 5、编写发布逻辑并发布数据。
//要求以10hz的频率发布数据,并且文本后添加编号
//先创建被发布的消息
std_msgs::String msg;
//发布频率
ros::Rate rate(10);
//设置编号
int count = 0;
//编写循环,循环中发布数据
while (ros::ok())
{
count++;
//实现字符串拼接数字
std::stringstream ss;
ss << "hello ---> " << count;
// msg.data = "hello";
msg.data = ss.str();
pub.publish(msg);
//添加日在
ROS_INFO("发布的数据是:%s",ss.str().c_str());
rate.sleep();
}
return 0;
}

(2)编写订阅方实现
开头步骤与前面一致,创建c++文件demo02_sub.cpp,输入代码
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "sstream"
/*
发布订阅方式:
1、包含头文件;
2、初始化RSO节点;
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[])
{
setlocale(LC_ALL,"");
// 2、初始化RSO节点;
ros::init(argc,argv,"cuiHua");
// 3、创建节点句柄;
ros::NodeHandle nh;
// 4、创建订阅在对象;
ros::Subscriber sub = nh.subscribe("fang",10,doMsg);
// 5、处理订阅到的数据。
ros::spin();
return 0;
}

5、打开三个终端
第一个终端输入roscore,第二个终端输入
cd demo02_ws/
source ./devel/setup.bash
rosrun plumbing_pub_sub demo01_pub
第三个终端输入
cd demo02_ws/
source ./devel/setup.bash
rosrun plumbing_pub_sub demo02_sub

(3)计算图

4、话题通信基本操作(python)
流程:
- 编写发布方实现;
- 编写订阅方实现;
- 为python文件添加可执行权限;
- 编辑配置文件;
- 编译并执行。
(1)编写发布方实现
1、新建plumbing_pub_sub文件夹
2、新建demo01_pub_p.py文件
3、编写代码
#! /usr/bin/env python
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)
4、给plumbing_pub_sub文件夹权限,打开该文件夹终端输入命令
chmod +x *.py
5、修改CMakeList.txt文件

6、编译ctrl+shift+b
打开三个终端
一个
roscore
另一个
source ./devel/setup.bash
最后一个
rostopic echo che

7、完善后的代码
#! /usr/bin/env python
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()

(2)编写订阅方实现
步骤与上述相同
新建文件demo02_sub_p.py,输入代码
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
def doMsg(msg):
rospy.loginfo("我订阅的数据:%s",msg.data)
if __name__ == "__main__":
rospy.init_node("huaHua")
sub = rospy.Subscriber("che",String,doMsg,queue_size=10)
rospy.spin()
修改文件

编译运行

(3)计算图

补充
解耦合,对于不同编译语言程序可以互通。


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

1.定义msg文件
功能包下新建 msg 目录,添加文件 Person.msg,输入内容
string name
uint32 age
float32 height
2.编译package.xml文件
新增两条代码
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

2、编译CMakeList.txt




3、编译
ctrl+shift+b编译后

6、话题通信自定义msg(c++)
流程:
- 编写发布方实现;
- 编写订阅方实现;
- 编辑配置文件;
- 编译并执行。
0、vscode配置
为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性:
找到工作空间下的devel文件夹,右键集成终端打开,输入pwd,获得路径,复制,打开.vscode文件夹下的c_cpp_properties.json文件,添加该路径

1、编写发布方实现
#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 = 1.73;
ros::Rate rate(1);
while(ros::ok()){
person.age =+1;
pub.publish(person);
rate.sleep();
ros::spinOnce();
}
return 0;
}
修改配置文件

ctrl+shift+b编译一下是否通过。

完善代码
#include "ros/ros.h"
#include "plumbing_pub_sub/person.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ROS_INFO("这是消息的发布方");
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 = 1.73;
ros::Rate rate(1);
while(ros::ok()){
person.age =+1;
pub.publish(person);
ROS_INFO("发布的消息:%s,%d,%2f",person.name.c_str(),person.age,person.height);
rate.sleep();
ros::spinOnce();
}
return 0;
}
2、编写订阅方实现
#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[])
{
/* code */
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;
}


3、计算图

7、话题通信自定义msg(python)
流程:
- 编写发布方实现;
- 编写订阅方实现;
- 为python文件添加可执行权限;
- 编辑配置文件;
- 编译并执行。
0、vscode配置
找到工作目录下的devel文件夹,接着找到lib文件夹,对于lib文件夹下的python3文件夹打开集成终端,输入pwd,复制路径,打开.vscode文件夹下的settings.jsan,添加路径

1、编写发布方实现
#! /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 = "奥特曼"
p.age = 8
p.height = 1.85
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()
pass



2、编写订阅方实现
#! /usr/bin/env python
import rospy
from plumbing_pub_sub.msg import person
def doperson(p):
rospy.loginfo("小伙子的数据:%s,%d,%.2f",p.name,p.age,p.height)
if __name__ == "__main__":
rospy.init_node("daYe")
sub = rospy.Subscriber("jiaoSheTou",person,doperson)
rospy.spin()



3、计算图

二、服务通信
1、概念
服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景:
机器人巡逻过程中,控制系统分析传感器数据发现可疑物体或人... 此时需要拍摄照片并留存。
在上述场景中,就使用到了服务通信。
- 一个节点需要向相机节点发送拍照请求,相机节点处理请求,并返回处理结果
与上述应用类似的,服务通信更适用于对时时性有要求、具有一定逻辑处理的应用场景。
概念
以请求响应的方式实现不同节点之间数据交互的通信模式。
作用
用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输场景。
参考资料
038话题通信_理论模型_Chapter2-ROS通信机制_哔哩哔哩_bilibili

3264

被折叠的 条评论
为什么被折叠?



