前言
本文记录ROS话题通信的学习过程,便于后续复习。首先明确,ROS中的话题通信,在ROS通信中非常重要,实现了分布式发布接收消息,也是实现了不同编程语言间的解耦,下面记录下自己学习过程中的相关代码和配置。
学习环境:VSCode Ubuntu18.04
主要分为话题订阅和发布标准信息和自定义信息两个部分,每个部分会记录下C++和Python的实现
1 标准信息格式下的话题通信
1.1 C++版
发布者逻辑:
1 引入头文件
2 初始化ROS节点
3 定义句柄
4 创建发布者对象
5 定义发布的消息及消息发布的一些设置
发布者代码:
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char *argv[]){
//控制中文不乱码
setlocale(LC_ALL,"");
//1 初始化ros节点
ros::init(argc,argv,"publisher");
//2 创建句柄
ros::NodeHandle nh;
//3 创建发布者对象
ros::Publisher pub = nh.advertise<std_msgs::String>("fang",10);
//4 编写发布逻辑并发布数据
// 创建被发布的信息
std_msgs::String msg;
//定义频率
ros::Rate rate(10);
//设置编号
int count=0;
//可让发送数据前 休眠3秒 当作是注册到roscore的时间
//这样可保证订阅者 可 接收到前几个信息
ros::Duration(3).sleep();
//循环发布
while(ros::ok()){
count++;
std::stringstream ss;
ss << "hello ---> " << count;
msg.data = ss.str();
pub.publish(msg);
//添加日志
ROS_INFO("发布的数据为: %s",ss.str().c_str());
rate.sleep();
//官方建议 主要用于调用回调函数
ros::spinOnce();
}
return 0;
}
订阅者逻辑:
1 引入头文件
2 初始化ROS节点
3 创建订阅者对象及消息回调函数
订阅者代码:
#include "ros/ros.h"
#include "std_msgs/String.h"
void doMsg(const std_msgs::String::ConstPtr &msg){
ROS_INFO("subcribe msg: %s", msg->data.c_str());
}
int main(int argc, char *argv[]){
setlocale(LC_ALL,"");
//1 初始化ros节点
ros::init(argc,argv,"subcribe");
//2 创建句柄
ros::NodeHandle nh;
//3 创建发布者对象
ros::Subscriber sub = nh.subscribe("fang",10,doMsg);
//为了处理上面的回调函数
ros::spin();
return 0;
}
相关配置和调用测试
1)整个项目的整体配置,这里先设置 tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label" : " catkin_make: debug", //代表提示的描述性信息
"type": "shell",
"command": "catkin_make",
"args": [],
//可以使用ctrl+shift+B进行编译项目
"group": {"kind": "build", "isDefault": true},
"presentation":{
"reveal":"always"//可选silence和always代表输出信息
},
"problemMatcher": "$msCompile"
/*
"type": "catkin_make",
"args": [
"--directory",
"此处为项目目录/Documents/learn/ros/ros_ws_demo1",
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
],
"problemMatcher": [
"$catkin-gcc"
],
"group": "build",
"label": "catkin_make: build"*/
}
]
}
2)首先看下文件夹的构成,及根目录为ROS_WS_DEMO,话题通信的代码在plumbing_pub_sub文件夹下(功能包):
上方每个文件夹的含义:
include:包含一些头文件和编译后的信息
msg:自定义的消息类型文件夹
scripts:Python 脚本文件夹
src:C++源代码文件夹
CMakeList.txt:项目执行配置文件
package.xml:一些依赖库文件
3)写好的代码需要在文件夹下的 CMakeList.txt进行配置
配置好后,其实就可以进行编译我们写好的项目了,如果您配置了项目根目录下的tasks.json,直接进行 ctrl+shift+B即可进行编译。如果没出错:
4)运行过程
4.1 首先打开一个终端,输入下面的命令,启动主节点:
roscore
4.2 然后打开另一个新终端,分别输入下方的命令,即可得到发布 订阅节点
#激活环境配置
source ./devel/setup.bash
#运行节点
rosrun plumbing_pub_sub sub_node
4.3 然后打开另一个新终端,分别输入下方的命令,即可得到发布 消息发布节点
#激活环境配置
source ./devel/setup.bash
#运行节点
rosrun plumbing_pub_sub pub_node
4.4 如果想看下话题的发布情况,也可以重新打开一个终端,输入
rqt_graph#可对当前发布的话题进行可视乎
1.2 Python版
在项目下面新建 scripts 文件夹,用来存储py的脚本
发布者代码:
#! /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) # che是话题名称,类型是String,消息队列大小为10
# 4.编写发布逻辑并发布数据
# 4.1 创建数据类型
msg = String()
# 4.2 指定发布频率
rate = rospy.Rate(1)
# 4.3 设置计数器
count = 0
# 休眠3s,完成在master下面的注册
rospy.sleep(3)
# 4.4 循环发布数据
while not rospy.is_shutdown(): # 判断当前节点是否已经关闭,没有关闭则发布数据
count += 1 # 如果要将count追加到hello后面,则需要将其变为字符串
msg.data = "hello" + str(count)
# 4.5 发布数据
pub.publish(msg)
# 4.6 添加日志输出
rospy.loginfo("发布的数据:%s",msg.data)
rate.sleep()
订阅者代码:
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
"""
订阅实现流程:
1.导包
2.初始化ROS节点
3.创建订阅者对象
4.回调函数处理数据
5.spin()
"""
def doMsg(msg): #将订阅到的数据传进来
rospy.loginfo("订阅的数据:%s",msg.data) #data是数据
if __name__ == "__main__":
# 2.初始化ROS节点
rospy.init_node("huaHua")
# 3.创建订阅者对象
sub = rospy.Subscriber("fang",String,doMsg,queue_size = 10)
# sub = rospy.Subscriber("fang",String,doMsg,queue_size = 10) 跨语言通信
# 4.回调函数处理数据
# 5.spin()
rospy.spin()
在Scripts下面编写的Python文件需要增加可执行权限,打开终端,切换到Scripts文件夹下,然后运行命令:
chmod +x *.py#让python文件增加可执行权限
配置过程
在CMakeList.txt中:
记得编译,然后运行的过程如下:
同样先启动 roscore 然后重新打开终端,执行下面的操作
启动发布者节点:
#激活环境配置
source ./devel/setup.bash
#运行节点
rosrun plumbing_pub_sub pub_node_py.py
启动订阅者节点:
#激活环境配置
source ./devel/setup.bash
#运行节点
rosrun plumbing_pub_sub sub_node_py.py
1.3 C++和Python间发布者和订阅者的解耦操作
ROS系统的强大之处在于不同的编程语言间,只要符合话题通信的机制,可以进行互相发布和订阅,只要保证两者发布和订阅的话题一样,不用考虑对应的程序语言实现,即可以按照下面的步骤实现C++和Python语言的解耦通信。
则以上两者话题只要保持一致,即可实现通信。相关过程,和上面的运行一样。
2 自定义消息类型的话题通信
以上过程使用的是ROS系统中内置的标准数据类型,但是在实际的项目实践中,往往需要自定义消息类型,这个是在本部分需要注意的内容。
2.1 自定义信息
内容:
string name
int32 age
float32 height
然后配置package.xml中的依赖文件
增加下面的语句:
<!-- 自定义消息文件依赖 -->
<build_depend>message_generation</build_depend>
<!-- 自定义消息文件依赖 -->
<exec_depend>message_runtime</exec_depend>
然后在CMakeList.txt中也需要配置 Msg的配置:
生成消息时,依赖于std_msgs
下面的内容中增加 message_runtime 即执行过程中的依赖
以上配置完全后,可以进行编译,这时候会发现项目中出现了自定义的 Person.msg
以上说明后续的代码中,我们可以使用自定义的消息类型了。
另外,为保证我们在源文件编写过程中,有所提示,可以引用到生成的新消息类型,我们需要完成下面的配置:
2.2 C++版
发布者代码:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ROS_INFO("this is the publisher...");
ros::init(argc,argv,"ros_pub");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("chat",10);
plumbing_pub_sub::Person person;
person.name = "123";
person.age = 10;
person.height = 10.3;
//定义发送频率每次发1条
ros::Rate rate(1);
while(ros::ok()){
pub.publish(person);
ROS_INFO("this is the publisher...%s,%d,%.2f",person.name.c_str(),person.age,person.height);
rate.sleep();
ros::spinOnce();
}
return 0;
}
订阅者代码:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
void doMsg(const plumbing_pub_sub::Person::ConstPtr& person){
ROS_INFO("subcribe msg:%s,%d,%.2f",person->name.c_str(),person->age,person->height);
}
int main(int argc, char *argv[]){
setlocale(LC_ALL,"");
ROS_INFO("this is a subcribe ... ");
ros::init(argc,argv,"sub_person");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("chat",10,doMsg);
ros::spin();
return 0;
}
CMakeList.txt中:
add_dependencies(pub_person ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(sub_person ${PROJECT_NAME}_generate_messages_cpp)
执行过程和上面标准信息格式的 C++版本一样
2.3 Python版本
发布者代码:
#! /usr/bin/env python
import rospy
from plumbing_pub_sub.msg import Person
if __name__=="__main__":
print("===",Person)
rospy.init_node("pub_person_py")
pub = rospy.Publisher("chat_py",Person,queue_size = 10)
p = Person()
p.name = "hh"
p.age = 10
p.height = 10.1
rate = rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish(p)
rospy.loginfo("the msgs:%s,%d,%.2f",p.name,p.age,p.height)
rate.sleep()
订阅者代码:
#! /usr/bin/env python
import rospy
from plumbing_pub_sub.msg import Person
def doMsg(p):
rospy.loginfo("the info:%s,%d,%.2f",p.name,p.age,p.height)
if __name__=="__main__":
rospy.init_node("sub_person_py")
sub = rospy.Subscriber("chat_py",Person,doMsg,queue_size=10)
rospy.spin()
CMakeLists.txt配置:
运行过程和上面的 Python版本一样, 注意编写完py文件后,需要 chmod +x *.py 即增加可执行权限