Ros 2-2 服务通信自定义srv调用 (C++)

需求
编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端
分析:
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:

服务端
客户端
数据

流程
1、编写服务端实现;
2、编写客户端实现;
3、编辑配置文件;
4、编译并执行。

0.VScode配置

需要像之前自定义 msg 实现一样配置c_cpp_properies.json 文件,如果以前已经配置且没有变更工作空间,可以忽略,如果需要配置,配置方式与之前相同:

{
  "configurations": [
    {
      "browse": {
        "databaseFilename": "${workspaceFolder}/.vscode/browse.vc.db",
        "limitSymbolsToIncludedHeaders": true
      },
      "includePath": [
        "/home/zxr/cartographer_ws/install_isolated/include/**",
        "/opt/ros/melodic/include/**",
        "/home/zxr/Ros_zhao/learning_topic_ws/src/pub_sub/include/**",
        "/usr/include/**",
        "/home/zxr/Ros_zhao/learning_topic_ws/devel/include/**",
        "/home/zxr/Ros_zhao/learning_topic_ws/src/server_client/include/**"
      ],
      "name": "ROS",
      "intelliSenseMode": "gcc-x64",
      "compilerPath": "/usr/bin/gcc",
      "cStandard": "gnu11",
      "cppStandard": "c++17"
    }
  ],
  "version": 4
}

"/home/zxr/Ros_zhao/learning_topic_ws/src/server_client/include/"**这个是新加上去的;

1.服务端实现

#include "ros/ros.h"
#include "serve_client/Addints.h"
/*
    服务端实现:解析客户端提交的数据,并运算再产生响应
        1、包含头文件
        2、初始化ROS节点
        3、创建节点句柄
        4、创建一个服务对象
        5、回调函数 处理请求并响应
        6、 由于请求多个,调用ros::spin()
*/
bool sum_back( serve_client::Addints::Request &req,
                                serve_client::Addints::Response &resp)
{
    int num1 = req.num1;
    int num2 = req.num2;
    ROS_INFO("服务器收到的数据为:num1 = %d, num2 = %d",num1,num2);
    //逻辑处理
    if(num1<0||num2<0 ){

        ROS_ERROR("提交的数据异常,数据不可以为负");
        return false;
    }
    resp.sum = num1+num2;
    return true;
}
int main(int argc,char **argv)
{
    setlocale(LC_ALL, "");

    //2、初始化节点
    ros::init(argc,argv,"AddInts_Server");
    //3、创建句柄
    ros::NodeHandle nh;
    //4、创建服务端对象
    ros::ServiceServer server = nh.advertiseService("AddInts", sum_back);
    ROS_INFO("服务已经启动.....");
    //5、回调函数
    ros::spin();
    return 0;
}

2.客户端实现

#include"ros/ros.h"
#include"serve_client/Addints.h"
/*
    需求: 
        编写两个节点实现服务通信,客户端节点需要提交两个整数到服务器
        服务器需要解析客户端提交的数据,相加后,将结果响应回客户端,
        客户端再解析

    服务器实现:
        1.包含头文件
        2.初始化 ROS 节点
        3.创建 ROS 句柄
        4.创建 客户端 对象
        5.请求服务,接收响应

*/
int main(int argc, char *argv[])
{
    setlocale(LC_ALL,"");
    ros::init(argc,argv,"AddInts_Client");
    //3、创建句柄
    ros::NodeHandle nh;
    //4、创建 客户端对象
    ros::ServiceClient client = nh.serviceClient<serve_client::Addints>("AddInts");
    //等待服务启动成功
    //方式1
    ros::service::waitForService("AddInts");
    //方式2:client.waitForExistence();
    //5、组织请求数据
    serve_client::Addints ai;
    ai.request.num1 = atoi(argv[1]);
    ai.request.num2 = atoi(argv[2]);
    //6、发出请求,返回bool值,标记是否成功
    bool flag = client.call(ai);
    //7、处理响应
    if(flag)
    {
        ROS_INFO("请求正常处理,响应结果为%d",ai.response.sum);
    }
    else{
        ROS_ERROR("请求失败.....");
        
    }


    return 0;
}

3.配置CMakeLists.txt

add_executable(server src/AddInts_Server.cpp)
add_executable(client src/AddInts_Client.cpp)

#注意这种gen_cpp的写法
add_dependencies(server ${PROJECT_NAME}_gencpp)
add_dependencies(client ${PROJECT_NAME}_gencpp)


target_link_libraries(server
  ${catkin_LIBRARIES}
)
target_link_libraries(client
  ${catkin_LIBRARIES}
)

4.执行

流程
需要先启动服务: rosrun 包名 可执行命名
再启动客户端::rosrun 包名 可执行名 参数1 参数2
当只启动服务端时,也可以通过rosservice喂数据,格式为:
rosservice call 服务通信名 “参数1: 参数2:”
例子:
rosservice call AddInts "num1: 7 num2: 5"

AddInts是服务端的服务ID,输入完AddInts可以按tab自动补齐后面的两个输入参数。注意::和7之间有空格不要删掉

结果
会根据提交的数据响应相加后的结果。

注意:
如果先启动客户端,那么会导致运行失败

优化:
在客户端发送请求前添加:client.waitForExistence();
或:ros::service::waitForService(“AddInts”);
这是一个阻塞式函数,只有服务启动成功后才会继续执行

此处可以使用 launch 文件优化,但是需要注意 args 传参特点

你可能感兴趣的:(ROS学习,c++)