ROS自定义接口解析

一、自定义接口创建

1.接口的理解

接口(Interface) 是一种用于节点间通信的数据结构,是各节点之间进行交流的统一标准。如使用加速度传感器时,用其定义好的规范的数据接口,用温度传感器时,使用其定义好的规范的数据接口。

2.接口分类

ROS 2 提供了三种主要的接口类型:
消息(Message):用于发布 和 订阅。文件为.msg
服务(Service):用于客户端-服务端的请求/响应。 文件为.srv
动作(Action):用于长时间执行的任务,具有反馈和结果。 文件为.action

3.数据类型

基础的数据类型为:可以加上[]成为数组

bool
byte
char
float32,float64
int8,uint8
int16,uint16
int32,uint32
int64,uint64
string

也可以在已有的接口类型上进行包含,

uint32 id
string image_name
sensor_msgs/Image  #系统自带的消息接口

通过ROS2的IDL模块,将.msg .srv .action文件变为Python和C++的头文件,有了头文件,我们就可以在程序里导入并使用这个接口模块。

4.自定义接口

机器人节点:移动指定距离,移动完成后返回当前位置,对外发布机器人的位置和状态

控制机器人节点:控制机器人移动指定距离,获取机器人的当前位置和状态,通过服务完成

1.MoveRobot.src文件

# 请求部分(客户端发送给服务端)
float32 distance

---

# 响应部分(服务端返回给客户端)
float32 pose

这里一定要用---进行分隔。

该接口主要是控制节点控制机器人移动时使用,控制节点作为客户端发送指令给服务端机器人,要求机器人移动指定的距离。

机器人节点接收到request后执行操作,并将移动完成后的当前位置返回给客户端控制节点。

2.RobotStatus.msg文件

uint32 STATUS_MOVEING = 1
uint32 STATUS_STOP = 2
uint32 status
float32 pose

该文件使用基础数据类型,主要用于话题通信中,该文件就是话题topic的消息类型。

机器人节点发布机器人的实时状态和当前位置到话题topic中,控制节点订阅该topic并获取机器人的状态和位置。

3.RobotPose.msg文件

uint32 STATUS_MOVEING = 1
uint32 STATUS_STOP = 2
uint32 status
geometry_msgs/Pose pose

该文件使用混合数据类型,也是用于 话题通信,但它使用了标准消息类型 geometry_msgs/Pose 来提供机器人当前位置的详细信息,包括 位置 和 方向,状态信息则是我们使用基础数据类型来表示。

该文件的使用和上一个文件一致。

4.自定义接口文件的转换

1.cmakelists.txt文件

find_package(rosidl_default_generators REQUIRED)
find_package(geometry_msgs REQUIRED)
# 添加下面的内容
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/RobotPose.msg"
  "msg/RobotStatus.msg"
  "srv/MoveRobot.srv"
  DEPENDENCIES geometry_msgs
)

我们需要使用rosidl_default_generators该工具,即IDL转换器。

rosidl_default_generators 是 ROS 2 中的一个包,提供了 ROS 接口定义语言(IDL) 生成工具,它负责将 .msg 和 .srv 文件转换为不同语言(如 C++、Python)可以使用的代码。

接着我们使用rosidl_generate_interfaces来告诉 CMake 要生成哪些接口。

${PROJECT_NAME}:当前的 ROS 2 包名,这里是.msg .src .msg这三个文件。

DEPENDENCIES 指定外部依赖包,这里我们的.msg文件使用混合数据类型,使用了标准消息类型 geometry_msgs/Pose,因此我们需要依赖这个包。

整体流程为:

.msg 和 .srv 文件 → rosidl_generate_interfaces → 生成代码(C++、Python) → 节点中使用

2.package.xml文件

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rosidl_default_generators</depend>
  <depend>geometry_msgs</depend>
  
  <member_of_group>rosidl_interface_packages</member_of_group> #添加这一行

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

添加这一行

<member_of_group>rosidl_interface_packages</member_of_group>

将当前包声明为 rosidl_interface_packages 组的成员,用于标记当前这个功能包主要是一个 接口定义包,通过这种方式,工具链可以针对接口包进行特殊处理,比如优化接口生成过程。

二、节点使用自定义消息接口

机器人节点example_interfaces_robot_01

控制节点example_interfaces_control_01

1.机器人节点

1.机器人类

