ROS入门之通信机制及常用API


  • 💂 个人主页:风间琉璃
  • 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
  • 💬 如果文章对你有帮助欢迎关注点赞收藏(一键三连)订阅专栏

前言

提示:这里可以添加本文要记录的大概内容:

ROS的通信架构是ROS的灵魂,也是整个ROS正常运行的关键所在。ROS通信架构包括各种数据的处理,进程的运行,消息的传递等等。ROS中的通信方式有:基于发布/订阅的话题通信基于客户端/服务器的服务通信基于RPC的参数服务器,每个通信方式都有自己的特点。这里主要记录了通信架构的基础通信方式和相关概念、各种通信常用API以及其他常用API。注意C++的API一般都是重载的,所有这里介绍的是比较常用的一种API。


一、话题通信

话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布/订阅模式,一个节点发布信息,另一个节点订阅信息。这种通信方式可实现不同节点之间数据交互的通信模型,用于实时更新、少逻辑处理的数据传送场景。

1.话题通信理论

话题通信模型主要包含三个角色分别是:

  • ROS Master(管理者):负责保管Talker和Listener注册的信息,并匹配话题相同的Talker与Listener,并建立两者之间的连接
  • Talker(发布者):发布信息
  • Listener(订阅者):接收Talker的消息
    在这里插入图片描述
    具体两者之间建立连接和通信的流程可参考这个笔记:话题通信,了解即可,在ROS中我们只需要编程实现发布者和订阅者。

2.话题通信常用API

🍗发布者

advertise

advertise创建一个发布者对象,用于向 ROS 系统中的指定主题发布消息。其函数原型如下所示,这是一个函数模板

template <class M>
Publisher advertise(const std::string& topic, uint32_t queue_size, bool latch = false)
{
  AdvertiseOptions ops;
  ops.template init<M>(topic, queue_size);
  ops.latch = latch;
  return advertise(ops);
}
  • 模板参数 : 表示这个函数可以接受任意类型 M 的消息。在 ROS 中,消息类型通常是由 std_msgs 命名空间中的消息类型之一表示,比如 std_msgs::String、geometry_msgs或者自定义消息类型。
  • topic:指定发布的主题名称
  • queue_size:指定发布队列的大小。
  • latch:(可选参数)如果设置为 true,则发布器会将最新的消息保存并在新的订阅者连接时立即发送给它,否则只在有新消息发布时才发送。
publish

publish函数是ros::Publisher类中的一个成员函数,用于将消息发布到相应的主题。函数原型如下所示

template <typename M>
void publish(const M& message) const
  • 模板参数: 这是消息的类型参数,表示要发布的具体消息类型,例如std_msgs::String。

  • message: 这是要发布的具体消息对象。你需要创建一个相应类型的消息对象并填充它的数据,然后将其作为参数传递给publish函数

下面是一个例子,演示如何使用advertise和publish函数发布一个std_msgs::String类型的消息:

#include <ros/ros.h>
#include <std_msgs/String.h>

int main(int argc, char **argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node");
    ros::NodeHandle nh;

    // 创建一个发布者,发布 std_msgs::String 类型的消息到主题 "example_topic",队列大小为 10
    ros::Publisher pub = nh.advertise<std_msgs::String>("example_topic", 10);

    // 创建并填充一个 std_msgs::String 类型的消息对象
    std_msgs::String msg;
    msg.data = "Hello, ROS!";

    // 设置发送频率为10Hz
    ros::Rate r(10);

    // 循环发送消息
    while (ros::ok())
    {
        // 使用 publish 函数发布消息
        pub.publish(msg);

        // 处理ROS回调函数
        ros::spinOnce();

        // 以指定频率休眠,以满足发送频率
        r.sleep();
    }

    return 0;
}

使用rostopic echo打印输出如下
在这里插入图片描述

🍖订阅者

subscribe

subscribe函数用于创建消息订阅者,有两个作用,其一是创建一个消息订阅者,用于订阅特定主题的消息;其二是当订阅的主题上有新消息时,调用指定的回调函数来处理消息。函数原型如下所示:

