第二章、ROS通信机制
一、话题通信(发布订阅)
1.话题通信理论模型
- 角色
-
ROSMaster 管理者(媒婆) -
talker 发布者(男方) -
listener 订阅者(女方)
- 流程
- ROSMaster可以根据话题建立发布者和订阅者的连接
- 注意事项
- RPC协议
- TCP协议
- 步骤0和步骤1没有顺序要求
- talker和listener可以存在多个
- talker个listener建立联系后,master就可以关闭了
- 上述实现流程已经被封装,直接调用就可以了
- 关注点
- 大部分实现已经被封装了
- 设置一致的话题
- 主要关注发布者的实现,订阅者的实现,消息载体
2.话题通信基本操作
- C++实现
#include "ros/ros.h"
#include "std_msgs/String.h"
#include "sstream"
int main(int argc,char* argv[]) {
ros::init(argc,argv,"结点名称");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("话题名称",10);
std_msgs::String msg;
ros::Rate rate(10);
int count = 0;
while(ros::ok()){
setlocale(LC_ALL,"");
count++;
std::stringstream ss;
ss << "hello--->" << count;
msg.data = ss.str();
ROS_INFO("发布的数据%s",ss.str().c_str());
pub.publish(msg);
rate.sleep();
}
return 0;
}
rostopic echo 话题名称
#include "ros/ros.h"
#include "std_msgs/String.h"
void 回调函数名(const std_msgs::String::ConstPtr& msg) {
ROS_INFO("翠花订阅到的数据%s",msg->data.c_str());
}
int main(int argc,char* argv[]) {
setlocale(LC_ALL,"");
ros::init(argc,argv,"结点名");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("订阅的话题名",10,回调函数名);
ros::spin();
}
- python实现
import rospy
from std_msgs.msg import String
if __name__ == "__main__":
rospy.init_node("sanDai")
pub = rospy.Publisher("che",String,queue_size=10)
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()
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()
pass
数据丢失:加延时
3.自定义话题通信
**简介:**ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty…但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性。
可以使用的字段类:
- int8, int16, int32, int64 (或者无符号类型: uint*)
- float32, float64
- string
- time, duration
- other msg files
- variable-length array[] and fixed-length array[C]
- 自定义msg文件
msg目录下文本文件
string name
uint16 age
float64 height
- 编辑文件配置
package.xml中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
CMakeList.txt编辑msg相关配置
# 需要加入 message_generation,必须有 std_msgs
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
## 配置 msg 源文件
add_message_files(
FILES
Person.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
#执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo02_talker_listener
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
# 防止编译时,编译顺序错误
add_dependencies(demo02_pub ${PROJECT_NAME}_generate_messages_cpp)
- 编译及VC配置
{
"configurations": [
{
"browse": {
"databaseFilename": "",
"limitSymbolsToIncludedHeaders": true
},
"includePath": [
"/opt/ros/noetic/include/**",
"/usr/include/**",
"/home/zhf/Learn_ROS/demo03_ws/devel/include/**"
],
"name": "ROS",
"intelliSenseMode": "gcc-x64",
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}
- C++实现
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"Person");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("teacher",20);
plumbing_pub_sub::Person stu;
stu.age = 1;
stu.name = "张三";
stu.height = 1.7;
ros::Rate rate(1);
while (ros::ok()) {
rate.sleep();
pub.publish(stu);
ROS_INFO("name:%s,age:%d,height:%0.2f",stu.name.c_str(),stu.age,stu.height);
stu.age ++;
ros::spinOnce();
}
return 0;
}
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
using namespace ros;
void doMsg(const plumbing_pub_sub::Person::ConstPtr & msg) {
ROS_INFO("%s,%d,%0.2f",msg->name.c_str(),msg->age,msg->height);
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
init(argc,argv,"student");
NodeHandle np;
Subscriber sub = np.subscribe("teacher",20,doMsg);
spin();
return 0;
}
这个函数的参数是针对消息的一个指针,const plumbing_pub_sub::Person::ConstPtr & msg 这是在ros里面固定格式的一个调用,plumbing_pub_sub::Person 对应于我们主函数里面话题内容/turtle1/pose是一样的,后面的ConstPtr是一个常指针,msg以一个常指针的形式指向plumbing_pub_sub::Person所有的信息的数据内容
VC 配置d
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/home/zhf/Learn_ROS/demo03_ws/devel/lib/python3/dist-packages"
],
"python.pythonPath": "/usr/bin/python",
"cmake.configureOnOpen": false
}
pub
import rospy
from plumbing_pub_sub.msg import Person
if __name__ == "__main__":
rospy.init_node("father")
pub = rospy.Publisher("liaotian",Person,queue_size=10)
fat = Person()
fat.name = "李四"
fat.age = 1
fat.height = 1.8
rate = rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish(fat)
fat.age += 1
rate.sleep()
sub
import rospy
from plumbing_pub_sub.msg import Person
def doMsg(p):
rospy.loginfo("%s,%d,%0.2f",p.name,p.age,p.height)
if __name__ == "__main__":
rospy.init_node("mather")
sub = rospy.Subscriber("liaotian",Person,doMsg,queue_size=10)
rospy.spin()
二、服务通信(请求响应)
1.概念
以请求响应的方式实现不同节点之间数据交互的通信模式。
用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输场景。
2.模型
- ROS master
- Talker(Server)
- Listener(Client)
1.服务端是先启动的,客户端后请求
2.客户端和服务端都可以存在多个
3.话题需要一致
4.数据格式自定义
3.自定义服务通信数据类型srv
- 按照固定格式创建srv文件
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_service_files(
FILES
AddInts.srv
)
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
)
4.C++实现
配置vscode
/home/zhf/Learn_ROS/demo03_ws/devel/include
服务端
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
using namespace ros;
bool doNums(plumbing_server_client::AddInts::Request & request,
plumbing_server_client::AddInts::Response & response) {
int num1 = request.num1;
int num2 = request.num2;
int sum = num1 + num2;
ROS_INFO("%d+%d=%d",num1,num2,sum);
response.sum = sum;
return true;
}
int main(int argc, char *argv[])
{
init(argc,argv,"server");
NodeHandle nh;
ServiceServer server = nh.advertiseService("addInts",doNums);
spin();
return 0;
}
CMakeList.txt
add_executable(demo01_server src/demo01_server.cpp)
# 后面的参数可以使用默认值
add_dependencies(demo01_server ${PROJECT_NAME}_gencpp)
target_link_libraries(demo01_server
${catkin_LIBRARIES}
)
测试指令
rosservice call addInts "num1: 6
num2: 5"
客户端
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
using namespace ros;
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
init(argc,argv,"client");
NodeHandle nh;
plumbing_server_client::AddInts msg;
msg.request.num1 = 5;
msg.request.num2 = 10;
ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("addInts");
bool flag=true;
Rate rate(10);
while (flag) {
msg.request.num1++;
flag = client.call(msg);
ROS_INFO("响应结果%d",msg.response.sum);
rate.sleep();
}
return 0;
}
实现参数的动态提交
client.waitForExistence();
ros::service::waitForService("等待的服务名称");
5.python实现
服务端
import rospy
from plumbing_server_client.srv import *
def doNum(request):
sum = request.num1 + request.num2
response = AddIntsResponse()
response.sum = sum
rospy.loginfo(response.sum)
return response.sum
if __name__ == "__main__":
rospy.init_node("demo01_server")
ser = rospy.Service("server_1",AddInts,doNum)
rospy.loginfo("服务器已经启动")
rospy.spin()
客户端
import rospy
from plumbing_server_client.srv import *
import sys
if __name__ == "__main__":
rospy.init_node("demo01_client")
cli = rospy.ServiceProxy("server_1",AddInts)
num1 = int(sys.argv[1])
num2 = int(sys.argv[2])
rospy.wait_for_service("server_1")
response = cli.call(num1,num2)
rospy.loginfo("响应数据%d",response.sum)
三、参数服务器(参数共享)
1.概念
参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据。
- 以共享的方式实现不同节点之间的数据交互
- 作用
- 参数服务器的增删改查
2.模型
参数服务器不是为高性能而设计的
32-bit integers
booleans
strings
doubles
iso8601 dates
lists
base64-encoded binary data
字典
3.C++实现
ros::NodeHandle
setParam("键",值)
ros::param
set("键",值)
#include "ros/ros.h"
using namespace ros;
int main(int argc, char *argv[])
{
init(argc,argv,"ros_param_C");
NodeHandle nh;
nh.setParam("type","xiaoHuang");
nh.setParam("radius_1",0.15);
param::set("type_param","xiaoBai");
param::set("radius_2",0.15);
nh.setParam("radius_1",0.3);
param::set("radius_2",0.25);
return 0;
}
rosparam list
rosparam get /数据名
#include "ros/ros.h"
using namespace ros;
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
init(argc,argv,"ros_param_C");
NodeHandle nh;
float radius = nh.param("radius",0.0);
ROS_INFO("radius:%0.2f",radius);
float radius_1;
bool result = nh.getParam("radius_111",radius_1);
if(result)
ROS_INFO("radius:%0.2f",radius_1);
else
ROS_INFO("没有找到数据");
result = nh.getParamCached("radius_1",radius_1);
if(result)
ROS_INFO("radius:%0.2f",radius_1);
else
ROS_INFO("没有找到数据");
std::vector<std::string> names;
nh.getParamNames(names);
for (auto &&name : names){
ROS_INFO("便利到的元素%s",name.c_str());
}
result = nh.hasParam("radius_1");
if(result)
ROS_INFO("键存在");
else
ROS_INFO("没有找到数据");
std::string key;
nh.searchParam("type",key);
ROS_INFO("搜索结果:%s",key.c_str());
return 0;
}
#include "ros/ros.h"
using namespace ros;
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
init(argc,argv,"ros_param_C");
NodeHandle nh;
nh.setParam("radius",0.5);
bool flag1 = nh.deleteParam("radius");
if (flag1) {
ROS_INFO("删除成功");
}else{
ROS_INFO("删除失败");
}
flag1 = ros::param::del("radius");
if (flag1) {
ROS_INFO("删除成功");
}else{
ROS_INFO("删除失败");
}
std::vector<std::string> names;
ros::param::getParamNames(names);
for (auto && name :names) {
ROS_INFO("节点有%s",name.c_str());
}
return 0;
}
4.Python实现
"""
参数服务器操作之新增与修改(二者API一样)_Python实现:
"""
import rospy
if __name__ == "__main__":
rospy.init_node("set_update_paramter_p")
rospy.set_param("p_int",10)
rospy.set_param("p_double",3.14)
rospy.set_param("p_bool",True)
rospy.set_param("p_string","hello python")
rospy.set_param("p_list",["hello","haha","xixi"])
rospy.set_param("p_dict",{"name":"hulu","age":8})
rospy.set_param("p_int",100)
"""
参数服务器操作之查询_Python实现:
get_param(键,默认值)
当键存在时,返回对应的值,如果不存在返回默认值
get_param_cached
get_param_names
has_param
search_param
"""
import rospy
if __name__ == "__main__":
rospy.init_node("get_param_p")
int_value = rospy.get_param("p_int",10000)
double_value = rospy.get_param("p_double")
bool_value = rospy.get_param("p_bool")
string_value = rospy.get_param("p_string")
p_list = rospy.get_param("p_list")
p_dict = rospy.get_param("p_dict")
rospy.loginfo("获取的数据:%d,%.2f,%d,%s",
int_value,
double_value,
bool_value,
string_value)
for ele in p_list:
rospy.loginfo("ele = %s", ele)
rospy.loginfo("name = %s, age = %d",p_dict["name"],p_dict["age"])
int_cached = rospy.get_param_cached("p_int")
rospy.loginfo("缓存数据:%d",int_cached)
names = rospy.get_param_names()
for name in names:
rospy.loginfo("name = %s",name)
rospy.loginfo("-"*80)
flag = rospy.has_param("p_int")
rospy.loginfo("包含p_int吗?%d",flag)
key = rospy.search_param("p_int")
rospy.loginfo("搜索的键 = %s",key)
"""
参数服务器操作之删除_Python实现:
rospy.delete_param("键")
键存在时,可以删除成功,键不存在时,会抛出异常
"""
import rospy
if __name__ == "__main__":
rospy.init_node("delete_param_p")
try:
rospy.delete_param("p_int")
except Exception as e:
rospy.loginfo("删除失败")
四、常用命令
-
如何获取话题和消息载体、可以通过下面命令得到 -
动态获取结点数据 -
官方链接:
1.rosnode:操作结点
rosnode ping 节点名称
rosnode list
rosnode info 结点名称
rosnode machine 设备名称
rosnode kill 结点名称
rosnode cleanup
2.rostopic:操作话题
- 用于获取ROS主题调试信息,包括订阅者,发布者,发布频率和ROS消息
- 用法
rostopic bw
rostopic delay
rostopic echo
rostopic find
rostopic hz
rostopic info
rostopic list
rostopic pub
rostopic type
3.rosmsg:操作msg消息
rosmsg show
rosmsg info
rosmsg list
rosmsg md5
rosmsg package
rosmsg packages
4.rosservice:操作服务
rosservice args
rosservice call
rosservice find
rosservice info
rosservice list
rosservice type
rosservice uri
5.rossrv:操作srv消息
rossrv show
rossrv info
rossrv list
rossrv md5
rossrv package
rossrv packages
6.rosparm:操作参数
- 用于使用YAML编码文件在参数服务器上获取和设置ROS参数。
- 用法
rosparam set
rosparam get
rosparam delete
rosparam list
rosparam load
rosparam dump
五、通信实操
案例一、话题发布
- 命令获取结点话题名称和数据类型
- C++实现
#include "ros/ros.h"
#include "geometry_msgs/Twist.h"
int main(int argc,char* argv[]) {
ros::init(argc,argv,"test_pub");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",100);
ros::Rate rate(100);
geometry_msgs::Twist msg;
msg.linear.x = 0.1;
msg.linear.y = 0.0;
msg.linear.z = 0.0;
msg.angular.z = 0.8;
msg.angular.y = 0.0;
msg.angular.x = 0.0;
while(ros::ok()){
pub.publish(msg);
msg.linear.x += 0.001;
ROS_INFO("haha");
rate.sleep();
ros::spinOnce();
}
return 0;
}
案例二、话题订阅
#include "ros/ros.h"
#include "turtlesim/Pose.h"
using namespace ros;
void doTurtlesim(const turtlesim::Pose::ConstPtr & pose) {
ROS_INFO("位置信息:");
ROS_INFO("位置(x:%0.2f,y:%0.2f)朝向(theta:%0.2f)线速度(linear_velocity:%0.2f)加速度(angular_velocity:%.2f)",pose->x,pose->y,pose->theta,pose->linear_velocity,pose->angular_velocity);
}
int main(int argc,char* argv[]) {
setlocale(LC_ALL,"");
init(argc,argv,"test_sub");
NodeHandle nh;
Subscriber sub = nh.subscribe("/turtle1/pose",100,doTurtlesim);
spin();
return 0;
}
补、
案例一和案例二需要手动添加依赖
pakeage.xml
CMakeList.txt
案例三、服务调用
需要依赖包turtlesim
#include "ros/ros.h"
#include "turtlesim/Spawn.h"
using namespace ros;
int main(int argc,char* argv[]) {
setlocale(LC_ALL,"");
init(argc,argv,"test_ser");
NodeHandle nh;
ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");
turtlesim::Spawn msg;
msg.request.x = 1.0;
msg.request.y = 1.0;
msg.request.theta = 1.0;
msg.request.name = "turtle3";
client.waitForService();
bool flag = client.call(msg);
if (flag)
{
ROS_INFO("新的乌龟生成,名字:%s",msg.response.name.c_str());
} else {
ROS_INFO("乌龟生成失败!!!");
}
spinOnce();
return 0;
}
案例四、参数服务调用
#include "ros/ros.h"
using namespace ros;
int main(int argc,char* argv[]){
init(argc,argv,"test_param");
param::set("/turtlesim/background_r",255);
param::set("/turtlesim/background_g",0);
param::set("/turtlesim/background_b",0);
return 0;
}
[外链图片转存中…(img-vBl8Uuzu-1629810352567)]
需要依赖包turtlesim
#include "ros/ros.h"
#include "turtlesim/Spawn.h"
using namespace ros;
int main(int argc,char* argv[]) {
setlocale(LC_ALL,"");
init(argc,argv,"test_ser");
NodeHandle nh;
ServiceClient client = nh.serviceClient<turtlesim::Spawn>("/spawn");
turtlesim::Spawn msg;
msg.request.x = 1.0;
msg.request.y = 1.0;
msg.request.theta = 1.0;
msg.request.name = "turtle3";
client.waitForService();
bool flag = client.call(msg);
if (flag)
{
ROS_INFO("新的乌龟生成,名字:%s",msg.response.name.c_str());
} else {
ROS_INFO("乌龟生成失败!!!");
}
spinOnce();
return 0;
}
[外链图片转存中…(img-fveLxOQC-1629810352568)]
案例四、参数服务调用
[外链图片转存中…(img-2s9OOhEB-1629810352568)]
#include "ros/ros.h"
using namespace ros;
int main(int argc,char* argv[]){
init(argc,argv,"test_param");
param::set("/turtlesim/background_r",255);
param::set("/turtlesim/background_g",0);
param::set("/turtlesim/background_b",0);
return 0;
}
|