class Robot
{
  public:
  Robot()=default;
  ~Robot()=default;
  float move_distance(float distance)
  {
    status_=example_ros2_interfaces::msg::RobotStatus::STATUS_MOVEING;
    target_pose_+=distance;
    while (fabs(target_pose_-current_pose_)>0.01)
    {
      float step=distance/fabs(distance)*fabs(target_pose_-current_pose_)*0.1;
      current_pose_+=step;
      std::cout<<"move: "<<step<<" current pose: "<<current_pose_<<std::endl;
      std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    status_=example_ros2_interfaces::msg::RobotStatus::STATUS_STOP;
    return current_pose_;
  }
  float get_current_pose() {return current_pose_;}
  int get_status() {return status_;}
  private:
  float current_pose_=0.0;
  float target_pose_=0.0;
  int status_=example_ros2_interfaces::msg::RobotStatus::STATUS_STOP;
};

该类主要用于建立一个机器人对象,实现机器人运动。

该类的框架为

成员变量:
    current_pose_,用于表示当前的位置
    target_pose_,用于表示目标位置
    status_,机器人当前的运动状态,初始值为STATUS_STOP静止状态
成员函数:
    move_distance,让机器人以一定步长向目标位置移动
    get_current_pose,获取机器人当前位置
    get_status,获取机器人状态
1.构造函数与析构函数
Robot()=default;
~Robot()=default;

使用了编译器提供的默认版本

2.move_distance 函数
float move_distance(float distance)
{
  status_ = example_ros2_interfaces::msg::RobotStatus::STATUS_MOVEING;
  target_pose_ += distance;

  while (fabs(target_pose_ - current_pose_) > 0.01)
  {
    float step = distance / fabs(distance) * fabs(target_pose_ - current_pose_) * 0.1;
    current_pose_ += step;

    std::cout << "move: " << step << " current pose: " << current_pose_ << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
  }

  status_ = example_ros2_interfaces::msg::RobotStatus::STATUS_STOP;
  return current_pose_;
}

传入的 distance,即要移动的距离。

进入函数后,首先将状态status_设置为正在移动STATUS_MOVEING,再将target_pose_加上distance。

之后进入while循环,当当前的位置current_pose_和目标位置target_pose_相差大于0.01时,进入while循环中。

计算步长step

float step = distance / fabs(distance) * fabs(target_pose_ - current_pose_) * 0.1;

distance/fabs(distance)用于得到步进方向是正还是负,

fabs(target_pose_ - current_pose_) * 0.1,以当前距离的 10% 作为步进量,确保每次移动逐渐接近目标

之后更新当前的位置current_pose_,并打印步长和当前位置

之后使用sleep_for(std::chrono::milliseconds(500)),添加 500 毫秒的延时,模拟移动过程中时间消耗。

跳出循环后,将机器人的状态设置为STATUS__STOP,并返回当前的位置current_pose_。

get_current_pose 函数返回当前位置current_pose_。

get_status 函数返回当前的状态status。

2.ExampleInterfacesRobot 类

ExampleInterfacesRobot 类主要有三个功能,

作为服务端,响应客户端控制节点的请求,控制机器人移动指定距离

作为发布者,使用定时器周期性发布机器人的当前位置和状态

类的结构为:

成员变量:
    move_robot_server_,服务端
    robot_status_publisher_,发布者
    timer_,定时器,用于定时进入回调函数
    robot,机器人类对象
成员函数:
    构造函数,创建服务端、发布者和定时器
    timer_callback(),发布者周期性进入该函数,发布机器人当前位置和状态
    handle_move_robot(),服务回调函数,接受到客户端请求后处理请求信息
1.构造函数
ExampleInterfacesRobot(std::string name):Node(name)
  {
    RCLCPP_INFO(this->get_logger(),"node has started: %s",name.c_str());
    move_robot_server_=this->create_service<example_ros2_interfaces::srv::MoveRobot>(
      "move_robot",std::bind(&ExampleInterfacesRobot::handle_move_robot,this,std::placeholders::_1,std::placeholders::_2)
    );
    robot_status_publisher_=this->create_publisher<example_ros2_interfaces::msg::RobotStatus>(
      "robot_status",
      10
    );
    timer_=this->create_wall_timer(std::chrono::milliseconds(500),std::bind(&ExampleInterfacesRobot::timer_callback,this));
  }

首先创建服务

move_robot_server_ = this->create_service<example_ros2_interfaces::srv::MoveRobot>(
  "move_robot", std::bind(&ExampleInterfacesRobot::handle_move_robot, this, std::placeholders::_1, std::placeholders::_2)
);

服务名称为"move_robot",收到请求之后进入服务函数为handle_move_robot,用于控制机器人移动。

创建发布者

robot_status_publisher_ = this->create_publisher<example_ros2_interfaces::msg::RobotStatus>("robot_status", 10);

话题topic的名称为robot_status,队列长度为10,消息的类型是我们之前定义的example_ros2_interfaces::msg::RobotStatus。

创建定时器

timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&ExampleInterfacesRobot::timer_callback, this));

定时周期为500ms,进入回调函数timer_callback。