template<class M>
Subscriber subscribe(const std::string& topic, uint32_t queue_size, void(*fp)(const boost::shared_ptr<M const>&), const TransportHints& transport_hints = TransportHints())
  • M(模板参数):指定消息的类型。例如,如果你想要订阅std_msgs::String类型的消息,那么M将被实例化为std_msgs::String。
  • topic:指定要订阅的主题名称
  • queue_size:指定订阅者的消息队列大小。队列用于存储尚未被处理的消息,以便稍后进行处理。
  • fp:是一个函数指针,指定一个回调函数该回调函数将在接收到新消息时被调用。回调函数的参数是一个boost::shared_ptr指向常量M类型的消息
  • transport_hints(可选参数):提供关于传输的提示,一般默认即可,例如指定使用的传输协议等。

下面是一个使用subscribe函数的简单示例,配合上面的发布者程序使用:

#include <ros/ros.h>
#include <std_msgs/String.h>

// 回调函数的定义
void messageCallback(const boost::shared_ptr<std_msgs::String const>& msg)
{
    ROS_INFO("Received message: %s", msg->data.c_str());
}

int main(int argc, char** argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node2");
    ros::NodeHandle nh;

    // 创建一个订阅者,订阅 std_msgs::String 类型的消息,回调函数为 messageCallback
    ros::Subscriber sub = nh.subscribe<std_msgs::String>("example_topic", 10, messageCallback);

    // 进入 ROS 主循环
    ros::spin();

    return 0;
}

运行结果如下:
在这里插入图片描述

3.自定义msg

在ROS通信协议中,数据载体是通信中的核心组成部分。ROS通过std_msgs库封装了一些原生的数据类型,如String、Int32、Int64、Char、Bool、Empty等。然而,这些数据类型一般只包含一个data字段,结构相对简单,对于一些复杂的数据传输,比如激光雷达的信息等,其功能上显得有些局限。

在ROS中,可以通过创建自定义消息来定义你自己的消息类型。自定义消息允许你在ROS系统中使用特定的数据结构来进行通信。以下是如何创建和使用自定义消息的一般步骤:

①创建自定义消息msg文件

功能包下新建 msg 目录,添加文件 一个.msg该文件包含自定义消息的定义。例如,假设你想创建一个名为MyCustomMsg的自定义消息,文件名可以是MyCustomMsg.msg。示例内容如下:

# MyCustomMsg.msg
string name
int64 age
int64 height

上面的例子定义了一个包含姓名、年龄和身高的自定义消息。
在这里插入图片描述

②修改package.xml文件
package.xml中添加编译依赖与执行依赖

 <build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>

在这里插入图片描述

③编辑CMakeLists.txt文件
在ROS工作空间的CMakeLists.txt文件中,确保包含以下内容,以编译并生成自定义消息:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs

## 配置 msg 源文件
add_message_files(
  FILES
  <自定义名称>.msg
)

# 生成消息时依赖于 std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)

#执行时依赖,添加message_runtime
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

注意: 官网没有在catkin_package中配置 message_runtime,经测试配置也可以。

最后,需要添加 add_dependencies 用以设置所依赖的消息相关的中间文件。

add_dependencies(可执行程序名称 ${PROJECT_NAME}_generate_messages_cpp)

编译后的中间文件查看:C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)。后续调用相关 msg 时,是从这些中间文件调用的。
在这里插入图片描述
生成的中间文件是在devel目录下,为了方便vscode编写程序,还需要设置其中间文件的路径,如下图所示,点击头文件报错的那行,然后点击小黄灯泡即可快速完成。
在这里插入图片描述
④调用中间文件

发布者代码:

#include <ros/ros.h>
#include <turtle_pos/MyCustomMsg.h>

