前言
本文记录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 pythonimport rospy
from plumbing_pub_sub.msg import Personif __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 = 10p.height = 10.1rate = 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 即增加可执行权限