1. 基本概念
- 节点(Node)
- 一个节点就表示一个进程
- 多节点、端到端、分布式通信机制
- 消息(Message)
- 节点之间通过订阅和发布传递的数据
- 可以使用ROS提供的消息类型,也可以使用 .msg 文件自定义消息类型
- 话题(Topic)
- 消息通过发布/订阅(Publish/Subscribe)方式传递
- 发布者节点(Talker)针对一个给定的话题发布消息,订阅者节点(Listener)订阅某个话题及其特定数据
- 话题通信是【异步通信】
- 无反馈,有缓冲,弱实时,节点关系多对多,适于数据传输
- 服务(Service)
- 基于服务器/客服端(Sercer/Client)模型
- 可以使用 ROS 提供的服务类型,也可以使用 .srv 文件自定义
- 服务通信是【同步通信】
- 有反馈,无缓冲,强实时,节点关系一对多,适于逻辑处理
- 节点管理器(ROS Master)
- 统筹管理所有节点,进行节点间的查找、连接等
- 为系统提供参数服务器,管理全局参数
- 节点管理器也体现了ROS的弊端:ROS Master 如果 broken down,整个系统将崩溃
2. 话题通信
注:案例、代码来自胡春旭《ROS机器人开发实践》
2.1 创建 Publisher
#include <sstream>
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "talker");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
// 设置循环的频率
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
// 初始化std_msgs::String类型的消息
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
// 发布消息
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
// 循环等待回调函数
ros::spinOnce();
// 按照循环频率延时
loop_rate.sleep();
++count;
}
return 0;
}
2.2 创建 Subscriber
#include "ros/ros.h"
#include "std_msgs/String.h"
// 接收到订阅的消息后,会进入消息回调函数
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
// 将接收到的消息打印出来
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
// 初始化ROS节点
ros::init(argc, argv, "listener");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个Subscriber,订阅名为chatter的topic,注册回调函数chatterCallback
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
// 循环等待回调函数
ros::spin();
return 0;
}
2.3 编写 CMakeLists.txt
CMakeLists.txt
文件是 CMake 的编译规则文件,用 catkin 命令创建功能包时会自动生产该文件,预置了一些编译选项,包含详细注释,稍作修改即可
cmake_minimum_required(VERSION 2.8.3)
project(learning_communication)
# include_directories 用于设置头文件的相对路径
# 全局路径默认为功能包的所在目录
include_directories(
include
${catkin_INCLUDE_DIRS}
)
# add_executable 用于设置需要编译的代码和生产的可执行文件
# 参数1:可执行文件名称,参数2:源文件 cpp
add_executable(talker src/talker.cpp)
add_executable(listener src/listener.cpp)
# target_link_libraries 用于设置链接库(第三方库)
target_link_libraries(talker ${catkin_LIBRARIES})
target_link_libraries(listener ${catkin_LIBRARIES})
# add_dependencies 用于设置依赖
add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)
- 工作空间路径下,
catkin_make
进行编译 - 编译之后,可执行文件
talker
和listener
放置在工作空间/devel/lib/<package_name>
路径下
2.4 执行
roscore
rosrun <package_name> talker
rosrun <package_name> listener
2.5 自定义话题消息
(1)编写 msg 消息文件,<package_name>/msg/Person.msg
string name
uint8 sex
uint8 age
uint8 unknown = 0
uint8 male = 1
uint8 female = 2
(2)msg 文件编译设置
- 在
package.xml
中添加包依赖
<build_depend>message_generation</build_depend>
# 部分ROS版本,用 exec_depend 代替 run-depend
<run_depend>message_runtime</run_depend>
- 在
CMakeLists.txt
文件中添加编译选项
# 在 find_package 中添加 message_generation
find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
rospy
std_msgs
message_generation
)
# catkin 依赖设置
catkin_package(
# INCLUDE_DIRS include
CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime
)
# 设置需要编译的 msg 文件
add_message_files(
FILES
Person.msg
)
generate_messages(
DEPENDENCIES
std_msgs
)
- 编译后,可用如下命令进行查看
rosmsg show Person
3. 服务 Service
3.1 自定义服务数据
(1)编写 srv 文件,<package_name>/srv/AddTwoInts.srv
int64 a
int64 b
---
int64 sum
(2)srv 文件编译设置
- 在
package.xml
中添加包依赖(同自定义消息)
<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>
- 在
CMakeLists.txt
文件中添加编译选项
# 在 find_package 中添加 message_generation
find_package(catkin REQUIRED COMPONENTS
geometry_msgs
roscpp
rospy
std_msgs
message_generation
)
# catkin 依赖设置
catkin_package(
# INCLUDE_DIRS include
CATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime
)
# 设置需要编译的 srv 文件
add_service_files(
FILES
AddTwoInts.srv
)
3.2 创建 Server
路径:<package_name>/src/server.cpp
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
// service回调函数,输入参数req,输出参数res
bool add(learning_communication::AddTwoInts::Request &req,
learning_communication::AddTwoInts::Response &res)
{
// 将输入参数中的请求数据相加,结果放到应答变量中
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "add_two_ints_server");
// 创建节点句柄
ros::NodeHandle n;
// 创建一个名为add_two_ints的server,注册回调函数add()
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
// 循环等待回调函数
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
3.3 创建 Client
路径:<package_name>/src/server.cpp
#include <cstdlib>
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
int main(int argc, char **argv)
{
// ROS节点初始化
ros::init(argc, argv, "add_two_ints_client");
// 从终端命令行获取两个加数
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
// 创建节点句柄
ros::NodeHandle n;
// 创建一个client,请求add_two_int service
// service消息类型是learning_communication::AddTwoInts
ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
// 创建learning_communication::AddTwoInts类型的service消息
learning_communication::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
// 发布service请求,等待加法运算的应答结果
if (client.call(srv))
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
3.4 编写 CMakeLists.txt
文件
add_executable(server src/server.cpp)
target_link_libraries(server ${catkin_LIBRARIES})
add_dependencies(server ${PROJECT_NAME}_gencpp)
add_executable(client src/client.cpp)
target_link_libraries(client ${catkin_LIBRARIES})
add_dependencies(client ${PROJECT_NAME}_gencpp)
3.5 编译运行
catkin_make
roscore
rosrun <package_name> server
rosrun <package_name> client 2 4