int main(int argc, char **argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node");
    ros::NodeHandle nh;

    // 创建一个发布者,发布turtle_pos::MyCustomMsg类型的消息到主题 "example_topic",队列大小为 10
    ros::Publisher pub = nh.advertise<turtle_pos::MyCustomMsg>("example_topic", 10);

    // 创建并填充一个turtle_pos::MyCustomMsg类型的消息对象
    turtle_pos::MyCustomMsg msg;
    msg.age = 18;
    msg.height = 170;
    msg.name = "sue";

    // 设置发送频率为1000Hz
    ros::Rate r(1000);

    // 循环发送消息
    while (ros::ok())
    {
        // 使用 publish 函数发布消息
        pub.publish(msg);

        // 处理ROS回调函数
        ros::spinOnce();

        // 以指定频率休眠,以满足发送频率
        r.sleep();
    }

    return 0;
}

订阅者代码:

#include <ros/ros.h>
#include <turtle_pos/MyCustomMsg.h>

// 回调函数的定义
void messageCallback(const boost::shared_ptr<turtle_pos::MyCustomMsg const>& msg)
{
    // 打印接收到的消息内容
    ROS_INFO("Received message: name:%s, age:%d, height:%d", msg->name.c_str(), msg->age, msg->height);
}

int main(int argc, char** argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node2");
    ros::NodeHandle nh;

    // 创建一个订阅者,订阅turtle_pos::MyCustomMsg类型的消息,回调函数为 messageCallback
    ros::Subscriber sub = nh.subscribe<turtle_pos::MyCustomMsg>("example_topic", 10, messageCallback);

    // 进入 ROS 主循环,等待接收消息并调用回调函数
    ros::spin();

    return 0;
}

结果如下:
在这里插入图片描述

二、服务通信

服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A

1.服务通信理论

服务通信较于话题通信比较简单,理论模型如下图所示,该模型中涉及到三个角色:

  • ROS Master:负责记录 Server 和 Client 注册的信息,并匹配话题相同的 Server 与 Client ,帮助 Server 与 Client 建立连接
  • Talker(Server,服务端):返回响应信息
  • Listener(Client,客户端):发送请求信息

在这里插入图片描述
其整个流程可以参考如下文章:服务通信理论

注意:

1.客户端请求被处理时,需要保证服务器已经启动;(在客户端中可以使用代码解决,等待服务器启动后在进行连接)
2.服务端和客户端都可以存在多个。

2.服务通信常用API

🎆服务端

advertiseService

advertiseService 函数是创建一个ROS服务服务器对象它允许其他节点请求服务并接收相应的结果。下面是对该函数的作用和参数介绍:

template<class MReq, class MRes>
ServiceServer advertiseService(const std::string& service, bool(*srv_func)(MReq&, MRes&))

service:服务的名称,是一个字符串,用于唯一标识服务
srv_func:是一个函数指针指向处理服务请求的回调函数当有节点请求该服务时, 函数将被调用来处理请求并生成响应。该回调函数应该有两个参数,一个是请求消息的引用(MReq&),另一个是响应消息的引用(MRes&)。这个函数应该返回一个布尔值,表示服务处理是否成功。

-MReq:请求消息类型(Request Message Type),表示服务的请求消息类型
-MRes:响应消息类型(Response Message Type),表示服务的响应消息类型

注意,虽然这是一个模板函数,但是其模板参数是可选的,如果服务的请求消息和响应消息类型可以从回调函数的参数中推断出来,就可以省略模板参数。这样的情况下,ROS会通过回调函数的参数类型来自动推断消息类型。

🎇客户端

serviceClient

serviceClient 函数是 ros::NodeHandle 类中的一个成员函数,用于创建 ROS 服务客户端,用于向指定服务发送请求。下面是对这个函数的作用和参数的解释:

template<class Service>
ServiceClient serviceClient(const std::string& service_name, bool persistent = false, 
                            const M_string& header_values = M_string())

  • service_name:是服务的名称用于唯一标识服务
  • persistent:是一个布尔值,表示是否创建一个持久化的服务客户端。如果为 true,则客户端将在网络断开时自动重新连接。
  • header_values:是一个 std::map<std::string, std::string> 类型的参数,用于指定服务调用的头部信息。这可以包括认证信息、附加的元数据等。
  • 模板参数:Service是服务的类型,表示服务的请求和响应消息类型
ros::service::waitForService

