在ros这种分布式框架中,各个节点可以独立的运行,但是在实际运用的多数情况下各个节点之间都需要有一定的联系或者说是信息的共享,因此在程序的设计上,各节点之间的通信尤为重要。
官方wiki教程链接:ROS Tutorials
如下所示可让我们对ros通信机制有初步的了解,其中也包括了对应通信节点的搭建。
关于工作空间的搭建参考第一篇文章 ROS学习笔记(一):安装ROS并搭建一个自己的ROS工作空间 ,这里主要记录在程序上如何实现节点通信。
话题通信是指节点间以发布订阅的方式进行通信,大体流程为发布者以一定的频率不断地在某个话题上发送消息出去,然后订阅者在ros masters这个平台上通过话题名称发现发布者发送了消息,然后接收其消息进行处理。可同时存在多个订阅者关注同一个话题,也可同时存在多个发布者在同一个话题上发布消息,当然一个订阅者也可以关注多个话题,一个发布者也可以发布多个话题。
关于话题通信,官方的C++案例链接:Writing a Simple Publisher and Subscriber (C++)
首先在该功能包下创建msg文件夹,然后在创建一个.msg文件。即:
mkdir msg
touch msg/xx.msg
该文件的编写格式为:
类型 名称
如: int32 num_hi
然后需要进入package.xml更改配置,分别添加下面两行:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
即下图所示:
然后进入CmakeLists.txt,添加如下配置,此处代表该功能包的依赖包:
这里的add_message_files打开注释后,还要将文件改成自己的msg文件,如下所示:
如下所示打开generate_messages的注释为srv文件添加std_msgs依赖。
再打开如下的注释并加入message_runtime,此处代表了find_package中的包的依赖包。
完成配置后回到工作空间主目录再次编译,然后就会生成如下所示的C++和python的msg头文件。
需要使用自定义的msg时,以上述的num.msg为例,如下所示操作即可。
#include "topics/num.h" // 调用自定义消息类型的头文件
topics::num num; // 用于引用自定义的类型
num.num_hi = xx; // 赋值
之后把num.num_hi当成正常的变量用就行了。
pub.cpp程序:
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
setlocale (LC_ALL, ""); // 防止中文乱码
std_msgs::String msg; // 定义发送数据类型
int count = 1;
ros::init(argc, argv, "talker"); // 初始化ros节点
ros::NodeHandle n; // 定义节点句柄
// 定义发布者
ros::Publisher pub = n.advertise<std_msgs::String>("what", 1000);
//ros::Rate rate(10); // 设置发布频率为10HZ
while (ros::ok()) // 节点正常运行时返回true
{
std::stringstream ss;
ss << "data" << count;
msg.data = ss.str();
pub.publish(msg); // 将话题内容msg发送出去给所有的订阅者
ROS_INFO("发布的消息为:%s", msg.data.c_str()); // 打印data
count += 1;
//rate.sleep(); // 对应频率改变睡眠时长
}
return 0;
}
程序大部分都写了注释,这些再补充记录一下:
1、ros::Publisher:定义了一个发布者对象,上文程序中的"what"为话题名称,1000为数据的缓冲区长度,<std_msgs::String>指该话题的消息类型为String类型。
2、关于那个 ros::ok() 返回flase的条件如下:
(1)、判定终端输入Ctrl - C停止程序的运行
(2)、出现同名的节点
(3)、程序调用了ros::shutdown()
(4)、节点句柄被销毁
3、ROS_INFO是printf的替代品,可用于打印某些数据至终端。
4、c_str():指向该字符数组的指针。
5、ros::Rate:如上方设置为10HZ,其会根据上次调用sleep()的时间来配置这次的睡眠时间以保持10HZ的发布速率。
sub.cpp程序:
#include "ros/ros.h"
#include "std_msgs/String.h"
// 消息回调函数
void what_callback(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, "listeners"); // 初始化ros节点
ros::NodeHandle n; // 定义节点句柄
// 定义订阅者
ros::Subscriber sub = n.subscribe("what", 1000, what_callback);
ros::spin(); // 进入循环,调用消息回调函数。
return 0;
}
程序大部分都写了注释,这些再补充记录一下:
1、ros::Subscriber:定义订阅者对象,what为话题名称,1000为数据的缓冲区长度,what_callback为消息回调函数。
2、消息回调函数:上述程序中定义了what_callback()为消息回调函数,在定义订阅者时配置为:当话题"what"有新消息发布时,调用一次消息回调函数what_callback()。
3、ros::spin():进入循环,调用消息回调函数,当ros::ok()返回 false 后退出,因此该循环类似于while (ros::ok()),可参考发布者的第2点补充。
关于配置的更改参考第一篇文章的 (3)、创建节点文件并配置编译依据文件中的更改配置文件,例如该话题通信功能包的配置更改,首先打开功能包中的CMakeLists.txt文件,然后如下所示进行添加替换即可,如下所示。
add_executable(pub src/pub.cpp)
add_executable(sub src/sub.cpp)
target_link_libraries(pub
${catkin_LIBRARIES}
)
target_link_libraries(sub
${catkin_LIBRARIES}
)
完成后回到工作空间主目录进行catkin_make编译一下,然后source一下,再运行pub和sub即可,当然工作前提是ros master运行中,即有一个终端已经运行了roscore。3个终端命令分别如下:
roscore
source ./devel/setup.bash
rosrun topics pub
source ./devel/setup.bash
rosrun topics sub
效果如下:
服务通信是指节点间以请求响应的方式进行通信,大体流程为客户端节点发送请求给服务端节点,服务端节点接收到请求后处理消息,再发出响应给客户端节点,以此实现节点间的通信,在通信和服务端处理消息期间,客户端的单线程是处在阻塞状态的。
关于服务通信,官方的C++的案例链接:Writing a Simple Service and Client (C++)
首先在该功能包下创建msg文件夹,然后在创建一个.msg文件。即:
mkdir srv
touch srv/xx.srv
该文件的编写格式为:
客户端请求
---
服务端响应
如:
int32 request_num
---
int32 response_num
然后和自定义msg一样,需要进入package.xml更改配置,分别添加下面两行:
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
然后进入CmakeLists.txt,添加如下配置,此处代表该功能包的依赖包:
然后和自定义msg不同的是,这里需要添加的配置是add_service_files,而不是add_message_files,即打开add_service_files的注释,并改成自己的srv文件,如下所示:
如下所示打开generate_messages的注释为srv文件添加std_msgs依赖。
再打开如下的注释并加入message_runtime,此处代表了find_package中的包的依赖包。
完成配置后回到工作空间主目录再次编译,然后就会生成如下所示的C++和python的srv头文件。
下文将使用该自定义的服务类型进行节点间的通信。
client.cpp程序:
#include "ros/ros.h"
#include "services/numm.h" // 此处添加的头文件为:功能包名/srv文件名.h
int main(int argc, char **argv)
{
bool flag;
setlocale (LC_ALL, ""); // 防止中文乱码
ros::init(argc, argv, "client"); // 初始化ros节点
ros::NodeHandle n; // 定义节点句柄
// 定义客户端
ros::ServiceClient client = n.serviceClient<services::numm>("why");
ros::Rate rate(1); // 设置发布频率为1HZ
services::numm srv;
srv.request.request_num = 1; // 赋请求值
while(1)
{
flag = client.call(srv); // 获取返回值并赋值给flag
if(flag)
{
ROS_INFO("请求成功,已将请求值×10,收到的响应为:%d", srv.response.response_num); // 打印出响应内容
srv.request.request_num += 1;
}
else
{
ROS_INFO("请求失败。");
}
rate.sleep(); // 对应频率改变睡眠时长
}
}
程序大部分都写了注释,这些再补充记录一下:
1、ros::ServiceClient:定义了一个客户端对象,上文程序中的"why"为服务名称,< services::numm >指服务类型,即自定义的srv文件中的服务类型。
2、client.call(srv):调用服务时将会处于堵塞状态,调用成功则返回true,response中的值为有效值;调用成功则返回flase,response中的值将为无效值。
注:一些和话题通信重复的程序在话题通信那块有注释。
service.cpp程序:
#include "ros/ros.h"
#include "services/numm.h" // 此处添加的头文件为:功能包名/srv文件名.h
// 参数1:当前功能包下的srv文件的请求,参数2:当前功能包下的srv文件的响应
bool service_callback(services::numm::Request &request, services::numm::Response &response)
{
int pending_num = request.request_num; // 获取客户端请求,赋值为待处理数据
ROS_INFO("收到的请求为:%d", pending_num); // 打印data
response.response_num = pending_num * 10; // 处理数据:×10
return true;
}
int main(int argc, char **argv)
{
setlocale (LC_ALL, ""); // 防止中文乱码
ros::init(argc, argv, "service"); // 初始化ros节点
ros::NodeHandle n; // 定义节点句柄
// 定义服务端
ros::ServiceServer service = n.advertiseService("why", service_callback);
ros::spin(); // 进入循环,调用消息回调函数。
}
程序大部分都写了注释,这些再补充记录一下:
1、ros::ServiceServer:定义服务端对象,why为话题名称,service_callback为服务回调函数。
2、服务回调函数:上述程序中定义了service_callback()为服务回调函数,在定义服务端时配置为:当话题"why"有新消息发布时,调用一次服务回调函数service_callback()。
注:一些和话题通信重复的程序在话题通信那块有注释。
关于配置的更改参考第一篇文章的 (3)、创建节点文件并配置编译依据文件中的更改配置文件,例如该话题通信功能包的配置更改,首先打开功能包中的CMakeLists.txt文件,然后如下所示进行添加替换即可,如下所示。
add_executable(cli src/client.cpp)
add_executable(ser src/service.cpp)
add_dependencies(cli ${PROJECT_NAME}_gencpp)
add_dependencies(ser ${PROJECT_NAME}_gencpp)
target_link_libraries(cli
${catkin_LIBRARIES}
)
target_link_libraries(ser
${catkin_LIBRARIES}
)
完成后回到工作空间主目录进行catkin_make编译一下,然后source一下,再运行pub和sub即可,当然工作前提是ros master运行中,即有一个终端已经运行了roscore。3个终端命令分别如下:
roscore
source ./devel/setup.bash
rosrun services ser
source ./devel/setup.bash
rosrun services cli
效果如下: