(菜鸡整理的学习笔记,原文链接已经挂上,不小心过来的朋友建议去博主那边学习,谢谢)
原文链接:https://blog.csdn.net/LoongEmbedded/article/details/124321129
ROS的核心——分布式通信机制
ROS是一个分布式框架,为用户提供多节点(进程)之间的通信服务,所有软件功能和工具都建立在这种分布式通信机制上,所以ROS的通信机制是最底层也是最核心的技术。ROS最核心的三种通信机制如下。
Topic机制
service机制
共享参数机制
原文链接:https://blog.csdn.net/allenhsu6/article/details/112334048
本文链接:https://blog.csdn.net/LoongEmbedded/article/details/124181661
topic通讯的具体实现是通过node之间传递message进行信息传输。
以激光雷达信息的采集为例,在ROS中有一个节点需要实时的发布当前雷达采集到的数据,导航模块中也有节点会订阅并解析雷达数据。
再以运动消息的发布为例,导航模块会根据传感器采集的数据时时的计算出运动控制信息并发布给底盘,地盘也可以有一个节点订阅运动信息并最终转换成控制电机的脉冲信号。
涉及到三个角色:
●ROS Master (大管家)
●Talker (发布者)
●Listener (订阅者)
ROS Master 负责保管 Talker 和 Listener 注册的信息,并匹配话题相同的 Talker 与 Listener,帮助 Talker 与 Listener 建立连接,连接建立后,Talker 可以发布消息,且发布的消息会被 Listener 订阅。
Node Master在整个通讯架构中相当于管理中心,是管理整个node通讯的大管家。node首先在master处进行注册,之后master会将该node纳入整个ROS程序中。
当ROS程序启动时,第一步首先启动master,然后节点管理器处理依次启动node。
1、talker注册:
Talker启动,通过1234端口使用RPC向ROS Master注册发布者的信息,包含所发布消息的话题名;ROS Master会将节点的注册信息加入注册列表中。
2、listener注册:
Listener启动,同样通过RPC向ROS Master注册订阅者的信息,包含需要订阅的话题名。
3、ros master信息匹配:
Master通过listener的订阅信息,在注册列表中查找,没有找到发布者,就等待发布者的加入;找到的话,就通过RPC向listener发布talker的RPC地址信息。
4、listener发送连接请求:
Listener接收到Master发回的Talker地址信息,尝试通过RPC向Talker发送连接请求,传输订阅的话题名、消息类型以及通信协议(TCP/UDP)
5、talker确认连接请求:
通过RPC向listener确认连接信息,其中包含TCP地址
6、listener尝试与talker建立网络连接
Listener接收到确认信息后,使用TCP尝试与Talker建立网络连接。
7、talker向listener发布数据
成功建立连接后,Talker开始向Listener发送话题消息数据。
注意1:上述实现流程中,前五步使用的 RPC协议,最后两步使用的是 TCP 协议
注意2: Talker 与 Listener 的启动无先后顺序要求
注意3: Talker 与 Listener 都可以有多个
注意4: Talker 与 Listener 连接建立后,不再需要 ROS Master。也即,即便关闭ROS Master,Talker 与 Listern 照常通信。
//包含核心ROS库的头文件
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include
int main(int argc, char *argv[])
{
//设置编码
setlocale(LC_ALL,"");
//初始化ROS节点,节点名为topic_pub
ros::init(argc,argv,"topic_pub");
//创建节点句柄
ros::NodeHandle nh;//该类封装了ROS中的一些常用功能
//实例化发布者对象
//泛型:std_msgs::String发布的消息类型
//参数1:chatter要发布到的话题;参数2:10,队列中最大保存的消息数,超出此阈值时,先进的先销毁
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);
//组织被发布的数据,并编写逻辑发布数据
//数据(动态组织)
std_msgs::String msg;
std::string msg_prefix = "hello!";//消息前缀
int count = 0;//消息计数器
//逻辑,1s一次
ros::Rate r(1);
while(ros::ok())//节点不死
{
//使用stringstream拼接字符串与编号
std::stringstream ss;
ss << msg_prefix << count;
msg.data = ss.str();
//发布消息
pub.publish(msg);
//加入调试,打印发送的消息
ROS_INFO("发送的消息:%s",msg.data.c_str());
//根据前面制定的发送频率自动休眠,休眠时间=1/频率;
r.sleep();
count++;//循环结束前,让count自增
//此节点只发布话题,没有订阅话题,此函数这里主要时方便后面如果要订阅话题可调用话题绑定的回调函数而已
ros::spinOnce();
}
return 0;
}
加上这一段代码,注意topic_pub要一致
add_executable(topic_pub src/topic_pub.cpp)
target_link_libraries{topic_pub
${catkin_LIBRARIES}
}
1、用ctrl+shift+b编译后,编译生成的文件topic_pub
2、终端执行命令验证:
roscore
rosrun topic_com topic_pub
rostopic echo chatter
rqt_graph
//包含头文件
#include "ros/ros.h"
#include "std_msgs/String.h"
void subscribe_topic(const std_msgs::String::ConstPtr& msg_p)
{
ROS_INFO("订阅的话题:%s",msg_p->data.c_str());
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//初始化ROS节点,节点名为topic_sub
ros::init(argc,argv,"topic_sub");
//创建节点句柄
ros::NodeHandle nh;//该类封装了ROS中的一些常用功能
//实例化订阅者对象,subscribe_topic是订阅者回调函数
ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,subscribe_topic);
//设置循环调用回调函数
ros::spin();//循环接收订阅的话题数据,并调用回调函数处理
return 0;
}
在发布者那一块加上代码,
add_executable(topic_pub src/topic_pub.cpp)
add_executable(topic_sub src/topic_pub.cpp)
target_link_libraries{topic_pub
${catkin_LIBRARIES}
}
target_link_libraries{topic_sub
${catkin_LIBRARIES}
}
rosrun topic_com topic_sub
先创建msg文件夹,并在其中创建person.msg。
string name
int32 age
float32 height
添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
#加入message_generation,必须有 std_msgs
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
#打开注释,并配置msg源文件person.msg
## Generate messages in the 'msg' folder
add_message_files(
FILES
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 topic_com
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
编译后生成的中间文件person.h位于devel/include目录:
后续调用相关msg时,是从中间文件person.h文件调用的
为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性,增加的内容是"/home/kandi/catkin_ws/devel/include/**",
"includePath": [
"/opt/ros/noetic/include/**",
"/home/kandi/catkin_ws/src/helloworld/include/**",
"/home/kandi/catkin_ws/src/topic_pub/include/**",
"/home/kandi/catkin_ws/devel/include/**",
"/usr/include/**"
],
原文链接:https://blog.csdn.net/LoongEmbedded/article/details/124321129
服务通信是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景:
机器人巡逻过程中,控制系统分析传感器数据发现可疑物体或人… 此时需要拍摄照片并留存。
在上述场景中,就使用到了服务通信。
一个节点需要向相机节点发送拍照请求,相机节点处理请求,并返回处理结果
博客园:https://www.cnblogs.com/Alexbeast-CN/p/15147608.html
该模型中涉及到三个角色:
●ROS master(管理者)
●Server(服务端)
●Client(客户端)
ROS Master 负责保管 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,帮助 Server 与 Client 建立连接,连接建立后,Client 发送请求信息,Server 返回响应信息。
(1) Talker(也就是server)注册
Talker启动,通过1234端口使用RPC向ROS Master注册发布者的信息,包含所提供的服务名;ROS Master会将节点的注册信息加入注册列表中。
(2) Listener(也就是client)注册
Listener启动,同样通过RPC向ROS Master注册订阅者的信息,包含需要订阅的服务名。
(3) ROS Master实现信息匹配
Master根据Listener的订阅信息从注册列表中进行查找,如果没有找到匹配的服务提供者,则等待该服务的提供者加入:如果找到匹配的服务提供者信息,则通过RPC向Listener发送Talker的TCP地址信息。
(4) Listener与Talker建立网络连接
Listener接收到确认信息后,使用TCP尝试与Talker建立网络连接,并且发送服务的请求数据。
(6) Talker向Listener发布服务应答数据
Talker接收到服务请求和参数后,开始执行服务功能,执行完成后,向Listener发送应答数据。
注意:
1.客户端请求被处理时,需要保证服务器已经启动;
2.服务端和客户端都可以存在多个。
#include "ros/ros.h"
#include "service_com/sum.h"
// bool 返回值由于标志是否处理成功
bool do_sum(service_com::sum::Request &request,
service_com::sum::Response &response)
{
//处理请求
int num1 = request.num1;
int num2 = request.num2;
ROS_INFO("收到的请求数据:num1=%d,num2=%d",num1,num2);
//响应请求
int sum = num1 + num2;
response.sum = sum;
ROS_INFO("求和结果:=%d",sum);
return true;
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//初始化节点,节点名称是sum_server
ros::init(argc,argv,"sum_server");
//创建节点句柄
ros::NodeHandle nh;
//创建一个服务对象,定义回调sum用于响应client的请求
ros::ServiceServer server = nh.advertiseService("sum",do_sum);
ROS_INFO("服务已启动");
//调用后不会再返回,也就是你的主程序到这儿就不往下执行了
ros::spin();
return 0;
}
编译之后,可以通过rosservice来测试编写的服务端实现是否正常
roscore
rosrun service com sum_service
rosservice call sum 1 2
在这里插入代码片
```#include "ros/ros.h"
#include "service_com/sum.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
//初始化节点
ros::init(argc,argv,"sum_client");
//创建ROS句柄
ros::NodeHandle nh;
//创建客户端对象
ros::ServiceClient client = nh.serviceClient<service_com::sum>("sum");
//等待服务启动成功
ros::service::waitForService("sum");
//组织请求的数据
service_com::sum add_num;
add_num.request.num1 = atoi(argv[1]);
add_num.request.num2 = atoi(argv[2]);
//发送请求,根据返回的bool值判断是否成功
bool flag = client.call(add_num);
//处理响应
if(flag)
{
ROS_INFO("请求正常,响应的结果:%d",add_num.response.sum);
}
else
{
ROS_ERROR("请求处理失败...");
return 1;
}
return 0;
}
add_executable(sum_server src/server.cpp)
add_executable(sum_client src/client.cpp)
target_link_libraries(sum_server
${catkin_LIBRARIES}
)
target_link_libraries(sum_client
${catkin_LIBRARIES}
)
终端执行命令验证:
1.roscore
2.rosrun service_com sum_server先启动服务
3.rosrun service_com sum_client 1 2调用客户端 :rosrun 包名 客户端 参数1 参数2
sum.srv文件内容如下:
int32 num1
int32 num2
---
int32 sum
srv文件内容包括请求和响应部分,其中num1和num2对应请求部分,sum对应响应部分,这两部分用—分开。
添加编译依赖和执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
#加入message_generation,必须有 std_msgs
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
#打开注释,并配置srv源文件sum.srv
## Generate services in the 'srv' folder
add_service_files(
FILES
sum.srv
)
#打开注释,生成消息和服务时依赖std_msgs,因为我们自定义的服务用到了std_msgs的数据类型,比如int32
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
#打开注释,增加message_runtime
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES service_com
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
为了方便代码提示以及避免误抛异常,需要先配置 vscode,将前面生成的 head 文件路径配置进 c_cpp_properties.json 的 includepath属性,增加的内容是"/home/kandi/catkin_ws/devel/include/**",
"includePath": [
"/opt/ros/noetic/include/**",
"/home/kandi/catkin_ws/src/helloworld/include/**",
"/home/kandi/catkin_ws/src/topic_pub/include/**",
"/home/kandi/catkin_ws/devel/include/**",
"/usr/include/**"
],
参数服务器,一般适用于存在数据共享的一些应用场景。
参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据,关于参数服务器的典型应用场景如下:
路径规划时,需要参考小车的尺寸,我们可以将这些尺寸信息存储到参数服务器,全局路径规划节点与本地路径规划节点都可以从参数服务器中调用这些参数
rosparam set 设置参数
rosparam get 获取参数
rosparam load 从外部文件加载参数
rosparam dump 将参数写出到外部文件
rosparam delete 删除参数
rosparam list 列出所有参数
示例:实现参数增删改查操作。
在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:
● ros::NodeHandle
● ros::param
param_set.cpp代码如下:
#include "ros/ros.h"
int main(int argc, char *argv[])
{
//初始化ROS节点
ros::init(argc,argv,"param_set");
//创建ROS节点句柄
ros::NodeHandle nh;
//参数增-----------------------------------------
//方案1:nh
nh.setParam("type1","round1");
nh.setParam("radius1",0.4);
//方案2:ros::param
ros::param::set("type2","round2");
ros::param::set("radius2",0.5);
//参数改-----------------------------------------
//方案1:nh
nh.setParam("radius1",0.45);
//方案2:ros::param
ros::param::set("radius2",0.55);
return 0;
}
param_get.cpp代码:
#include "ros/ros.h"
int main(int argc, char *argv[])
{
//设置编码
setlocale(LC_ALL,"");
//初始化ROS节点
ros::init(argc,argv,"param_get");
//初始化节点句柄
ros::NodeHandle nh;
//ros::NodeHandle------------------------------
//1.param
double radius1 = nh.param("radius1",0.6);
ROS_INFO("radius1:%.2f",radius1);
//2.getParam
double radius2 = 0.0;
//bool result = nh.getParam("radius2",radius2);
//3.getParamCached与getParam类似,只是性能上有提升,一般测试情况看不出差别
bool result = nh.getParamCached("radius2",radius2);
if(result)
{
ROS_INFO("获取的半径是:%.2f",radius2);
}
else
{
ROS_INFO("被查询的变量不存在");
}
//4.getParamNames
std::vector<std::string> names;
nh.getParamNames(names);
for (auto &&name : names)
{
ROS_INFO("遍历的元素:%s",name.c_str());
}
//5.hasParam
bool flag1 = nh.hasParam("radius1");
bool flag2 = nh.hasParam("radius3");
ROS_INFO("radius1存在吗? %d",flag1);
ROS_INFO("radius3存在吗? %d",flag2);
//6.searchParam
std::string key;
nh.searchParam("radius3",key);
ROS_INFO("搜索结果:%s",key.c_str());
//ros::param--------------------------------
double radius_param = ros::param::param("radius1",0.4);
ROS_INFO("radius_param:%.2f",radius_param);
std::vector<std::string> names_param;
ros::param::getParamNames(names_param);
for (auto &&name : names_param)
{
ROS_INFO("键:%s",name.c_str());
}
return 0;
}
param_del.cpp代码如下:
#include "ros/ros.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,"");
ros::init(argc,argv,"param_del");
ros::NodeHandle nh;
//删除:NodeHandle--------------------------
bool flag1 = nh.deleteParam("radius1");
if(flag1)
{
ROS_INFO("删除rradius1成功");
}
else
{
ROS_INFO("删除rradius1失败");
}
//删除:ros::param--------------------------
bool flag2 = ros::param::del("radius2");
if(flag2)
{
ROS_INFO("删除radius2成功");
}
else
{
ROS_INFO("删除radius2失败");
}
return 0;
}
roscore
rosrun param_server param_set
rosparam list
rosrun get /type1
rosrun get /radius1