waitForService 函数是 ros::NodeHandle 类中的成员函数,用于等待指定服务启动成功通常用于服务客户端在调用服务之前等待服务启动。以下是对这个函数的作用和参数的介绍:

ROSCPP_DECL bool waitForService(const std::string& service_name, ros::Duration timeout = ros::Duration(-1));
  • service_name:是服务的名称,用于唯一标识服务
  • timeout:是一个可选参数,表示等待服务的超时时间。默认值为 ros::Duration(-1),表示无限等待,直到服务启动成功或节点关闭。如果指定了超时时间,函数将在超时时返回 false。
  • 返回一个布尔值,表示等待服务是否成功启动。如果返回 true,表示服务启动成功;如果返回 false,表示等待超时或节点关闭。
waitForExistence

waitForExistence 函数是 ros::ServiceClient 类中的成员函数,用于等待服务存在,通常用于服务客户端在调用服务之前等待服务的注册。以下是对这个函数的作用和参数的介绍:

bool waitForExistence(ros::Duration timeout = ros::Duration(-1));
  • timeout:是一个可选参数,表示等待服务存在的超时时间。默认值为 ros::Duration(-1),表示无限等待,直到服务存在或节点关闭。如果指定了超时时间,函数将在超时时返回 false。
  • 返回一个布尔值,表示等待服务是否成功存在。如果返回 true,表示服务存在;如果返回 false,表示等待超时或节点关闭。
call

call 函数是 ros::ServiceClient 类中的成员函数,用于向服务发送请求并等待响应。下面是对这个函数的作用和参数介绍:

template<class Service>
bool call(Service& service)
  • Service:表示服务的请求和响应消息类型
  • service:是服务请求的对象,它包含了请求消息的数据。在调用该函数时,这个对象将被填充为服务的请求消息,同时也可以包含用于接收服务响应的字段
  • 返回一个布尔值,表示服务调用是否成功。如果返回 true,表示服务调用成功;如果返回 false,表示服务调用失败。

调用流程:

  • 调用 client.call(service) 时,客户端将发送请求消息(service 对象中的数据)给服务端,并阻塞等待服务端的响应

  • 如果服务调用成功(服务端成功处理了请求),call 函数返回 true,同时服务端的响应消息被填充到 service 对象中,你可以通过 service.response 获取响应的数据。

  • 如果服务调用失败,call 函数返回 false,通常表示服务端未能成功处理请求或服务不存在等原因。

3.自定义srv

srv 文件内的可用数据类型msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:

①定义srv文件
服务通信中,数据分成两部分,请求响应,在 srv 文件中请求和响应使用---分割,具体实现如下:
功能包下新建 srv 目录,添加 xxx.srv 文件,内容:

# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum

在这里插入图片描述
②修改package.xml文件
package.xml中添加编译依赖执行依赖

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

在这里插入图片描述
③编辑CMakeLists.txt文件

在CMakeLists.txt编辑 srv 相关配置。

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs

## Generate services in the 'srv' folder
add_service_files(
  FILES
  addint.srv
)
## Generate added messages and services with any dependencies listed here
generate_messages(
  DEPENDENCIES
  std_msgs
)

注意: 官网没有在catkin_package中配置 message_runtime,经测试配置也可以,具体配置可见自定义msg,这里就懒得配置啦。

最后,需要添加 add_dependencies 用以设置所依赖的消息相关的中间文件,注意后缀名与msg的区别

add_dependencies(可执行程序名称${PROJECT_NAME}_gencpp)

编译后的中间文件查看:C++ 需要调用的中间文件(…/工作空间/devel/include/包名/xxx.h)。后续调用相关 srv 时,是从这些中间文件调用的。
在这里插入图片描述
生成的中间文件是在devel目录下,为了方便vscode编写程序,还需要设置其中间文件的路径,如下图所示,点击头文件报错的那行,然后点击小黄灯泡即可快速完成。具体见自定义msg。

④调用中间文件

服务端代码:

#include <ros/ros.h>
#include <serve_client/addint.h>

