参考:
bilibili@Autolabor官方
回调函数:!
由外部中断激发而执行的函数,函数执行的时间不由函数本身控制,而是由外部激发
1、话题通信
publisher发布者,和subscriber订阅者通过topic相互连接,在同一个话题进行数据交换
publish一个0/1的信号,表示急停信号,推送到mobile_base/sensors/switch
1.1 使用场景
不断更新,逻辑处理少
1.2 作用
发布方以固定频率发送文本,订阅方接收文本并输出
1.3 话题通信的角色及其功能
master:管理者
可以根据话题建立发布者和订阅者之间的连接
listener:订阅者
talker:发布者
1.4 话题通信的流程,涉及RPC与TCP协议
1、talker在master注册一个话题advertise("话题名",RPC地址)
2、listener在master申请订阅话题subscribe("话题名")
3、若话题名匹配成功,master向listener提供话题的RPC地址
4、listener通过RPC地址远程访问talker
5、talker给listener发送响应,包含talker的TCP地址
6、listener根据TCP地址访问talker
7、talker给listener发送数据
注:注册和订阅话题没有先后顺序
1.5 发布方实现 视频040
/*
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
#include <sstream> //实现字符串拼接
#include "ros.h"
#include "stc_msgs/String.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,""); //避免中文乱码
ros::init(argc,argv,"pub"); //初始化节点
ros::NodeHandle nh; //创建Node句柄
ros::Publisher pub = nh.advertise<std_msgs::String>("em_state",10); //创建发布者对象
std_msgs::String msg; //创建发布消息
ros::Rate rate(10); // 创建发布速率为10Hz
int cnt=0;
//ros::ok()若节点存活
while(ros::ok()) //创建发布逻辑,循环发布数据
{
cnt++;
//msg.data = "hello" ;
std::stringstream ss;
ss << "hello --->" << count;
msg.data = ss.str(); //将stringstream中的数据转换成字符串
pub.publish(msg);
rate.sleep();//和ros::Rate rate(10) 配合实现每秒10次
}
return 0;
}
1.6 订阅方实现 视频42
/*
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建订阅者对象
5.处理订阅到的数据
6.声明spin函数
*/
#include <sstream> //实现字符串拼接
#include "ros.h"
#include "stc_msgs/String.h"
void doMsg(const std_msgs::String::ConstPtr &mas)
{
//通过msg获取并操作订阅到的数据
ROS_INFO("订阅的数据是:%s",msg->data.c_str()); //以日志形式输出订阅到的数据
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,""); //避免中文乱码
ros::init(argc,argv,"sub"); //初始化节点
ros::NodeHandle nh; //创建Node句柄
//ros::Subscriber sub = nh.subscribe("话题名",队列长度,回调函数名)
ros::Subscriber sub = nh.subscribe("em_state",10,doMsg); //创建订阅者对象
ros::spin(); //字面意思:回头,再次调用回调函数doMsg
return 0;
}
2 自定义msg的实现
原因:msgs只是简单的文本文件,无法对数据进行复合
特殊类型:Header
包含时间戳以及ROS中常用的坐标帧信息
1.6.1 编写msg文件
//在src/package下新建msg文件夹,放入msg文件
//Person.msg
string name
int32 age
float32 height
1.6.2 编写配置文件
<!--在package.xml文件中进行配置-->
<!--添加依赖-->
<build_depend>smessage_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
# cmake 修改
find_package(
#添加
message_generation
)
#添加
add_message_files(
FILES
Person.msg
#文件名2
)
#添加
generate_messages(
DEPENDENCIES
std_msgs
)
#添加
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
add_dependencies(包名 ${PROJECT_NAME}_generate_message_cpp)
1.6.3 发布方 视频 注意预先设置vscode
/*
发布person.msg格式的消息
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建发布者对象
5.编写发布逻辑并发布数据
*/
#include "包名/Person.h" //导入包
#include "ros.h"
#include "stc_msgs/String.h"
int main(int argc, char *argv[])
{
setlocale(LC_ALL,""); //避免中文乱码
ros::init(argc,argv,"pub"); //初始化节点
ros::NodeHandle nh; //创建Node句柄
ros::Publisher pub = nh.advertise<包名::Person>("em_state",10); //创建发布者对象
包名::Person msg; //创建发布消息
msg.name = "张三";
msg.age = 1;
msg.height = 1.65;
ros::Rate rate(1); // 创建发布速率为10Hz
while(ros::ok()) //创建发布逻辑,循环发布数据
{
msg.age+=1; // 修改数据
pub.publish(msg);
rate.sleep();//和ros::Rate rate(10) 配合实现每秒10次
ros::spinOnce(); //官方建议,目前不用
}
return 0;
}
1.6.6 订阅方
/*
发布person.msg格式的消息
1.包含头文件
2.初始化ROS节点
3.创建节点句柄
4.创建订阅者对象
5.处理订阅到的数据
6.调用spin()函数
*/
#include "包名/Person.h" //导入包
#include "ros.h"
#include "stc_msgs/String.h"
void doMsg(const 包名::Person::ConstPtr &person)
{
//通过msg获取并操作订阅到的数据
ROS_INFO("订阅的数据是:%s,%d,%.2f",person->name.c_str(),person->age,person->height); //以日志形式输出订阅到的数据
}
int main(int argc, char *argv[])
{
setlocale(LC_ALL,""); //避免中文乱码
ros::init(argc,argv,"sub"); //初始化节点
ros::NodeHandle nh; //创建Node句柄
ros::Subscriber sub = nh.Subscribe("em_state",10,doMsg); //创建发布者对象
ros::spin();
return 0;
}
2、服务通信 srv文件
server和client进行请求和相应
适用性:对实时性有要求,需要一定的逻辑处理
2.1理论模型
角色:
Master
Server
Client
流程:
master根据话题实现server和client的连接
1、server向master注册信息,话题与地址
2、client向master注册话题
3、master向client返回ROSRPC格式的地址
4、建立TCP连接,实现Clinet和Server的通信
注:必须是服务端(server)先启动和注册,之后client发送请求才有效
// 创建srv文件夹
//编写请求与响应内容
int32 num1
int32 num2
//请求跟响应隔3个-
---
int32 sum
<!---修改package.xml--->
<!---添加依赖--->
<bulid_depend>message_generation</bulid_depend>
<exec_depend>message_runtime</exec_depend>
## 修改cmakelist.txt
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
## 添加
add_service_files(
FILES
文件名.srv
)
## 添加
generate_messages(
DEPENDENCIES
std_msgs
)
## 添加
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
编译完成后生成 请求名.h、响应名.h 头文件
c++实现 ?(查看topic是否存在,检验是话题名还是服务名)
-
配置文件
devel/include路径查看
添加到 c_cpp_properties.json中的includePath中
-
server.cpp 服务端:解析处理客户端的数据并给出响应
//1. 头文件 #include "ros/ros.h" #include "包名/srv文件夹下的名称.h" bool doSums(包名::功能名::Request &request, 包名::功能名::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; return true; } int main(int argc, char *argv[]) { //节点初始化 ros::init(argc,argv,"节点名"); //创建节点句柄 ros::NodeHandler nh; //创建服务对象 ros::serviceServer 服务端名 = nh.advertiseService("话题名",回调函数(doSums)); //spin ros::spin() }
添加配置文件 CmakeList.txt
add_executable(名称 src/被执行的文件.cpp) ## 防止编译顺序引发的异常 add_dependencies(名称 ${PROJECT_NAME}_gencpp) target_link_libraries(名称 ${catkin_LIBRARIES} )
-
client.cpp 客户端
//1. 头文件 #include "ros/ros.h" #include "包名/srv文件夹下的名称.h" // main传递的是,参数数量和参数值 int main(int argc, char *argv[]) { //节点初始化 ros::init(argc,argv,"节点名"); //创建节点句柄 ros::NodeHandler nh; //创建客户端对象 ros::ServiceClient 客户端名 = nh.serverce<客户端名称::服务名>("话题名"); //组织请求(固定请求) 客户端名::服务名 ai; ai.request.num1 = 100; ai.request.num2 = 200; //组织请求(自定义传值) if(argc !=3){ ROS_INFO("args num error"); }else{ ai.request.num1 = atoi(argv[1]); ai.request.num2 = atoi(argv[2]); } //处理响应 bool flag = client.call(servise对象(ai)); if(flag) { ROS_INFO("Response TRUE"); ROS_INFO("SUM=%d",ai.response.sum); }else{ ROS_INFO("G"); } //spin ros::spin() }
添加配置文件 CmakeList.txt
add_executable(名称 src/被执行的文件.cpp) ## 防止编译顺序引发的异常 add_dependencies(名称 ${PROJECT_NAME}_gencpp) target_link_libraries(名称 ${catkin_LIBRARIES} )
客户端优化
先启动客户端时,若服务端未开启,则抛出异常,
设定:在客户端发请求时,若服务端未开启,则挂起等待服务端启动,而不是直接退出抛异常
在调用call之前加入下面命令之一
1、
client.waitForExistence()
2、
ros::service::waitForService("服务名")
3、参数服务器通信
开辟空间存放data,各个节点都可以从data中存取数据,共享模式。