2.timer_callback() 回调函数
void timer_callback()
  {
    example_ros2_interfaces::msg::RobotStatus message;
    message.status=robot.get_status();
    message.pose=robot.get_current_pose();
    RCLCPP_INFO(this->get_logger(),"Publishing: %f",robot.get_current_pose());
    robot_status_publisher_->publish(message);
  };

定时器每500ms进入一次timer_callback() 回调函数

首先创建了一个 RobotStatus 消息对象,它属于 example_ros2_interfaces::msg 命名空间,该对象用于存储并传输机器人状态和位置信息。

通过 Robot 类中的 get_status() 方法获取当前的机器人状态STATUS_MOVEING或者STATUS_STOP,通过 Robot 类中的 get_current_pose() 方法获取机器人的当前位置,赋值到message的结构体中。

使用RCLCPP_INFO将当前机器人位置输出到终端。

之后使用 robot_status_publisher_ 发布填充好的 RobotStatus 消息,该消息会发布到"robot_status" 话题,控制节点订阅该话题,可以接受到机器人的状态和位置。

3.handle_move_robot()函数
void handle_move_robot(const std::shared_ptr<example_ros2_interfaces::srv::MoveRobot::Request> request,
                         std::shared_ptr<example_ros2_interfaces::srv::MoveRobot::Response> response){
    RCLCPP_INFO(this->get_logger(),"cover request moveing distance: %f, current pose: %f",request->distance,robot.get_current_pose());
    robot.move_distance(request->distance);
    response->pose=robot.get_current_pose();
  };

request接口类型是MoveRobot::Request,使用智能指针shared_ptr管理其生命周期,其包含客户端发送的移动距离 (distance) 请求。

response接口类型为MoveRobot::Response,使用指针指针shared_ptr管理其生命周期,用于向客户端返回移动后的机器人位置 (pose)。

使用 RCLCPP_INFO 输出日志信息,输出request->distance即客户端请求的移动距离robot.get_current_pose()即当前机器人的位置信息。

robot.move_distance(request->distance);

之后使用机器人类的move_distance函数移动指定距离。

response->pose = robot.get_current_pose();

并向客户端返回当前机器人的位置信息。

流程图为

[main函数]
   |
   V
[初始化ROS2节点: example_interfaces_robot_01]
   |
   V
[创建 ExampleInterfacesRobot 节点实例]
   |      ├──> 创建 ROS2 服务: move_robot
   |      ├──> 创建 发布者: robot_status
   |      ├──> 创建 定时器: 每500ms发布机器人状态
   |
   V
[事件循环: rclcpp::spin]
   |
   |  ├─> [服务请求处理: handle_move_robot]
   |  |      ├──> 读取请求距离
   |  |      ├──> 调用 robot.move_distance() 逻辑执行移动
   |  |      └──> 返回最终位置
   |
   |  ├─> [定时器触发: timer_callback]
   |  |      ├──> 读取 robot 当前位置信息
   |  |      ├──> 构建 robot_status 消息
   |  |      └──> 发布消息到 /robot_status 话题
   |
   └──> 等待事件 (请求、回调等)

[节点结束: rclcpp::shutdown]

2.控制节点

控制节点代码为

#include "rclcpp/rclcpp.hpp"
#include "example_ros2_interfaces/msg/robot_status.hpp"
#include "example_ros2_interfaces/srv/move_robot.hpp"
class ExampleInterfacesControl:public rclcpp::Node
{
    public:
    ExampleInterfacesControl(std::string name):Node(name)
    {
        RCLCPP_INFO(this->get_logger(),"node has started: %s",name.c_str());
        client_=this->create_client<example_ros2_interfaces::srv::MoveRobot>("move_robot");
        robot_status_subscribe_=this->create_subscription<example_ros2_interfaces::msg::RobotStatus>("robot_status",10,std::bind(&ExampleInterfacesControl::robot_status_callback_,this,std::placeholders::_1));                                                                            
    }
    void move_robot(float distance)
    {
        RCLCPP_INFO(this->get_logger(),"request robot move %f",distance);
        while (!client_->wait_for_service(std::chrono::seconds(1)))
        {
            if(!rclcpp::ok())
            {
                RCLCPP_ERROR(this->get_logger(),"wait be interupted");
                return;
            }
            RCLCPP_INFO(this->get_logger(),"wait server online");
        }
        auto request=std::make_shared<example_ros2_interfaces::srv::MoveRobot::Request>();
        request->distance=distance;
        client_->async_send_request(
            request,std::bind(&ExampleInterfacesControl::result_callback_,this,std::placeholders::_1)
        );
        
    }

