【ROS2】高级:解锁 Fast DDS 中间件的潜力 [社区贡献]
目标:本教程将展示如何在 ROS 2 中使用 Fast DDS 的扩展配置功能。
教程级别:高级
时间:20 分钟
目录
背景
先决条件
在同一个节点中混合同步和异步发布
创建具有发布者的节点
创建包含配置文件的 XML 文件
执行发布者节点
创建一个包含订阅者的节点
执行订阅者节点
示例分析
使用其他 FastDDS 功能与 XML
限制匹配订阅者的数量
在主题内使用分区
配置服务和客户端
使用服务和客户端创建节点
为服务和客户端创建 XML 配置文件
执行节点
背景
ROS 2 堆栈和 Fast DDS 之间的接口由 ROS 2 中间件实现 rmw_fastrtps 提供。此实现可在所有 ROS 2 发行版中使用,无论是从二进制文件还是从源代码。
ROS 2 RMW 仅允许配置某些中间件 QoS(参见 ROS 2 QoS 策略 https://docs.ros.org/en/jazzy/Concepts/Intermediate/About-Quality-of-Service-Settings.html )。然而, rmw_fastrtps
提供了扩展的配置功能,以充分利用 Fast DDS 中的功能。本教程将通过一系列示例指导您如何使用 XML 文件解锁此扩展配置。
为了获得有关在 ROS 2 上使用 Fast DDS 的更多信息,请查看以下文档。https://fast-dds.docs.eprosima.com/en/latest/fastdds/ros2/ros2.html
sudo apt install ros-jazzy-rmw-fastrtps-cpp
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_IMPLEMENTATION=rmw_fastrtps_dynamic_cpp
RMW_IMPLEMENTATION=rmw_fastrtps_cpp ros2 run <package> <application>
RMW_IMPLEMENTATION=rmw_fastrtps_dynamic_cpp ros2 run <package> <application>
先决条件
本教程假设您知道如何创建一个包。它还假设您知道如何编写一个简单的发布者和订阅者以及一个简单的服务和客户端。尽管示例是用 C++实现的,但相同的概念也适用于 Python 包。
在同一个节点中混合同步和异步发布
在这个第一个例子中,将创建一个具有两个发布者的节点,其中一个是同步发布模式,另一个是异步发布模式。
rmw_fastrtps
默认使用同步发布模式。
在同步发布模式下,数据直接在用户线程的上下文中发送。这意味着在写操作期间发生的任何阻塞调用都会阻塞用户线程,从而阻止应用程序继续运行。然而,由于线程之间没有通知或上下文切换,这种模式通常在较低的延迟下产生更高的吞吐量。
另一方面,在异步发布模式下,每次发布者调用写操作时,数据会被复制到队列中,后台线程(异步线程)会收到有关队列中新增数据的通知,并在数据实际发送之前将线程的控制权返回给user。后台线程负责消费队列并将数据发送给每个匹配的reader。
创建带有发布者的节点
首先,在新的工作区上创建一个名为 sync_async_node_example_cpp
的新包:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp std_msgs -- sync_async_node_example_cpp
然后,向包中添加一个名为 src/sync_async_writer.cpp
的文件,内容如下。请注意,同步发布者将发布在主题 sync_topic
上,而异步发布者将发布在主题 async_topic
上。
#include <chrono> // 包含用于时间操作的头文件
#include <functional> // 包含用于函数对象和绑定的头文件
#include <memory> // 包含用于智能指针的头文件
#include <string> // 包含用于字符串操作的头文件#include "rclcpp/rclcpp.hpp" // 包含ROS 2的C++客户端库
#include "std_msgs/msg/string.hpp" // 包含标准消息类型Stringusing namespace std::chrono_literals; // 使用chrono命名空间中的字面量class SyncAsyncPublisher : public rclcpp::Node // 定义一个名为SyncAsyncPublisher的类,继承自rclcpp::Node
{
public:SyncAsyncPublisher() // 构造函数: Node("sync_async_publisher"), count_(0) // 初始化节点名称为sync_async_publisher,计数器count_初始化为0{// 创建一个同步发布者,发布到主题'sync_topic'sync_publisher_ = this->create_publisher<std_msgs::msg::String>("sync_topic", 10);// 创建一个异步发布者,发布到主题'async_topic'async_publisher_ = this->create_publisher<std_msgs::msg::String>("async_topic", 10);// 定义一个定时器回调函数,每次定时器触发时执行的操作auto timer_callback = this{// 创建一个新的消息auto sync_message = std_msgs::msg::String();sync_message.data = "SYNC: Hello, world! " + std::to_string(count_);// 将消息记录到控制台以显示进度RCLCPP_INFO(this->get_logger(), "Synchronously publishing: '%s'", sync_message.data.c_str());// 使用同步发布者发布消息sync_publisher_->publish(sync_message);// 创建一个新的消息auto async_message = std_msgs::msg::String();async_message.data = "ASYNC: Hello, world! " + std::to_string(count_);// 将消息记录到控制台以显示进度RCLCPP_INFO(this->get_logger(), "Asynchronously publishing: '%s'", async_message.data.c_str());// 使用异步发布者发布消息async_publisher_->publish(async_message);// 准备下一条消息的计数count_++;};// 创建一个定时器,每隔半秒触发一次,执行定时器回调函数timer_ = this->create_wall_timer(500ms, timer_callback);}private:// 定时器,每隔半秒触发一次,发布新的数据rclcpp::TimerBase::SharedPtr timer_;// 异步发布者rclcpp::Publisher<std_msgs::msg::String>::SharedPtr async_publisher_;// 同步发布者rclcpp::Publisher<std_msgs::msg::String>::SharedPtr sync_publisher_;// 已发送的消息数量size_t count_;
};int main(int argc, char * argv[]) // 主函数
{rclcpp::init(argc, argv); // 初始化ROS 2rclcpp::spin(std::make_shared<SyncAsyncPublisher>()); // 创建SyncAsyncPublisher节点并运行rclcpp::shutdown(); // 关闭ROS 2return 0; // 返回0表示程序正常结束
}
现在打开 CMakeLists.txt
文件,添加一个新的可执行文件并将其命名为 SyncAsyncWriter
,以便您可以使用 ros2 run
运行您的节点:
add_executable(SyncAsyncWriter src/sync_async_writer.cpp)
ament_target_dependencies(SyncAsyncWriter rclcpp std_msgs)
最后,添加 install(TARGETS…)
部分,以便 ros2 run
可以找到你的可执行文件:
install(TARGETSSyncAsyncWriterDESTINATION lib/${PROJECT_NAME})
您可以通过删除一些不必要的部分和注释来清理您的 CMakeLists.txt
,使其看起来像这样:
cmake_minimum_required(VERSION 3.8) # 设置CMake的最低版本要求为3.8
project(sync_async_node_example_cpp) # 定义项目名称为sync_async_node_example_cpp# 默认使用C++14标准
if(NOT CMAKE_CXX_STANDARD)set(CMAKE_CXX_STANDARD 14) # 如果没有设置C++标准,则设置为C++14
endif()# 如果使用GNU编译器或Clang编译器,添加编译选项
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic) # 添加编译选项:显示所有警告、额外警告和严格警告
endif()find_package(ament_cmake REQUIRED) # 查找ament_cmake包,标记为必需
find_package(rclcpp REQUIRED) # 查找rclcpp包,标记为必需
find_package(std_msgs REQUIRED) # 查找std_msgs包,标记为必需add_executable(SyncAsyncWriter src/sync_async_writer.cpp) # 添加可执行文件SyncAsyncWriter,源文件为src/sync_async_writer.cpp
ament_target_dependencies(SyncAsyncWriter rclcpp std_msgs) # 设置SyncAsyncWriter的依赖项为rclcpp和std_msgsinstall(TARGETS # 安装目标SyncAsyncWriter # 安装SyncAsyncWriterDESTINATION lib/${PROJECT_NAME}) # 安装路径为lib/${PROJECT_NAME}ament_package() # 声明ament包
如果现在构建并运行此节点,两个发布者将表现相同,两个发布者在主题中都异步发布,因为这是默认的发布模式。默认的发布模式配置可以在节点启动期间使用 XML 文件在运行时更改。
创建包含配置文件的 XML 文件
创建一个名为 SyncAsync.xml
的文件,并包含以下内容:
cxy@ubuntu2404-cxy:~/ros2_ws/src/sync_async_node_example_cpp$ gedit SyncAsync.xml
<?xml version="1.0" encoding="UTF-8" ?> <!-- XML声明,定义版本和编码 -->
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles"> <!-- 定义profiles根元素,并指定其命名空间 --><!-- 默认发布者配置文件 --><publisher profile_name="default_publisher" is_default_profile="true"> <!-- 定义一个发布者配置文件,名称为default_publisher,设置为默认配置文件 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --></publisher><!-- 默认订阅者配置文件 --><subscriber profile_name="default_subscriber" is_default_profile="true"> <!-- 定义一个订阅者配置文件,名称为default_subscriber,设置为默认配置文件 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --></subscriber><!-- sync_topic主题的发布者配置文件 --><publisher profile_name="/sync_topic"> <!-- 定义一个发布者配置文件,名称为/sync_topic --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --><qos> <!-- 定义QoS(服务质量)设置 --><publishMode> <!-- 定义发布模式 --><kind>SYNCHRONOUS</kind> <!-- 设置发布模式为同步 --></publishMode></qos></publisher><!-- async_topic主题的发布者配置文件 --><publisher profile_name="/async_topic"> <!-- 定义一个发布者配置文件,名称为/async_topic --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --><qos> <!-- 定义QoS(服务质量)设置 --><publishMode> <!-- 定义发布模式 --><kind>ASYNCHRONOUS</kind> <!-- 设置发布模式为异步 --></publishMode></qos></publisher></profiles>
请注意,定义了多个发布者和订阅者的配置文件。定义了两个默认配置文件,将 is_default_profile
设置为 true
,以及两个名称与先前定义的主题相符的配置文件: sync_topic
和另一个 async_topic
。这两个配置文件将发布模式分别设置为 SYNCHRONOUS
或 ASYNCHRONOUS
。还请注意,所有配置文件都指定了一个 historyMemoryPolicy
值,这是示例正常运行所需的值,原因将在本教程后面解释。
执行发布者节点
您需要导出以下环境变量以加载 XML:
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=~/ros2_ws/src/sync_async_node_example_cpp/SyncAsync.xml
最后,确保您已获取设置文件并运行节点:
source install/setup.bash
ros2 run sync_async_node_example_cpp SyncAsyncWriter
您应该看到发布者从发布节点发送数据,如下所示:
[INFO] [1612972049.994630332] [sync_async_publisher]: Synchronously publishing: 'SYNC: Hello, world! 0'
[INFO] [1612972049.995097767] [sync_async_publisher]: Asynchronously publishing: 'ASYNC: Hello, world! 0'
[INFO] [1612972050.494478706] [sync_async_publisher]: Synchronously publishing: 'SYNC: Hello, world! 1'
[INFO] [1612972050.494664334] [sync_async_publisher]: Asynchronously publishing: 'ASYNC: Hello, world! 1'
[INFO] [1612972050.994368474] [sync_async_publisher]: Synchronously publishing: 'SYNC: Hello, world! 2'
[INFO] [1612972050.994549851] [sync_async_publisher]: Asynchronously publishing: 'ASYNC: Hello, world! 2'
现在你有一个同步发布者和一个异步发布者在同一个节点内运行。
创建一个带有订阅者的节点
接下来,将创建一个包含订阅者的新节点,这些订阅者将监听 sync_topic
和 async_topic
发布。在名为 src/sync_async_reader.cpp
的新源文件中写入以下内容:
#include <memory> // 包含用于智能指针的头文件#include "rclcpp/rclcpp.hpp" // 包含ROS 2的C++客户端库
#include "std_msgs/msg/string.hpp" // 包含标准消息类型Stringclass SyncAsyncSubscriber : public rclcpp::Node // 定义一个名为SyncAsyncSubscriber的类,继承自rclcpp::Node
{
public:SyncAsyncSubscriber() // 构造函数: Node("sync_async_subscriber") // 初始化节点名称为sync_async_subscriber{// Lambda函数,每次接收到新消息时运行auto topic_callback = this{RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str()); // 将接收到的消息记录到控制台};// 创建一个同步订阅者,订阅主题'sync_topic'// 并将其绑定到topic_callbacksync_subscription_ = this->create_subscription<std_msgs::msg::String>("sync_topic", 10, topic_callback);// 创建一个异步订阅者,订阅主题'async_topic'// 并将其绑定到topic_callbackasync_subscription_ = this->create_subscription<std_msgs::msg::String>("async_topic", 10, topic_callback);}private:// 一个订阅'sync_topic'主题的订阅者rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sync_subscription_;// 一个订阅'async_topic'主题的订阅者rclcpp::Subscription<std_msgs::msg::String>::SharedPtr async_subscription_;
};int main(int argc, char * argv[]) // 主函数
{rclcpp::init(argc, argv); // 初始化ROS 2rclcpp::spin(std::make_shared<SyncAsyncSubscriber>()); // 创建SyncAsyncSubscriber节点并运行rclcpp::shutdown(); // 关闭ROS 2return 0; // 返回0表示程序正常结束
}
打开 CMakeLists.txt
文件,在前一个 SyncAsyncWriter
下添加一个新的可执行文件,并将其命名为 SyncAsyncReader
add_executable(SyncAsyncReader src/sync_async_reader.cpp)
ament_target_dependencies(SyncAsyncReader rclcpp std_msgs)install(TARGETSSyncAsyncReaderDESTINATION lib/${PROJECT_NAME})
执行订阅者节点
在一个终端中运行发布者节点后,打开另一个终端并导出加载 XML 所需的环境变量:
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=~/ros2_ws/src/sync_async_node_example_cpp/SyncAsync.xml
最后,确保您已获取设置文件并运行节点:
source install/setup.bash
ros2 run sync_async_node_example_cpp SyncAsyncReader
您应该看到订阅者从发布节点接收数据,如下所示:
[INFO] [1612972054.495429090] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 10'
[INFO] [1612972054.995410057] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 10'
[INFO] [1612972055.495453494] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 11'
[INFO] [1612972055.995396561] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 11'
[INFO] [1612972056.495534818] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 12'
[INFO] [1612972056.995473953] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 12'
示例分析
配置文件 XML
XML 文件定义了发布者和订阅者的几种配置。您可以拥有一个默认的发布者配置文件和几个特定主题的发布者配置文件。唯一的要求是所有发布者配置文件必须有不同的名称,并且只能有一个默认配置文件。订阅者也是如此。
为了定义特定主题的配置,只需将配置文件命名为 ROS 2 主题名称(如示例中的 /sync_topic
和 /async_topic
), rmw_fastrtps
将此配置文件应用于该主题的所有发布者和订阅者。默认配置文件由属性 is_default_profile
设置为 true
标识,并在没有其他名称与主题名称匹配的配置文件时充当回退配置文件。
环境变量 FASTRTPS_DEFAULT_PROFILES_FILE
用于通知 Fast DDS 配置文件的 XML 文件路径。
RMW_FASTRTPS_USE_QOS_FROM_XML
在所有可配置属性中, rmw_fastrtps
对 publishMode
和 historyMemoryPolicy
的处理方式不同。默认情况下,这些值在 rmw_fastrtps
实现中设置为 ASYNCHRONOUS
和 PREALLOCATED_WITH_REALLOC
,并且 XML 文件中设置的值将被忽略。为了使用 XML 文件中的值,必须将环境变量 RMW_FASTRTPS_USE_QOS_FROM_XML
设置为 1
。
然而,这还涉及另一个警告:如果设置了 RMW_FASTRTPS_USE_QOS_FROM_XML
,但 XML 文件没有定义 publishMode
或 historyMemoryPolicy
,这些属性将采用 Fast DDS 默认值而不是 rmw_fastrtps
默认值。这一点很重要,尤其是对于 historyMemoryPolicy
,因为 Fast DDS 默认值是 PREALLOCATED
,它不适用于 ROS2 主题数据类型。因此,在示例中,已明确设置了该策略的有效值( DYNAMIC
)。
rmw_qos_profile_t 的优先级
ROS 2 QoS 包含在 rmw_qos_profile_t https://docs.ros.org/en/jazzy/p/rmw/generated/structrmw__qos__profile__s.html中的 QoS 始终被遵守,除非设置为 *_SYSTEM_DEFAULT
。在这种情况下,将应用 XML 值(或在没有 XML 值的情况下应用 Fast DDS 默认值)。这意味着,如果 rmw_qos_profile_t
中的任何 QoS 设置为 *_SYSTEM_DEFAULT
以外的值,则 XML 中的相应值将被忽略。
使用其他 FastDDS 功能与 XML
虽然我们创建了一个具有不同配置的两个发布者的节点,但很难检查它们的行为是否不同。现在已经介绍了 XML 配置文件的基础知识,让我们使用它们来配置一些对节点有视觉效果的东西。具体来说,将在一个发布者上设置最大匹配订阅者数量,在另一个发布者上设置分区定义。请注意,这些只是通过 XML 文件可以调整的所有配置属性中的一些非常简单的示例。请参阅*Fast DDS*文档https://fast-dds.docs.eprosima.com/en/latest/fastdds/xml_configuration/xml_configuration.html#xml-profiles 以查看可以通过 XML 文件配置的属性的完整列表。
限制匹配订阅者的数量
将最大数量的匹配订阅者添加到 /async_topic
发布者配置文件。它应该看起来像这样:
<!-- async_topic主题的发布者配置文件 -->
<publisher profile_name="/async_topic"> <!-- 定义名为/async_topic的发布者配置文件 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --><qos> <!-- 定义QoS(服务质量)设置 --><publishMode> <!-- 定义发布模式 --><kind>ASYNCHRONOUS</kind> <!-- 设置发布模式为异步 --></publishMode></qos><matchedSubscribersAllocation> <!-- 定义匹配订阅者的分配策略 --><initial>0</initial> <!-- 初始分配的订阅者数量为0 --><maximum>1</maximum> <!-- 最大分配的订阅者数量为1 --><increment>1</increment> <!-- 每次增加的订阅者数量为1 --></matchedSubscribersAllocation>
</publisher>
匹配订阅者的数量被限制为一个。
现在打开三个终端,不要忘记源化设置文件并设置所需的环境变量。在第一个终端上运行发布者节点,在另外两个终端上运行订阅者节点。您应该看到只有第一个订阅者节点接收到来自两个主题的消息。第二个订阅者节点无法在 /async_topic
中完成匹配过程,因为发布者阻止了它,因为它已经达到了匹配发布者的最大数量。因此,只有来自 /sync_topic
的消息将会在这个第三终端中接收到。
[INFO] [1613127657.088860890] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 18'
[INFO] [1613127657.588896594] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 19'
[INFO] [1613127658.088849401] [sync_async_subscriber]: I heard: 'SYNC: Hello, world! 20'
在主题内使用分区
分区功能可用于控制在同一主题内哪些发布者和订阅者交换信息。
分区在由域 ID 引起的物理隔离内引入了逻辑实体隔离级别的概念。为了使发布者与订阅者进行通信,他们必须至少属于一个共同的分区。分区代表了在域和主题之外分离发布者和订阅者的另一个级别。与域和主题不同,一个端点可以同时属于多个分区。为了在不同的域或主题上共享某些数据,每个域或主题必须有一个不同的发布者,分享其自己的更改历史。然而,单个发布者可以使用单个主题数据更改在不同的分区上共享相同的数据样本,从而减少网络过载。
让我们将 /sync_topic
发布者更改为分区 part1
,并创建一个使用分区 part2
的新 /sync_topic
订阅者。他们的配置文件现在应如下所示:
<!-- sync_topic主题的发布者配置文件 -->
<publisher profile_name="/sync_topic"> <!-- 定义一个发布者配置文件,名称为/sync_topic --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --><qos> <!-- 定义QoS(服务质量)设置 --><publishMode> <!-- 定义发布模式 --><kind>SYNCHRONOUS</kind> <!-- 设置发布模式为同步 --></publishMode><partition> <!-- 定义分区 --><names> <!-- 分区名称 --><name>part1</name> <!-- 设置分区名称为part1 --></names></partition></qos>
</publisher><!-- sync_topic主题的订阅者配置文件 -->
<subscriber profile_name="/sync_topic"> <!-- 定义一个订阅者配置文件,名称为/sync_topic --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy> <!-- 设置历史内存策略为动态 --><qos> <!-- 定义QoS(服务质量)设置 --><partition> <!-- 定义分区 --><names> <!-- 分区名称 --><name>part2</name> <!-- 设置分区名称为part2 --></names></partition></qos>
</subscriber>
打开两个终端。不要忘记加载设置文件并设置所需的环境变量。在第一个终端上运行发布者节点,在另一个终端上运行订阅者节点。您应该看到只有 /async_topic
消息到达订阅者。 /sync_topic
订阅者没有接收到数据,因为它与相应的发布者在不同的分区中。
[INFO] [1612972054.995410057] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 10'
[INFO] [1612972055.995396561] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 11'
[INFO] [1612972056.995473953] [sync_async_subscriber]: I heard: 'ASYNC: Hello, world! 12'
配置服务和客户端
服务和客户端各有一个发布者和一个订阅者,它们通过两个不同的主题进行通信。例如,对于名为 ping
的服务,有:
在
/rq/ping
上监听请求的服务订阅者。服务发布者在
/rr/ping
上发送响应。客户端发布者在
/rq/ping
上发送请求。一个客户端订阅者正在监听
/rr/ping
上的响应。
尽管您可以使用这些主题名称在 XML 上设置配置文件,有时您可能希望将相同的配置文件应用于节点上的所有服务或客户端。与其为所有服务生成的所有主题名称复制相同的配置文件,您可以只创建一个名为 service
的发布者和订阅者配置文件对。对于创建名为 client
的对的客户端,也可以这样做。
使用服务和客户端创建节点
开始使用该服务创建节点。在您的包中添加一个名为 src/ping_service.cpp
的新源文件,并包含以下内容:
#include <memory> // 包含用于智能指针的头文件#include "rclcpp/rclcpp.hpp" // 包含ROS 2的C++客户端库
#include "example_interfaces/srv/trigger.hpp" // 包含example_interfaces包中的Trigger服务/*** 服务操作:响应success=true并在控制台打印请求*/
void ping(const std::shared_ptr<example_interfaces::srv::Trigger::Request> request,std::shared_ptr<example_interfaces::srv::Trigger::Response> response)
{// 请求数据未使用(void) request;// 构建响应response->success = true;// 记录到控制台RCLCPP_INFO(rclcpp::get_logger("ping_server"), "Incoming request"); // 打印收到请求的日志RCLCPP_INFO(rclcpp::get_logger("ping_server"), "Sending back response"); // 打印发送响应的日志
}int main(int argc, char **argv) // 主函数
{rclcpp::init(argc, argv); // 初始化ROS 2// 创建节点和服务std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("ping_server"); // 创建名为ping_server的节点rclcpp::Service<example_interfaces::srv::Trigger>::SharedPtr service =node->create_service<example_interfaces::srv::Trigger>("ping", &ping); // 创建名为ping的服务,并绑定到ping函数// 记录服务已准备好的日志RCLCPP_INFO(rclcpp::get_logger("ping_server"), "Ready to serve."); // 打印服务已准备好的日志// 运行节点rclcpp::spin(node); // 运行节点rclcpp::shutdown(); // 关闭ROS 2
}
在名为 src/ping_client.cpp
的文件中创建客户端,内容如下:
#include <chrono> // 包含用于时间操作的头文件
#include <memory> // 包含用于智能指针的头文件#include "rclcpp/rclcpp.hpp" // 包含ROS 2的C++客户端库
#include "example_interfaces/srv/trigger.hpp" // 包含example_interfaces包中的Trigger服务using namespace std::chrono_literals; // 使用chrono命名空间中的字面量int main(int argc, char **argv) // 主函数
{rclcpp::init(argc, argv); // 初始化ROS 2// 创建节点和客户端std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("ping_client"); // 创建名为ping_client的节点rclcpp::Client<example_interfaces::srv::Trigger>::SharedPtr client =node->create_client<example_interfaces::srv::Trigger>("ping"); // 创建名为ping的客户端// 创建请求auto request = std::make_shared<example_interfaces::srv::Trigger::Request>(); // 创建Trigger服务的请求// 等待服务可用while (!client->wait_for_service(1s)) { // 每隔1秒检查一次服务是否可用if (!rclcpp::ok()) { // 如果ROS 2被中断RCLCPP_ERROR(rclcpp::get_logger("ping_client"), "Interrupted while waiting for the service. Exiting."); // 打印错误日志return 0; // 返回0表示程序正常结束}RCLCPP_INFO(rclcpp::get_logger("ping_client"), "Service not available, waiting again..."); // 打印服务不可用的日志}// 现在服务可用了,发送请求RCLCPP_INFO(rclcpp::get_logger("ping_client"), "Sending request"); // 打印发送请求的日志auto result = client->async_send_request(request); // 异步发送请求// 等待结果并将其记录到控制台if (rclcpp::spin_until_future_complete(node, result) ==rclcpp::FutureReturnCode::SUCCESS) // 如果成功接收到响应{RCLCPP_INFO(rclcpp::get_logger("ping_client"), "Response received"); // 打印接收到响应的日志} else {RCLCPP_ERROR(rclcpp::get_logger("ping_client"), "Failed to call service ping"); // 打印调用服务失败的日志}rclcpp::shutdown(); // 关闭ROS 2return 0; // 返回0表示程序正常结束
}
打开 CMakeLists.txt
文件并添加两个新的可执行文件 ping_service
和 ping_client
:
find_package(example_interfaces REQUIRED) # 查找example_interfaces包,标记为必需add_executable(ping_service src/ping_service.cpp) # 添加可执行文件ping_service,源文件为src/ping_service.cpp
ament_target_dependencies(ping_service example_interfaces rclcpp) # 设置ping_service的依赖项为example_interfaces和rclcppadd_executable(ping_client src/ping_client.cpp) # 添加可执行文件ping_client,源文件为src/ping_client.cpp
ament_target_dependencies(ping_client example_interfaces rclcpp) # 设置ping_client的依赖项为example_interfaces和rclcppinstall(TARGETS # 安装目标ping_service # 安装ping_serviceDESTINATION lib/${PROJECT_NAME}) # 安装路径为lib/${PROJECT_NAME}install(TARGETS # 安装目标ping_client # 安装ping_clientDESTINATION lib/${PROJECT_NAME}) # 安装路径为lib/${PROJECT_NAME}
最后,构建包。
为服务和客户端创建 XML 配置文件
创建一个名为 ping.xml
的文件,并包含以下内容:
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles"><!-- 默认发布者配置文件 --><publisher profile_name="default_publisher" is_default_profile="true"><!-- 历史内存策略设置为动态 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy></publisher><!-- 默认订阅者配置文件 --><subscriber profile_name="default_subscriber" is_default_profile="true"><!-- 历史内存策略设置为动态 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy></subscriber><!-- 服务发布者配置为同步模式 --><publisher profile_name="service"><!-- 历史内存策略设置为动态 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy><qos><publishMode><!-- 发布模式设置为同步 --><kind>SYNCHRONOUS</kind></publishMode></qos></publisher><!-- 客户端发布者配置为异步模式 --><publisher profile_name="client"><!-- 历史内存策略设置为动态 --><historyMemoryPolicy>DYNAMIC</historyMemoryPolicy><qos><publishMode><!-- 发布模式设置为异步 --><kind>ASYNCHRONOUS</kind></publishMode></qos></publisher></profiles>
此配置文件将服务上的发布模式设置为 SYNCHRONOUS
,将客户端上的发布模式设置为 ASYNCHRONOUS
。请注意,我们仅定义了服务和客户端的发布者配置文件,但也可以提供订阅者配置文件。
执行节点
打开两个终端,并在每个终端上加载设置文件。然后设置加载 XML 所需的环境变量:
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export RMW_FASTRTPS_USE_QOS_FROM_XML=1
export FASTRTPS_DEFAULT_PROFILES_FILE=~/ros2_ws/src/sync_async_node_example_cpp/ping.xml
在第一个终端上运行服务节点。
ros2 run sync_async_node_example_cpp ping_service
您应该看到服务正在等待请求:
[INFO] [1612977403.805799037] [ping_server]: Ready to serve.
在第二个终端上运行客户端节点。
ros2 run sync_async_node_example_cpp ping_client
您应该看到客户端发送请求并接收响应:
[INFO] [1612977404.805799037] [ping_client]: Sending request
[INFO] [1612977404.825473835] [ping_client]: Response received
同时,服务器控制台中的输出已更新:
[INFO] [1612977403.805799037] [ping_server]: Ready to serve.
[INFO] [1612977404.807314904] [ping_server]: Incoming request
[INFO] [1612977404.836405125] [ping_server]: Sending back response
相关文章:
【ROS2】高级:解锁 Fast DDS 中间件的潜力 [社区贡献]
目标:本教程将展示如何在 ROS 2 中使用 Fast DDS 的扩展配置功能。 教程级别:高级 时间:20 分钟 目录 背景 先决条件在同一个节点中混合同步和异步发布 创建具有发布者的节点创建包含配置文件的 XML 文件执行发布者节点创建一个包含订阅者的节…...
VirtualBox虚拟机与主机互传文件的方法
建立共享文件夹 1.点击设置,点击共享文件夹,添加共享文件夹路径,保存 2.启动虚拟机,点击设备,点击安装增强功能,界面会出现一个光碟图标,点击光碟图标 3.打开光碟图标,出现一个目…...
访问控制系列
目录 一、基本概念 1.客体与主体 2.引用监控器与引用验证机制 3.安全策略与安全模型 4.安全内核 5.可信计算基 二、访问矩阵 三、访问控制策略 1.主体属性 2.客体属性 3.授权者组成 4.访问控制粒度 5.主体、客体状态 6.历史记录和上下文环境 7.数据内容 8.决策…...
【BUG】已解决:ModuleNotFoundError: No module named ‘cv2’
已解决:ModuleNotFoundError: No module named ‘cv2’ 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我是博主英杰,211科班出身,就职于医疗科技公司,热衷分享知识,武汉城市开…...
成都亚恒丰创教育科技有限公司 【插画猴子:笔尖下的灵动世界】
在浩瀚的艺术海洋中,每一种创作形式都是人类情感与想象力的独特表达。而插画,作为这一广阔领域中的璀璨明珠,以其独特的视觉语言和丰富的叙事能力,构建了一个又一个令人遐想连篇的梦幻空间。成都亚恒丰创教育科技有限公司 在众多插…...
gite+picgo+typora打造个人免费笔记软件
文章目录 1️⃣个人笔记软件2️⃣ 配置教程2.1 使用软件2.2 node 环境配置2.3 软件安装2.4 gite仓库设置2.5 配置picgo2.6 测试检验2.7 github教程 🎡 完结撒花 1️⃣个人笔记软件 最近换了环境,没有之前的生产环境舒适,写笔记也没有劲头&…...
只用 CSS 能玩出什么花样?
在前端开发领域,CSS 不仅仅是一种样式语言,它更像是一位多才多艺的艺术家,能够创造出令人惊叹的视觉效果。本文将带你探索 CSS 的无限可能,从基本形状到动态动画,从几何艺术到仿生设计,只用 CSS 就能玩出令…...
Linux C++ 056-设计模式之迭代器模式
Linux C 056-设计模式之迭代器模式 本节关键字:Linux、C、设计模式、迭代器模式 相关库函数: 概念 迭代器模式(Iterator Pattern)是一种常用的设计模式。迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而…...
【Elasticsearch7.11】reindex问题
参考博文链接 问题:reindex 时出现如下问题 原因:数据量大,kibana的问题 解决方法: 将DSL命令转化成CURL命令在服务上执行 CURL命令 自动转化 curl -XPOST "http://IP:PORT/_reindex" -H Content-Type: application…...
nginx代理缓存
在服务器架构中,反向代理服务器除了能够起到反向代理的作用之外,还可以缓存一些资源,加速客户端访问,nginx的ngx_http_proxy_module模块不仅包含了反向代理的功能还包含了缓存功能。 1、定义代理缓存规则 参数详解: p…...
[React 进阶系列] useSyncExternalStore hook
[React 进阶系列] useSyncExternalStore hook 前情提要,包括 yup 的实现在这里:yup 基础使用以及 jest 测试 简单的提一下,需要实现的功能是: yup schema 需要访问外部的 storage外部的 storage 是可变的React 内部也需要访问同…...
Linux C++ 055-设计模式之状态模式
Linux C 055-设计模式之状态模式 本节关键字:Linux、C、设计模式、状态模式 相关库函数: 概念 状态模式(State Pattern)是设计模式的一种,属于行为模式。允许一个对象在其内部状态改变时改变它的行为。对象看起来似…...
景联文科技构建高质量心理学系知识图谱,助力大模型成为心理学科专家
心理大模型正处于快速发展阶段,在临床应用、教育、研究等多个领域展现出巨大潜力。 心理学系知识图谱能够丰富心理大模型的认知能力,使其在处理心理学相关问题时更加精确、可靠和有洞察力。这对于提高心理健康服务的质量和效率、促进科学研究以及优化教育…...
【数学建模】——数学规划模型
目录 一、线性规划(Linear Programming) 1.1 线性规划的基本概念 1.2 线性规划的图解法 模型建立: 二、整数规划(Integer Programming) 2.1 整数规划的基本概念 2.2 整数规划的求解方法 三、非线性规划&#x…...
卸载linux 磁盘的内容,磁盘占满
Linux清理磁盘 https://www.cnblogs.com/siyunianhua/p/17981758 当前文件夹下,数量 ls -l | grep "^-" | wc -l ls -lR | grep "^-" | wc -l 找超过100M的大文件 find / -type f -size 100M -exec ls -lh {} \; df -Th /var/lib/docker 查找…...
LeetCode-随机链表的复制
. - 力扣(LeetCode) 本题思路: 首先注意到随机链表含有random的指针,这个random指针指向是随机的;先一个一个节点的拷贝,并且把拷贝的节点放在拷贝对象的后面,再让拷贝节点的next指向原链表拷贝…...
axios 下载大文件时,展示下载进度的组件封装——js技能提升
之前面试的时候,有遇到一个问题:就是下载大文件的时候,如何得知下载进度,当时的回复是没有处理过。。。 现在想到了。axios中本身就有一个下载进度的方法,可以直接拿来使用。 下面记录一下处理步骤: 参考…...
Linux: network: device事件注册机制 chatGPT; notify
ChatGPT 在 Linux 内核中,有关网络设备(net-device)的事件注册机制,允许用户在网络设备的状态发生变化(例如设备被删除、添加或修改)时接收通知。这主要通过 netdev 事件通知机制实现。具体来说,内核提供了一组用于注册和处理网络设备事件的 API。 以下是一些关键组件…...
【ROS2】测试
为什么要进行自动化测试? 以下是我们应该进行自动化测试的许多重要原因之一: 您可以更快地对代码进行增量更新。ROS 有数百个包,具有许多相互依赖关系,因此很难预见一个小变化可能引起的问题。如果您的更改通过了单元测试…...
别卷模型,卷应用:从李彦宏的AI观点谈起
2024年7月4日,世界人工智能大会暨人工智能全球治理高级别会议在上海世博中心隆重召开。百度创始人、董事长兼首席执行官李彦宏在产业发展主论坛上的发言,引起了广泛关注。他提出:“大家不要卷模型,要卷应用!”这一观点…...
数据库(Database,简称DB)介绍
数据库(Database,简称DB)是信息技术领域中一个至关重要的组成部分,它按照数据结构来组织、存储和管理数据。以下是对数据库的详细介绍: 一、定义与基本概念 定义:数据库是按照数据结构来组织、存储和管理…...
Redis五种常用数据类型详解及使用场景
Redis 5 种基本数据类型 Redis 共有 5 种基本数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。 这 5 种数据类型…...
Postman API测试覆盖率:全面评估指南
📊 Postman API测试覆盖率:全面评估指南 在API测试中,测试覆盖率是一个关键指标,它衡量了测试用例对代码的覆盖程度。Postman提供了多种工具和方法来评估API测试覆盖率,帮助开发者和测试人员确保API的质量和稳定性。本…...
C++--find
find 在[first,last)区间找第一个等于val的元素。 template<class InputIterator, class T> InputIterator find(InputIterator first,//起始迭代器 InputIterator last, //结束迭代器 const T& val); //需要查找的值 源码剖析 template<class InputI…...
JavaWeb入门程序解析(Spring官方骨架、配置起步依赖、SpringBoot父工程、内嵌Tomcat)
3.3 入门程序解析 关于web开发的基础知识,我们可以告一段落了。下面呢,我们在基于今天的核心技术点SpringBoot快速入门案例进行分析。 3.3.1 Spring官方骨架 之前我们创建的SpringBoot入门案例,是基于Spring官方提供的骨架实现的。 Sprin…...
mysql命令练习
创建数据表grade: CREATE TABLE grade( id INT NOT NULL, sex CHAR(1), firstname VARCHAR(20) NOT NULL, lastname VARCHAR(20) NOT NULL, english FLOAT, math FLOAT, chinese FLOAT ); 向数据表grade中插…...
AI绘画Stable Diffusion 零基础入门 —AI 绘画原理与工具介绍,万字解析AI绘画的使用教程
大家好,我是设计师阿威 想要入门 AI 绘画,首先需要了解它的原理是什么样的。 其实很早就已经有人基于深度学习模型展开了对图像生成的研究了,但在那时,生成的图像分辨率和内容都非常抽象。 直到近两年,AI 产出的图像…...
jenkins添加ssh证书
1、生成ssh密匙:windows生成ssh密匙-CSDN博客 2、添加添加ssh凭证:jenkins路由地址为:/manage/credentials/store/system/domain/_/ 点击添加凭证 选择第二个,将生成的私匙 id_rsa 里边的内容赋值到密钥,id留空自动…...
C++--accumulate介绍
在C中,accumulate是一个用于对容器中的元素进行累加操作的函数模板,位于 头文件中。它允许你对容器(如vector或array)中的元素进行累加运算,并返回累加的结果。 源代码展示 template<class InputIterator, class …...
C++写一个线程池
C写一个线程池 文章目录 C写一个线程池设计思路测试数据的实现任务类的实现线程池类的实现线程池构造函数线程池入口函数队列中取任务添加任务函数线程池终止函数 源码 之前用C语言写了一个线程池,详情请见: C语言写一个线程池 这次换成C了!…...
查找网站备案信息/站外推广免费网站
MySQL数学函数简明总结1. ABS(x): 返回x的绝对值mysql> select ABS(1), ABS(-1), ABS(0);-------------------------| ABS(1) | ABS(-1) | ABS(0) |-------------------------| 1 | 1 | 0 |-------------------------2. PI(): 返回圆周率mysql> select P…...
南京网站房地产/国外搜索引擎入口
有些人说,我零基础,应该怎么学习?经过大半年的更新,大家细看里面的教程,基本都可以找到答案这里汇总了一份视频跟图文的教程,定期更新,所以大家收藏好学摄影,找的是合适的老师&#…...
可以免费发布信息的网站有哪些/企业网站cms
textView.setCompoundDrawables(drawable, null, null, null);如果看不到图片,这是由于需要手动定drawable适当的大小,使用drawable.setBounds。 假设drawable为图片,指导drawable.setBounds(0,0,drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight());。这直接给图片本…...
有哪些网站可以找兼职做/常见的系统优化软件
LR中检查点有两种:图片和文字。这两种检查点可用以下三个函数实现:web_find()、web_reg_find()和web_image_check() 下面分别介绍三种函数的用法 1.web_find()函数 函数作用:在页面中查找相应的内容 参数举例:web_find("…...
衡阳网页设计/seo外链工具下载
http://one.laptop.org/...
网站规划与建设/做app软件大概多少钱
前几天,笔者为大家介绍了一款型号为JA-F32*RH的网络数字摄像头,不少读者看后虽然觉得这种枪机不错,但似乎并不适合室内安装,因为枪机的焦距比较大。于是他们留言问我有没有角度大点的、性价比又高一点的?作为一名全能型…...