// 回调函数的定义
bool addIntsCallback(serve_client::addint::Request& req, serve_client::addint::Response& res)
{
    // 打印服务器接收到的请求数据
    ROS_INFO("服务器接收到的请求数据为: num1 = %d, num2 = %d", req.num1, req.num2);

    // 执行服务请求的操作(在这个例子中,将两个整数相加)
    res.sum = req.num1 + req.num2;
    
    // 返回 true 表示服务处理成功
    return true;
}

int main(int argc, char** argv)
{
    setlocale(LC_ALL, "");

    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node");
    ros::NodeHandle nh;

    // 创建一个服务服务器,服务名称为 "AddInts",回调函数为 addIntsCallback,二选一即可,具体原因见advertiseService那里
    //ros::ServiceServer server = nh.advertiseService("AddInts", addIntsCallback);
    ros::ServiceServer server = nh.advertiseService<serve_client::addint::Request, serve_client::addint::Response>("AddInts", addIntsCallback);

    // 输出服务器启动成功的消息
    ROS_INFO("服务器启动成功");

    // 进入 ROS 主循环,等待服务请求
    ros::spin();

    return 0;
}

客户端代码:

#include <ros/ros.h>
#include <serve_client/addint.h>

int main(int argc, char** argv)
{
    setlocale(LC_ALL, "");

    // 调用时动态传值,如果通过 launch 的 args 传参,需要传递的参数个数 +3
    if (argc != 3)
    // if (argc != 5) //launch 传参(0-文件路径 1传入的参数 2传入的参数 3节点名称 4日志路径)
    {
        ROS_ERROR("请提交两个整数");
        return 1;
    }

    // 初始化 ROS 节点
    ros::init(argc, argv, "example_client");

    // 创建 ROS 句柄
    ros::NodeHandle nh;

    // 创建服务客户端,服务名称为 "AddInts"
    ros::ServiceClient client = nh.serviceClient<serve_client::addint>("AddInts");

    // 等待服务启动成功
    //client.waitForExistence();
    ros::service::waitForService("AddInts");

    // 组织请求数据
    serve_client::addint srv;
    srv.request.num1 = atoi(argv[1]);
    srv.request.num2 = atoi(argv[2]);

    // 调用服务并等待响应
    if (client.call(srv))
    {
        // 请求正常处理,输出响应结果
        ROS_INFO("请求正常处理,响应结果: %d", srv.response.sum);
    }
    else
    {
        // 请求处理失败
        ROS_ERROR("请求处理失败");
        return 1;
    }

    return 0;
}

运行结果如下:
在这里插入图片描述
如果先启动客户端,那么会导致运行失败。在客户端发送请求前添加:client.waitForExistence()或:ros::service::waitForService(“AddInts”);这是一个阻塞式函数只有服务启动成功后才会继续执行。此处可以使用 launch 文件优化,但是需要注意 args 传参特点。

三、参数服务器

参数服务器在ROS中主要用于实现不同节点之间的数据共享。参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据。

参数服务器是ROS参数系统的一部分,它允许在运行时存储和检索参数值。参数服务器是一个分布式的键-值存储系统,用于在ROS系统中共享和存储配置参数、配置文件、常量值等信息。它是一种以共享的方式实现不同节点之间数据交互的通信模式,其主要作用是存储多节点共享的数据,类似于全局变量

1.参数服务器通信理论

参数服务器实现是最为简单的,该模型如下图所示,该模型中涉及到三个角色:

  • ROS Master (管理者),作为一个公共容器保存参数
  • Talker (参数设置者),可以向容器中设置参数
  • Listener (参数调用者),可以获取参数
    在这里插入图片描述
    整个流程可以参考如下文章:参数服务器理论

参数可使用数据类型:32-bit integers,booleans,strings,doubles,iso8601 dates,lists,base64-encoded binary data,字典。

注意:参数服务器不是为高性能而设计的,因此最好用于存储静态的非二进制的简单数据。

2.参数服务器常用API

在 C++ 中实现参数服务器数据的增删改查,可以通过两套 API 实现:ros::NodeHandleros::param

