ROS通信机制包括话题通信、服务通信和参数服务器三种通信方式,各原理及代码实现如下表
功能 | 博客链接 | 说明 |
---|---|---|
VScode配置 ROS 环境 | VScode创建ROS项目 ROS集成开发环境 | 使用VScode使用配置了ROS开发环境,实现ROS开发的第一步 |
话题通信理论 | ROS话题通信流程理论 | 介绍了话题通信机制的原理 |
ROS话题通信机制实操C++ | ROS话题通信机制实操C++ | 手把手的用C++实现了话题通信机制 |
ROS话题通信机制实操Python | ROS话题通信机制实操Python | 手把手的用Python实现了话题通信机制 |
ROS话题通信流程自定义msg格式 | ROS话题通信流程自定义msg格式 | 介绍了如何准备话题通信机制自定义msg数据格式,及相关配置 |
ROS话题通信机制自定义msg实战C++ | ROS话题通信机制自定义msg实战C++ | 手把手的用C++实现了话题通信机制自定义msg数据通信 |
ROS话题通信机制自定义msg实战Python | ROS话题通信机制自定义msg实战Python | 手把手的用Python实现了话题通信机制自定义msg数据通信 |
服务通信理论 | ROS服务通信流程理论 | 介绍了服务通信机制的原理 |
ROS服务通信自定义srv | ROS服务通信自定义srv | 介绍了如何准备服务通信机制自定义srv数据格式,及相关配置 |
ROS服务通信机制实操C++ | ROS服务通信机制实操C++ | 手把手的用C++实现了服务通信机制 |
ROS服务通信机制实操Python | ROS服务通信机制实操Python | 手把手的用Python实现了话题通信机制 |
参数服务器理论 | ROS参数服务器理论模型 | 介绍了参数服务器的原理 |
ROS参数服务器增删改查实操C++ | ROS参数服务器增删改查实操C++ | 用 C++ 实现了参数服务器的的增删改查操作 |
ROS参数服务器增删改查实操Python | ROS参数服务器增删改查实操Python | 用 Python 实现了参数服务器的的增删改查操作 |
ROS服务通信的理论查阅:ROS服务通信流程理论
ROS服务通信的自定义srv数据的准备可以查阅:ROS服务通信自定义srv
在模型实现中,ROS master
不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
- 服务端
- 客户端
- 数据
步骤流程
实现ROS服务通信的步骤分为以下5步:
- 1.
VScode
配置 - 2.编写服务端实现;
- 3.编写客户端实现;
- 4.编辑配置文件;
- 5.编译并执行。
VScode 配置
需要像之前自定义 msg
实现一样配置 c_cpp_properies.json
文件,如果以前已经配置且没有变更工作空间,可以忽略,如果需要配置,配置方式与之前相同:
服务端
-
基本流程
- 1.包含头文件;
- 2.初始化ROS节点;
- 3.创建6节点句柄;
- 4.创建一个服务对象;
- 5.处理请求并产生响应;
- 6.spin()
-
代码实现
#include "ros/ros.h" #include "plumbing_server_client/AddInts.h" /* 服务端实现:解析客户端提交的数据,并运算再进行响应 1.包含头文件; 2.初始化ROS节点; 3.创建6节点句柄; 4.创建一个服务对象; 5.处理请求并产生响应; 6.spin() */ bool doMsg(plumbing_server_client::AddInts::Request & request,plumbing_server_client::AddInts::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("求和结果:sum = %d", sum); return true; } int main(int argc, char *argv[]) { setlocale(LC_ALL, ""); // 2.初始化ROS节点; ros::init(argc, argv, "heiShui"); // 3.创建6节点句柄; ros::NodeHandle nh; // 4.创建一个服务对象; ros::ServiceServer server = nh.advertiseService("addInts", doMsg); ROS_INFO("服务器端启动"); // 5.处理请求并产生响应; // 6.spin() ros::spin(); //处理回调函数 return 0; }
-
验证是否有问题
-
编辑配置文件
修改 plumbing_server_client 功能包下的CMakeLists.txt,找到add_executable、add_dependencies和target_link_libraries,修改成如图所示:
-
按快捷键
ctrl + shift + B
编译 -
开启一个Terminal,运行 roscore 命令;再开启一个新的Terminal,运行
source ./devel/setup.bash rosrun plumbing_server_client demo_server_c
;再开启一个Terminal,运行rosservice call addInts "num1: 1 num2: 2"
(num1 num2是按Tab键自动补齐,然后修改数字),查看服务请求数据并响应
-
客户端
-
基本流程
- 1.包含头文件;
- 2.初始化ROS节点;
- 3.创建节点句柄;
- 4.创建一个客户端对象;
- 5.提交请求并处理响应
-
代码实现
#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, "daZhuang"); // 3.创建节点句柄; ros::NodeHandle nh; // 4.创建一个客户端对象; ros::ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("addInts"); // 5.提交请求并处理响应 plumbing_server_client::AddInts add; // 创建一个 AddInts 对象 // 5-1 组织请求 add.request.num1 = 100; add.request.num2 = 300; // 5-2 处理请求 bool flag = client.call(add); // 处理请求,传入AddInts对象, 返回响应结果,响应成功flag为true,失败为flase if(flag) { ROS_INFO("响应成功"); ROS_INFO("响应结果:sum = %d", add.response.sum); } else { ROS_INFO("响应失败..."); } return 0; }
编辑配置文件
修改 plumbing_server_client 功能包下的CMakeLists.txt,找到add_executable、add_dependencies和target_link_libraries,修改成如图所示:
编译并执行
-
编译代码
按快捷键 ctrl + shift + B 编译 -
执行代码
-
1.启动 roscore;
开启一个Terminal,启动 roscore
-
2.启动服务节点;
开启一个新的Terminalcd topic_ws/ source ./devel/setup.bash rosrun plumbing_server_client demo_server_c
-
3.启动客户节点。
cd topic_ws/ source ./devel/setup.bash rosrun plumbing_server_client demo_client_c
-
正常情况下的服务通信必须得先启动服务端,然后启动客户端
优化
实现参数的动态提交
前面响应的数据都是写死的,将数据实现动态的输入,主要是修改客户端代码,服务端不用修改。
格式:rosrun xxxxx xxxxxx num1 num2
代码实现
#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, ""); // 处理中文乱码
// 优化实现,获取命令中的参数
if (argc != 3)
{
ROS_INFO("传入参数的个数不对!!!");
return 1;
}
// 2.初始化ROS节点;
ros::init(argc, argv, "daZhuang");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建一个客户端对象;
ros::ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("addInts");
// 5.提交请求并处理响应
plumbing_server_client::AddInts add; // 创建一个 AddInts 对象
// 5-1 组织请求
// add.request.num1 = 100;
// add.request.num2 = 300;
add.request.num1 = atoi(argv[1]); // atoi()将字符串转成 int 类型
add.request.num2 = atoi(argv[2]);
// 5-2 处理请求
bool flag = client.call(add); // 处理请求,传入AddInts对象, 返回响应结果,响应成功flag为true,失败为flase
if(flag)
{
ROS_INFO("响应成功");
ROS_INFO("响应结果:sum = %d", add.response.sum);
}
else
{
ROS_INFO("响应失败...");
}
return 0;
}
编译后,启动 roscore
和服务端,然后启动客户端,同时输入请求的参数 num1
和 num2
,如图所示:
优化先启动客户端后启动服务端
正常情况下是先启动服务端,然后在启动客户端。如果有先启动客户端,挂起等待服务端启动后再请求的需求。就需要调用ROS的内置等待函数 client.waitForExistence()
和 ros::service::waitForService("服务话题")
。
代码实现
#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, ""); // 处理中文乱码
// 优化实现,获取命令中的参数
if (argc != 3)
{
ROS_INFO("传入参数的个数不对!!!");
return 1;
}
// 2.初始化ROS节点;
ros::init(argc, argv, "daZhuang");
// 3.创建节点句柄;
ros::NodeHandle nh;
// 4.创建一个客户端对象;
ros::ServiceClient client = nh.serviceClient<plumbing_server_client::AddInts>("addInts");
// 5.提交请求并处理响应
plumbing_server_client::AddInts add; // 创建一个 AddInts 对象
// 5-1 组织请求
// add.request.num1 = 100;
// add.request.num2 = 300;
add.request.num1 = atoi(argv[1]); // atoi()将字符串转成 int 类型
add.request.num2 = atoi(argv[2]);
// 调用判断服务器状态的函数
// client.waitForExistence(); // 创建的客户端自己有的等待函数
ros::service::waitForService("addInts"); // ros内置的等待函数,需要传入等待的服务话题
// 5-2 处理请求
bool flag = client.call(add); // 处理请求,传入AddInts对象, 返回响应结果,响应成功flag为true,失败为flase
if(flag)
{
ROS_INFO("响应成功");
ROS_INFO("响应结果:sum = %d", add.response.sum);
}
else
{
ROS_INFO("响应失败...");
}
return 0;
}
启动客户端后,没有启动服务端,结果如图所示:
再启动服务端后,请求进行响应,结果如图所示: