此为自己学习笔记记录,仅供自己学习参考。
ROS中的基本通信机制包括三种:
1.话题通信(发布订阅模式)
2.服务通信(请求响应模式)
3.参数服务器(参数共享模式)
ROS=通信+工具软件包+功能+生态
1.创建工作空间,创建功能包,创建c++文件,配置CmakeLists文件,编译检查CmakeLists文件是否有问题,然后打开ros核心,运行文件。
2.编辑话题的发布代码及释义如下:
#include "ros/ros.h"
#include "std_msgs/String.h"
/*
发布方实现:
1.包含头文件;
ROS中文本类型 --->std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建发布者对象;
5.编写发布逻辑并发布数据;
*/
int main(int argc, char *argv[])
{
//2.初始化ROS节点;
ros::init(argc,argv,"erGoouzi");
//3.创建节点句柄;
ros::NodeHandle nh;
//4.创建发布者对象;运用的是advertise的函数,被发布者消息的类型为std_msgs,话题名为fang.
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;
}
#include "ros/ros.h"
#include "std_msgs/String.h"
#include
/*
发布方实现:
1.包含头文件;
ROS中文本类型 --->std_msgs/String.h
2.初始化ROS节点;
3.创建节点句柄;
4.创建发布者对象;
5.编写发布逻辑并发布数据;
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//2.初始化ROS节点;
ros::init(argc,argv,"erGoouzi");
//3.创建节点句柄;
ros::NodeHandle nh;
//4.创建发布者对象;运用的是advertise的函数,被发布者消息的类型为std_msgs,话题名为fang.
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;
}
3.编辑话题的订阅代码及释义如下:
#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[])
{
setlocale(LC_ALL,"");
//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.创建工作空间,创建功能包,创建python文件,添加python文件的权限,配置CmakeLists文件,编译检查CmakeLists文件是否有问题,然后打开ros核心,运行文件。
2.编辑话题的发布代码及释义如下:
#! /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)
pass
#! /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
#设置休眠3秒
rospy.sleep(3)
#使用循环发布数据
while not rospy.is_shutdown():
count += 1
msg.data = "hello" + str(count)
#发布数据
pub.publish(msg)
rospy.loginfo("发布的数据:%s",msg.data)
rate.sleep()
pass
3.编辑话题的订阅代码及释义如下:
#! /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)
if __name__ == "__main__":
# 2.初始化ROS节点;
rospy.init_node("huaHua")
# 3.创建订阅者对象;
sub = rospy.Subscriber("che",String,doMsg,queue_size=10)
# 4.回调函数处理数据;
# 5.spin()
rospy.spin()
pass
1.功能包下新建msg目录即文件夹,添加文件Person.msg
string name
int32 age
float32 height
2.编辑配置文件:
package.xml中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
// exce_depend以前对应的是 run_depend 现在非法
CMakeLists.txt编辑 msg 相关配置
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入message_generation,必须有 std_msgs
#改动地方为,添加了 message_generation
# # 配置 msg 源文件
## Generate messages in the 'msg' folder
add_message_files(
FILES
Person.msg
)
#改动地方为,删除了两行,只添加了 Person.msg,去掉了对应注释。
# 生成消息时依赖于 std_msgs
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
#改动地方为,去掉了最后三行注释。
#执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES plumbing_pub_sub
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
#改动地方为,只添加了message_runtime,并去掉了该行的注释
1. .vscode下的c_cpp_properties.json文件配置,目的是为了提供自动检测等功能。
"includePath": [
"/opt/ros/noetic/include/**",
"/usr/include/**",
"/opt/ros/noetic/include",
//添加了如下之行
"/home/wjy/demo03_ws/devel/include/**"
2.发布方代码编辑如下:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
/*
发布方:发布人的消息
1.包含头文件;
#include "plumbing_pub_sub/Person.h"
2.初始化ROS节点;
3.创建节点句柄;
4.创建发布者对象;
5.编写发布逻辑,发布数据。
*/
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
// 2.初始化ROS节点;
ros::init(argc,argv,"banZhuRen");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建发布者对象;
ros::Publisher pub = nh.advertise<plumbing_pub_sub::Person>("liaoTian",10);
// 5.编写发布逻辑,发布数据。
//5-1.创建被发布的数据
plumbing_pub_sub::Person person;
person.name = "张三";
person.age = 1;
person.height = 1.73;
//5.2.设置发布频率
ros::Rate rate(1);
//5.3.循环发布数据
while(ros::ok()){
//修改数据
person.age += 1;
//核心:数据发布
pub.publish(person);
//休眠
rate.sleep();
//建议
ros::spinOnce();
}
return 0;
}
3.发布方配置如下
add_executable(demo03_pub_person src/demo03_pub_person.cpp)
target_link_libraries(demo03_pub_person
${catkin_LIBRARIES}
)
//此行的添加意味着,先执行msg文件的配置编译,然后再执行代码编辑的配置编译,防止发生逻辑颠倒问题。
add_dependencies(demo03_pub_person ${PROJECT_NAME}_generate_messages_cpp)
4.订阅方代码编辑如下:
#include "ros/ros.h"
#include "plumbing_pub_sub/Person.h"
/* 订阅方:订阅消息
1.包含头文件;
#include "plumbing_pub_sub/Person.h"
2.初始化ROS节点;
3.创建节点句柄;
4.创建订阅者对象;
5.处理订阅的数据;
6.调用spin()函数。
*/
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[])
{
setlocale(LC_ALL,"");
ROS_INFO("订阅方实现");
// 2.初始化ROS节点;
ros::init(argc,argv,"jiaZhang");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建订阅者对象;
ros::Subscriber sub = nh.subscribe("liaoTian",10,doPerson);
// 5.处理订阅的数据;
// 6.调用spin()函数。
ros::spin();
return 0;
}
5.订阅方配置如下:
add_executable(demo04_sub_person src/demo04_sub_person.cpp)
target_link_libraries(demo04_sub_person
${catkin_LIBRARIES}
)
//此行的添加意味着,先执行msg文件的配置编译,然后再执行代码编辑的配置编译,防止发生逻辑颠倒问题。
add_dependencies(demo04_sub_person ${PROJECT_NAME}_generate_messages_cpp)
1.VScode配置:为了方便代码提示以及误抛出异常,需要进行相应配置,将前面生成的python文件路径配置进settings.json
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
//只添加了以下此路径,可以增加自动补齐功能
"/home/wjy/demo03_ws/devel/lib/python3/dist-packages"
2.发布方代码实现:
#! /usr/bin/env python
import rospy
from plumbing_pub_sub.msg import Person
"""
发布方:发布人的消息
1.导包;
2.初始化ROS节点;
3.创建发布者对象;
4.组织发布逻辑并发布数据。
"""
if __name__ == "__main__":
#2.初始化ROS节点;
rospy.init_node("daMa")
#3.创建发布者对象,需要3个参数,话题名称,消息类型,队列长度;
pub = rospy.Publisher("jiaoSheTou",Person,queue_size=10)
# 4.组织发布逻辑并发布数据。
#4.1创建Person数据
p = Person()
p.name = "奥特曼"
p.age = 8
p.height = 1.85
#4.2创建Rate对象,即发布频率;
rate = rospy.Rate(1)
#4.3循环发布数据
while not rospy.is_shutdown():
pub.publish(p)
rospy.loginfo("发布的消息:%s,%d,%.2f",p.name,p.age,p.height)
rate.sleep()
pass
3 修改可执行文件权限
//在scripts文件夹上右键。在集成终端打开输入以下命令改变权限
chmod +x *.py
//查看权限情况
ll
4 发布方文件配置:
//只添加了这一行即可
scripts/demo03_pub_person_p.py
5 订阅方代码实现:
#! /usr/bin/env python
import rospy
from plumbing_pub_sub.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__":
# 2.初始化ROS节点;
rospy.init_node("daYe")
# 3.创建订阅者对象;
sub = rospy.Subscriber("jiaoSheTou",Person,doPerson)
# 4.处理订阅的数据;
# 5.spin()
rospy.spin()
pass
6 修改可执行文件权限
//在scripts文件夹上右键。在集成终端打开输入以下命令改变权限
chmod +x *.py
//查看权限情况
ll
7 订阅方文件配置
scripts/demo04_sub_person_p.py
注意:
1.保证顺序。客户端发起请求时,服务端需要已经启动;
2.客户端和服务端都可以存在多个;
流程:srv文件内的可用数据类型与msg文件一致,且定义srv实现流程与自定义msg实现流程类似:
服务通信中,数据分成两部分,请求与响应,在srv文件中请求和响应使用- - -分割,具体实现如下:
功能包下新建srv目录,添加xxx,srv文件,内容如下:
#客户端请求时发送的两个数字
int32 num1
int32 num2
---
#服务器响应发送的数据
int32 sum
1.配置package.xml文件
// 在std_msgs 下添加这一行
<build_depend>message_generation</build_depend>
//在std_msgs 下添加这一行
<exec_depend>message_runtime</exec_depend>
2.配置CMakeLists.txt文件
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
#在这里只添加了最后一行 message_generation
## Generate services in the 'srv' folder
add_service_files(
FILES
AddInts.srv
)
#在这里打开了注释,添加了 AddInts.srv ,删除了其他2行
## Generate added messages and services with any dependencies listed here
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
)
#在这里打开了倒数第2行的注释,末尾添加了message_runtime
C++ 中需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)
python 中需要调用的中间文件(…/工作空间/devel/lib/python3/dist-package/包名/msg)
1 vscode配置如下:在终端打开该目录,打印输出该路径,复制到c_cpp_properties.json下的"includePath":里面。
2 在功能包下的src新建文件demo01_server.cpp文件,编辑服务端代码如下:
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
/*
服务端实现:解析客户端提交的数据,并运算再产生响应
1.包含头文件
2.初始化ROS节点;
3.创建节点句柄;
4.创建一个服务对象;
5.处理请求并产生响应;
6.spin()
*/
bool doNums(plumbing_server_client::AddInts::Request &request,
plumbing_server_client::AddInts::Response &response){
//1.处理请求
int num1 = request.num1;
int num2 = request.num2;
ROS_INFO("收到的请求数据:num1 = %d, num2 = %d",num1, num2);
//2.组织响应
int sum = num1 + num2;
response.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);
// 5.处理请求并产生响应;
// 6.spin()
ros::spin();
return 0;
}
3 配置package.xml文件:
<build_depend>message_generation</build_depend>
//注意对应位置
<exec_depend>message_runtime</exec_depend>
4 配置CMakeLists.txt文件:
# add_executable(${PROJECT_NAME}_node src/plumbing_server_client_node.cpp)
add_executable(demo01_server src/demo01_server.cpp)
## Specify libraries to link a library or executable target against
target_link_libraries(demo01_server
${catkin_LIBRARIES}
)
#这一行的意思是和之前一样,防止逻辑问题出现,需要先编译中间文件,再来编译此demo01_server文件,逻辑顺序不能错。
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(demo01_server ${PROJECT_NAME}_gencpp)
5 在功能包下的src新建文件demo02_client.cpp文件,编辑客户端代码如下:
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
/*
客户端实现:提交两个整数,并处理响应的结果。
1.包含头文件
2.初始化ROS节点;
3.创建节点句柄;
4.创建一个客户端对象;
5.提交请求并处理响应;
*/
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.提交请求并处理响应;
plumbing_server_client::AddInts ai;
//5-1. 组织请求
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;
}
6 配置CMakeLists.txt文件:
add_dependencies(demo02_client ${PROJECT_NAME}_gencpp)
target_link_libraries(demo02_client
${catkin_LIBRARIES}
)
add_dependencies(demo02_client ${PROJECT_NAME}_gencpp)
7 优化后的客户端代码,可以动态输入数据。
#include "ros/ros.h"
#include "plumbing_server_client/AddInts.h"
/*
客户端实现:提交两个整数,并处理响应的结果。
1.包含头文件
2.初始化ROS节点;
3.创建节点句柄;
4.创建一个客户端对象;
5.提交请求并处理响应;
实现参数的动态提交
1.格式:rosrun xxxx xxxx 12 34
2.节点执行时,需要获取命令中的参数,并组织进 request
*/
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.提交请求并处理响应;
plumbing_server_client::AddInts ai;
//5-1. 组织请求
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 当客户端先启动,使其挂起,等待服务端启动后再请求方法。
//5-2. 处理响应
//调用判断服务器状态的函数
//函数1
client.waitForExistence();
//或者函数2也行
ros::service::waitForExistence("服务话题");
1 vscode配置:,配置settings.json文件方法如下:
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
//添加了如下之行的路径
"/home/wjy/demo03_ws/devel/lib/python3/dist-packages"
],
2 新建demo01_server_p.py文件,代码编辑如下:
#! /usr/bin/env python
import rospy
# from plumbing_server_client.srv import AddInts,AddIntsRequest,AddIntsResponse
from plumbing_server_client.srv import *
"""
服务端:解析客户端请求,产生响应
1.导包;
2.初始化ROS节点;
3.创建服务端对象;
4.处理逻辑(回调函数)
5.spin()
"""
#参数:封装了请求数据
#返回值:响应数据
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.spin()
rospy.spin()
3 修改文件权限
chmod +x *.py
ll
4 配置CMakeLists.txt文件
## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
catkin_install_python(PROGRAMS
scripts/demo01_server_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据。
以共享的方式实现不同节点之间数据交互的通信模式,存储一些多节点共享的数据,类似于全局变量。
同上操作,代码如下。
#include "ros/ros.h"
/*
需要实现参数的新增与修改
需求:首先设置机器人的共享参数,类型、半径(0.15米)
再修改半径(0.2米)
实现:
ros::NodeHandle
setParm("键",值)
ros::param
set("键",值)
修改,只需要继续调用,setParam或 set 函数,保证键已经存在,那么值会覆盖;
*/
int main(int argc, char *argv[])
{
//初始化ROS节点;
ros::init(argc,argv,"set_param_c");
//创建节点句柄;
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;
}