setParam

setParam 函数是ros::NodeHandle类中的成员函数,用于将一个整数值设置到ROS参数服务器上或者更新一个整数类型的参数。以下是对这个函数的作用和参数的介绍:

void setParam(const std::string& key, int i) const;
  • key:是参数的名称,是一个唯一的字符串,用于标识参数。
  • i:是要设置的整数值,将这个整数值关联到指定的参数名称上。

注意:如果参数已经存在,setParam 将更新参数的值为提供的整数值。如果参数不存在,setParam 将在ROS参数服务器上创建新的参数,并将其值设置为提供的整数值。
用法如下:

	 // 初始化 ROS 节点
    ros::init(argc, argv, "param_setter");

    // 创建 ROS 节点句柄
    ros::NodeHandle nh;

    // 将整数值 42 设置到参数服务器上,参数名称为 "my_integer_param"
    nh.setParam("my_integer_param", 42);
ros::param::set

set 函数是 ros::param 命名空间下的一个全局函数用于将整数值设置到ROS参数服务器上或更新一个整数类型的参数。以下是对这个函数的作用和参数的介绍:

ROSCPP_DECL void set(const std::string& key, int i);

参数和作用与setParam一样这里就不重复了,区别只有用法不一样,就是不需要构造节点句柄。

	// 初始化 ROS 节点
    ros::init(argc, argv, "param_setter");

    // 将整数值 42 设置到参数服务器上,参数名称为 "my_integer_param"
    ros::param::set("my_integer_param", 42);

    ROS_INFO("整数参数已经设置到参数服务器上");

其他操作函数如下:

功能ros::NodeHandleros::param
设置或修改参数值setParam(键,值)ros::param::set
获取参数值param(键,默认值)ros::param::param
获取参数值getParam(键,存储结果的变量)ros::param::get
获取参数值getParamCached键,存储结果的变量)ros::param::getCached
获取所有键(参数名称)getParamNames(std::vector<std::string>)ros::param::getParamNames
判断键是否存在hasParam(键)ros::param::has
查询键searchParam(被搜索的键,搜索结果存储变量)ros::param::search
删除键deleteParam(“键”)ros::param::del

注意:两者的参数都是一样的,所以后者的参数就省略了。

四、其他常用API

ros::init

init函数一般用于初始化ROS系统包括设置ROS参数、解析命令行参数、初始化ROS节点等。为ROS节点提供必要的运行时环境

void init(int &argc, char **argv, const std::string& name, uint32_t options = 0);
  • argc(argument count)是命令行参数的数量,包括程序名在内。
  • argv(argument vector)是一个指向命令行参数字符串数组的指针。这两个参数是从程序的命令行传递给 main() 函数的,ROS通过这两个参数获取命令行输入,包括 ROS 参数、启动文件等。
  • name 是ROS节点的名称,是一个字符串。每个ROS节点在系统中都有唯一的名称,用于在ROS网络中标识和通信。
  • options 是一个可选的参数,是一个32位的位掩码,用于设置ROS系统的一些选项一般情况下,可以使用默认值 0。

ros::spinOnce

ros::spinOnce() 是ROS C++库中的一个函数,用于让ROS节点处理一次事件循环中的回调函数和消息,执行所有挂起的回调函数以及处理收到的消息。以下是 ros::spinOnce() 函数的作用和参数介绍:

ROSCPP_DECL void spinOnce();

ros::spin

ros::spin() 函数用于启动ROS节点的事件循环,使节点开始执行回调函数、处理消息以及响应服务请求
一旦调用 ros::spin(),节点将一直运行,直到程序被手动终止或者收到中断信号。

ROSCPP_DECL void spin();

ros::spinOnce和ros::spin二者比较:

  • 相同点:二者都用于处理回调函数;

  • 不同点:ros::spin() 是进入了循环执行回调函数,而 ros::spinOnce() 只会执行一次回调函数(没有循环),在 ros::spin() 后的语句不会执行到,而 ros::spinOnce() 后的语句可以执行

ros::ok

