ROS2服务通信(C++实现)

最近在学习赵虚左老师的ROS2课程,为此记录学习过程

目录

一、自定义接口

二、服务端实现

完整代码

三、客户端实现

完整代码

四、总结:


一、自定义接口

1.自己创建一个.srv文件,里面放置变量属性和变量名,这里我放在了base_interfaces_demo/srv中。

其中分隔符--- 用于明确区分请求和响应部分。具体来说:

请求部分(Request): --- 之前的部分定义了服务请求的参数。这个部分包含了客户端在调用服务时需要发送的数据。在下面的例子中,num1 和 num2 是客户端发送的请求参数。

响应部分(Response):在 --- 之后的部分定义了服务响应的参数。这个部分包含了服务器在处理请求后返回的数据。在下面的例子中,sum 是服务器计算后返回的结果。

2.修改xml文件,插入依赖,要根据自己的功能包名称进行修改

  <!-- 编译依赖 -->
  <build_depend>rosidl_default_generators</build_depend>
  <!-- 执行依赖 -->
  <exec_depend>rosidl_default_runtime</exec_depend>
  <!-- 声明当前包所属的组 -->
  <member_of_group>rosidl_interface_packages</member_of_group>

3.编辑txt文件,添加功能包目录下的对应文件

find_package(rosidl_default_generators REQUIRED)

#为接口文件生成源码/************/
rosidl_generate_interfaces( ${PROJECT_NAME}
  "srv/AddInts.srv"
)

4.编译并测试文件

colcon build --packages-select base_interfaces_demo
ros2 interface show base_interfaces_demo/srv/AddInts

出现这个说明接口配置成功

二、服务端实现

1.先直接导入之前配置的模板,模板之前文章提到过,添加需要用到的头文件,将类名称与节点名称修改

2.声明服务端,放到private中

rclcpp::Service<AddInts>::SharedPtr server_;

3.创建服务端

server_ = this->create_service<AddInts>("addints",std::bind(&ADDIntServer::add_callback,this,_1,_2));

"addints"表示话题名称

std::bind(&ADDIntServer::add_callback,this,_1,_2)表示回调函数引用ADDIntServer类中的add_callback,由当前对象调用,_1,_2表示占位符

4.回调函数

void add_callback(const AddInts::Request::SharedPtr req,const AddInts::Response::SharedPtr res)
{
    res->sum = req->num1 + req->num2;
    RCLCPP_INFO(this->get_logger(),"%d + %d = %d",req->num1,req->num2,res->sum);
}

通常,回调函数的第一个参数会是请求消息(输入参数),第二个参数会是响应消息(输出参数),请求消息用于接收客户端发送的数据,而响应消息则用于填充服务器处理后的结果。

验证结果:

判断代码是否正确可以进行编译运行以下,然后开启新的终端,配置source之后使用

ros2 service call /addints base_interfaces_demo/srv/AddInts {"'num1': 10,'num2': 30"}

如果正确

完整代码

//包含头文件

#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using std::placeholders::_1;
using std::placeholders::_2;

// 自定义节点类
class ADDIntServer :public rclcpp::Node
{
    public:
        ADDIntServer():Node("add_int_server_node")
        {
            RCLCPP_INFO(this->get_logger(),"创建服务端");
            server_ = this->create_service<AddInts>("addints",std::bind(&ADDIntServer::add_callback,this,_1,_2));
        }
    private:
        void add_callback(const AddInts::Request::SharedPtr req,const AddInts::Response::SharedPtr res)
        {
            res->sum = req->num1 + req->num2;
            RCLCPP_INFO(this->get_logger(),"%d + %d = %d",req->num1,req->num2,res->sum);
        }
        rclcpp::Service<AddInts>::SharedPtr server_;

};

int main(int argc,char **argv)
{
    // 初始化ROS2客户端
    rclcpp::init(argc,argv);
    //调用spin函数,传入指针
    rclcpp::spin(std::make_shared<ADDIntServer>());
    //释放资源
    rclcpp::shutdown();
    return 0;
}

三、客户端实现

客户端的代码比较多,具体流程为

获取服务端参数,检查输入参数是否正确 -> 连接话题 -> 发送请求 -> 获取服务端返回的数据 -> 数据响应客户端 

首先还是先将模板放上去,添加需要用到的头文件,修改对应类与节点名称