    private:
    rclcpp::Client<example_ros2_interfaces::srv::MoveRobot>::SharedPtr client_;
    rclcpp::Subscription<example_ros2_interfaces::msg::RobotStatus>::SharedPtr robot_status_subscribe_;
    void result_callback_(rclcpp::Client<example_ros2_interfaces::srv::MoveRobot>::SharedFuture result_future)
    {
        auto response=result_future.get();
        RCLCPP_INFO(this->get_logger(),"cover move result: %f",response->pose);
    }
    void robot_status_callback_(const example_ros2_interfaces::msg::RobotStatus::SharedPtr msg)
    {
        RCLCPP_INFO(this->get_logger(),"cover status pose: %f status: %d",msg->pose,msg->status);
    }
};
int main(int argc,char ** argv)
{
    rclcpp::init(argc,argv);
    auto node =std::make_shared<ExampleInterfacesControl>("example_interfaces_control_01");
    node->move_robot(5.0);
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

控制节点创建了ExampleInterfacesControl类用于控制机器人节点移动。

该类的框架为

成员变量:
    client_,服务端,用于发送控制移动的请求
    robot_status_subscribe_,订阅者,用于订阅话题获得机器人的位姿信息
成员函数:
    构造函数,创建客户端、订阅者
    move_robot,服务请求机器人移动指定的距离
    robot_status_callback_,话题回调函数,打印机器人当前状态和位置
    result_callback_,服务结果回调,获取响应中的机器人当前位置
1.构造函数

创建客户端

client_ = this->create_client<example_ros2_interfaces::srv::MoveRobot>("move_robot");

创建一个服务客户端client_,用于调用名为 move_robot 的服务,该服务是我们之前定义好的接口文件里面的MoveRobot类型。

创建订阅者

robot_status_subscribe_=this->create_subscription<example_ros2_interfaces::msg::RobotStatus>("robot_status",10,std::bind(&ExampleInterfacesControl::robot_status_callback_,this,std::placeholders::_1));

创建一个订阅者robot_status_subscribe_,用于订阅第一个参数 robot_status 的主题,接收并处理 发布者发布到话题中的RobotStatus 类型消息。第二个参数表示队列长度为10,第三个参数表示回调函数为robot_status_callback_。

2.move_robot函数
void move_robot(float distance)
{
    RCLCPP_INFO(this->get_logger(),"request robot move %f",distance);
    while (!client_->wait_for_service(std::chrono::seconds(1)))
    {
        if(!rclcpp::ok())
        {
            RCLCPP_ERROR(this->get_logger(),"wait be interupted");
            return;
        }
        RCLCPP_INFO(this->get_logger(),"wait server online");
    }
    auto request=std::make_shared<example_ros2_interfaces::srv::MoveRobot::Request>();
    request->distance=distance;
    client_->async_send_request(
        request, std::bind(&ExampleInterfacesControl::result_callback_, this, std::placeholders::_1)
    );
}

接受参数为distance,进入函数后先输出distance到终端上,然后进入循环,等待服务端的上线。

std::chrono::seconds(1) 是一个时间参数,表示最多等待 1 秒,如果1秒之内上线返回true,否则返回false。

当返回false即服务器还没上线时会进入循环内部,如果rclcpp::ok() 返回 false,说明节点被中断,程序会输出错误日志并退出,否则一直打印日志等待服务端上线到终端。

当服务端上线后,构造一个对象request,类型为example_ros2_interfaces::srv::MoveRobot::Request,这是我们之前写的接口文件内的类型。

将request的distance进行赋值,通过异步发送向指定的服务端发送请求,接收到回应后进入服务回调函数result_back。

3.服务回调函数
void result_callback_(rclcpp::Client<example_ros2_interfaces::srv::MoveRobot>::SharedFuture result_future)
{
    auto response = result_future.get();
    RCLCPP_INFO(this->get_logger(),"cover move result: %f",response->pose);
}

在异步请求完成后,result_callback_ 回调函数获取响应中的机器人当前位置,从SharedFuture 类型的对象result_future中获得response结果,并将位置信息打印。

4.话题回调函数
void robot_status_callback_(const example_ros2_interfaces::msg::RobotStatus::SharedPtr msg)
{
    RCLCPP_INFO(this->get_logger(),"cover status pose: %f status: %d",msg->pose,msg->status);
}

从订阅的消息 msg 中获取机器人当前位置信息 (pose) 和状态码 (status),msg类型为我们定义的RobotStatus类型,并将其打印到终端。

整个流程为

服务客户端:调用 move_robot 服务,控制机器人移动。
订阅者:监听 robot_status 主题,实时获取机器人状态。
回调机制:result_callback_:处理服务响应。
robot_status_callback_:处理订阅消息。
流程控制:主函数初始化节点,发起服务请求,进入消息循环 (spin)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值