ros::ok() 函数用于检查ROS节点是否仍然处于运行状态。当节点被关闭或者收到终止信号时,ros::ok() 返回 false,表示节点应该退出。

ros::shutdown

ros::shutdown() 函数用于关闭ROS节点,即终止ROS系统。当调用 ros::shutdown() 后,ROS节点将立即停止执行,事件循环将退出,节点将被关闭。

日志相关

ROS_DEBUG

ROS_DEBUG(message)用于输出调试级别的日志消息,通常用于输出详细的调试信息。在默认情况下,ROS_DEBUG 日志级别是禁用的,需要在启动节点时通过参数设置 --ros-args --log-level=debug 来启用。

ROS_INFO

ROS_INFO(message)用于输出信息级别的日志消息,通常用于输出程序的一般执行信息。默认情况下,ROS_INFO 是启用的,输出的日志消息会显示在控制台上

ROS_WARN

ROS_WARN(message)用于输出警告级别的日志消息,通常用于输出可能导致问题但程序仍然可以正常运行的信息。默认情况下,ROS_WARN 是启用的,输出的日志消息会以黄色字体显示在控制台上。

ROS_ERROR

ROS_ERROR(message)用于输出错误级别的日志消息,通常用于输出可能导致程序中断的问题。默认情况下,ROS_ERROR 是启用的,输出的日志消息会以红色字体显示在控制台上。

ROS_FATAL

ROS_FATAL(message)用于输出致命错误级别的日志消息,通常用于输出导致程序无法继续执行的问题。
默认情况下,ROS_FATAL 是启用的,输出的日志消息会以红色字体显示在控制台上,并且程序会退出。

如下图所示DEBUG信息没有开启所以没有输出。
在这里插入图片描述

时间相关

ros::Time::now

ros::Time::now() 用于获取当前时刻的时间戳,通常在ROS程序中用于记录事件发生的时间、计算时间间隔或者设置消息的时间戳。

#include <ros/ros.h>

int main(int argc, char** argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node");

    // 获取当前时刻的时间戳
    ros::Time current_time = ros::Time::now();

    // 使用当前时间戳设置消息的时间戳
    std_msgs::String msg;
    msg.data = "Hello, ROS!";
    msg.header.stamp = current_time;

    // 打印当前时间戳
    ROS_INFO("Current timestamp: %f", current_time.toSec());

    // ... 其他ROS相关的操作 ...

    return 0;
}

ros::Duration

ros::Duration 是ROS中用于表示时间间隔的类它用于表示两个 ros::Time 时间点之间的时间差ros::Duration 对象可以用于延时等操作,也可以与 ros::Time 一起用于计算时间间隔。

#include <ros/ros.h>

int main(int argc, char** argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node");

    // 获取当前时刻
    ros::Time start_time = ros::Time::now();

    // 创建一个持续时间对象,表示3秒钟
    ros::Duration duration(3.0);

    // 计算结束时间
    ros::Time end_time = start_time + duration;

    // 打印开始时间和结束时间
    ROS_INFO("Start time: %f", start_time.toSec());
    ROS_INFO("End time: %f", end_time.toSec());

    // 休眠3秒钟
    duration.sleep();
    ROS_INFO("After sleeping for 3 seconds");

    return 0;
}

ros::Rate

ros::Rate 是ROS中用于控制循环执行频率的类。它允许在循环中保持固定的执行频率,用于控制程序的执行速率,特别是在发布和订阅消息时,以及执行需要特定时间间隔的任务

#include <ros/ros.h>

int main(int argc, char** argv)
{
    // 初始化 ROS 节点
    ros::init(argc, argv, "example_node");

    // 设置期望的执行频率为10赫兹
    ros::Rate rate(10);

    while (ros::ok())
    {
        // 在这里执行循环中的操作

        // 休眠,以达到指定的频率
        rate.sleep();
    }

    return 0;
}

createTimer