1.检查输入参数是否正确,如果正确进行之后进行初始化并创建一个客户端

    if(argc != 3)
    {
        RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个参数");
        return 0;
    }

    rclcpp::init(argc,argv);
    auto client = std::make_shared<ADDIntClient>();

.2.客户端尝试连接话题服务,连接成功返回true,连接失败返回false

bool flag = client->connect_serve();

if(!flag)
{
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败");
}

/*********************************/

// 创建连接函数,放在class的public中
bool connect_serve()
{
    while(!client_-> wait_for_service(1s))
    {
        //判断是否按下ctrl + c,使用的是rclcpp::getlogger
            if(!rclcpp::ok())
            {
            // 因为按下ctrl + c 就会释放资源,此时clinet不存在了,所以不能用this->getlogger()
            // 而rclcpp::get_logger不依赖于context的
            RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"取消连接");
            return false;
            }
        RCLCPP_INFO(this->get_logger(),"等待连接中"); 
    }
    return true;
}

如果没有按下ctrl +c ,便会一直等待连接,否则直接return返回,这里使用rclcpp而不是this是因为ctrl +c之后便会释放内存,ADDIntClient也会消失,这是如果还是调用this的话便会报错

3.连接成功后发送请求

发送请求使用到的是async_send_request这个函数,输入参数为一个请求输出为一个FutureAndRequestId instance

我们需要调用ADDIntClient类的函数,将返回值传给future

auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));

/******************************************/


//创建请求函数
rclcpp::Client<AddInts>::FutureAndRequestId send_request(int num1,int num2)
{
    /*
    rclcpp::Client<base_interfaces_demo::srv::AddInts>
    */
    auto request = std::make_shared<AddInts::Request>();
    request->num1 = num1;
    request->num2 = num2;
    return client_->async_send_request(request);
}
private:
    rclcpp::Client<AddInts>::SharedPtr client_;

我们直接构建一个函数,其功能是将我们之前传入的两个参数放到reques中,发送给 async_send_request,future得到相应的FutureAndRequestId。

4.判断客户端是否得到相应

需要调用spin_until_future_complete函数,输入参数为客户端与future,返回值由success 、 timeout 和 interrupted三个,我们只需要看是否成功即可

//处理响应
if(rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)
{
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应成功,sum=%d",future.get()->sum);

}
else
{
    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应失败");
}

将服务端开启,运行客户端时附带两个参数,正确结果如下图

完整代码

//包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"

using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;
// 自定义节点类

class ADDIntClient :public rclcpp::Node
{
    public:
        ADDIntClient():Node("add_int_client_name")
        {
            RCLCPP_INFO(this->get_logger(),"客户端创建");
            client_ = this->create_client<AddInts>("addints");
        }
        // 创建连接函数
        bool connect_serve()
        {
            while(!client_-> wait_for_service(1s))
            {
                //判断是否按下ctrl + c,使用的是rclcpp::getlogger
                 if(!rclcpp::ok())
                 {
                    // 因为按下ctrl + c 就会释放资源,此时clinet不存在了,所以不能用this->getlogger()
                    // 而rclcpp::get_logger不依赖于context的
                    RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"取消连接");
                    return false;
                 }
                RCLCPP_INFO(this->get_logger(),"等待连接中"); 
            }
            return true;
        }
        //创建请求函数
        rclcpp::Client<AddInts>::FutureAndRequestId send_request(int num1,int num2)
        {
            
            //rclcpp::Client<base_interfaces_demo::srv::AddInts>
            
            auto request = std::make_shared<AddInts::Request>();
            request->num1 = num1;
            request->num2 = num2;
            return client_->async_send_request(request);
        }
    private:
        rclcpp::Client<AddInts>::SharedPtr client_;
  
};

int main(int argc,char **argv)
{
    // 初始化ROS2客户端
    if(argc != 3)
    {
        RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个参数");
        return 0;
    }

    rclcpp::init(argc,argv);

    auto client = std::make_shared<ADDIntClient>();

    bool flag = client->connect_serve();

    if(!flag)
    {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败");
    }
    auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));
    //处理响应
    if(rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)
    {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应成功,sum=%d",future.get()->sum);

    }
    else
    {
        RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应失败");
    }
    //释放资源
    rclcpp::shutdown();
    return 0;
}

四、总结:

服务端主要是构建一个回调函数,客户端在输入两个参数且连接到服务后,将参数传入话题后,由服务端得到最终值并返回到客户端,客户端在进行最后的响应

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值