createTimer 函数用于在节点中创建一个计时器(timer),允许用户指定时间间隔、回调函数和计时器的触发方式。计时器的触发会导致回调函数的执行允许在特定时间间隔内执行一些周期性的操作。以下是 createTimer 函数的参数介绍和函数作用:

  Timer createTimer(Duration period, const TimerCallback& callback, bool oneshot = false,
                    bool autostart = true) const;
  • period:指定计时器的时间间隔,即回调函数将以这个时间间隔的频率执行。
  • callback:回调函数,即在计时器触发时执行的函数。
  • oneshot(可选,默认值为 false):如果设置为 true,计时器将只触发一次;如果设置为 false,计时器将一直周期性触发。
  • autostart(可选,默认值为 true):如果设置为 true,计时器在创建后将立即启动;如果设置为 false,需要调用 timer->start() 手动启动计时器。
#include <ros/ros.h>

void timerCallback(const ros::TimerEvent& event)
{
    // 在这里执行计时器触发时的操作
    ROS_INFO("Timer triggered");
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "example_node");
    ros::NodeHandle nh;

    // 创建一个1秒触发一次的计时器,并指定回调函数
    ros::Timer timer = nh.createTimer(ros::Duration(1.0), timerCallback);

    // 进入ROS事件循环,必须是这个才行
    ros::spin();

    return 0;
}

小结:
1.三种通信机制比较:
三种通信机制中,参数服务器是一种数据共享机制,可以在不同的节点之间共享数据话题通信与服务通信是在不同的节点之间传递数据的,三者是ROS中最基础也是应用最为广泛的通信机制。这其中,话题通信和服务通信有一定的相似性也有本质上的差异,在此将二者做一下简单比较。

二者的实现流程是比较相似的,都是涉及到四个要素:

  • 要素1: 消息的发布方/客户端(Publisher/Client)
  • 要素2: 消息的订阅方/服务端(Subscriber/Server)
  • 要素3: 话题名称(Topic/Service)
  • 要素4: 数据载体(msg/srv)

两个节点通过话题关联到一起,并使用某种类型的数据载体实现数据传输。二者的实现也是有本质差异的,具体比较如下:
在这里插入图片描述

2.ROS 架构中的主要组件:

  • 节点(Nodes): 节点是 ROS 架构的基本单位,通常是一个执行特定运算任务的独立进程。每个节点可以执行感知、控制、规划等不同功能。节点之间通过消息进行通信。

  • 主题(Topics): 主题是节点之间进行消息传递的通道。一个节点可以发布(publish)消息到一个主题,而其他节点可以订阅(subscribe)这个主题来接收该消息。主题是一种异步通信机制,多个节点可以同时发布和订阅同一个主题。

  • 消息(Messages): 消息是在节点之间传递的数据单元。节点可以根据定义的消息类型创建消息实例,并通过主题发布或接收这些消息。消息可以是包含任意数据的结构化数据类型,如图像、点云、传感器数据等,每一个消息都是一种严格的数据结构,支持标准数据类型(整型、浮点型、布尔型等),也支持嵌套结构和数组(类似于C语言的结构体struct),还可以根据需求由开发者自主定义。

  • 服务(Services): 服务提供了节点之间的一对一通信机制,类似于传统的客户端-服务器模型。一个节点可以提供一个服务,而其他节点可以请求该服务并等待响应。与主题不同,服务是一种同步通信机制,请求节点会等待直到收到响应。

  • 参数服务器(Parameter Server): 参数服务器也称节点管理器是一个全局的、分布式的键值存储系统,用于存储节点的配置参数。节点可以读取和写入参数服务器中的值,以便在运行时调整系统的行为。

ROS 架构实现了高度的灵活性和可扩展性。开发人员可以将不同的节点组合在一起,构建复杂的机器人系统,并实现各种功能和算法的集成和协同工作。

结束语

感谢阅读吾之文章,今已至此次旅程之终站 🛬。

吾望斯文献能供尔以宝贵之信息与知识也 🎉。

学习者之途,若藏于天际之星辰🍥,吾等皆当努力熠熠生辉,持续前行。

然而,如若斯文献有益于尔,何不以三连为礼?点赞、留言、收藏 - 此等皆以证尔对作者之支持与鼓励也 💞。

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Super.Bear

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值