【ROS2】初级:客户端-创建自定义 msg 和 srv 文件

目标:定义自定义接口文件( .msg 和 .srv )并将它们与 Python 和 C++节点一起使用。

 教程级别:初学者

 时间:20 分钟

 目录

  •  背景

  •  先决条件

  •  任务

    • 1. 创建一个新包

    • 2. 创建自定义定义

    • CMakeLists.txt

    • package.xml

    • 5. 构建 tutorial_interfaces 包

    • 6 确认消息和服务创建

    • 7. 测试新界面

  •  摘要

  •  下一步

 背景

在之前的教程中,您利用消息和服务接口来学习关于主题、服务以及简单的发布者/订阅者(C++/Python)和服务/客户端(C++/Python)节点。在那些情况下,您使用的接口是预先定义的。

虽然使用预定义的接口定义是一种好习惯,但有时您可能也需要定义自己的消息和服务。本教程将向您介绍创建自定义接口定义的最简单方法。

 先决条件

您应该有一个 ROS 2 工作空间。

本教程还使用了在发布者/订阅者(C++ 和 Python)和服务/客户端(C++ 和 Python)教程中创建的包来尝试新的自定义消息。

 任务

1. 创建一个新包

在本教程中,您将在各自的包中创建自定义的 .msg 和 .srv 文件,然后在另一个包中使用它们。两个包应该在同一个工作空间中。

由于我们将使用在早期教程中创建的 pub/sub 和 service/client 包,请确保您位于这些包的同一工作空间( ros2_ws/src ),然后运行以下命令来创建一个新包:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 tutorial_interfaces
cxy@ubuntu2404-cxy:~/ros2_ws/src$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 tutorial_interfaces
going to create a new package
package name: tutorial_interfaces
destination directory: /home/cxy/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['cxy <cxy@todo.todo>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: []
creating folder ./tutorial_interfaces
creating ./tutorial_interfaces/package.xml
creating source and include folder
creating folder ./tutorial_interfaces/src
creating folder ./tutorial_interfaces/include/tutorial_interfaces
creating ./tutorial_interfaces/CMakeLists.txt

tutorial_interfaces 是新包的名称。请注意,它是且只能是一个 CMake 包,但这并不限制您可以在哪种类型的包中使用您的消息和服务。您可以在 CMake 包中创建自己的自定义接口,然后在 C++或 Python 节点中使用它,这将在最后一节中介绍。

.msg 和 .srv 文件需要分别放置在名为 msg 和 srv 的目录中。在 ros2_ws/src/tutorial_interfaces 中创建目录:

cxy@ubuntu2404-cxy:~/ros2_ws/src/tutorial_interfaces$ 
mkdir msg srv

424bf2a08c1d6c220841f61c3aad2f10.png

2. 创建自定义定义

 2.1 消息定义

在您刚刚创建的 tutorial_interfaces/msg 目录中,新建一个名为 Num.msg 的文件,其中包含一行代码声明其数据结构:

int64 num

这是一个自定义消息,用于传输一个名为 num 的 64 位整数。

在您刚刚创建的 tutorial_interfaces/msg 目录中,还要新建一个名为 Sphere.msg 的文件,内容如下:

geometry_msgs/Point center
float64 radius

此自定义消息使用了另一个消息包中的消息(在这种情况下为 geometry_msgs/Point )。

 2.2 srv 定义

在您刚刚创建的 tutorial_interfaces/srv 目录中,创建一个名为 AddThreeInts.srv 的新文件,包含以下请求和响应结构:

int64 a
int64 b
int64 c
---
int64 sum

这是您的自定义服务,它请求三个整数,分别命名为 a 、 b 和 c ,并返回一个名为 sum 的整数。

CMakeLists.txt

要将您定义的接口转换为特定语言的代码(如 C++和 Python),以便它们可以在这些语言中使用,请将以下行添加到 CMakeLists.txt :

find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)rosidl_generate_interfaces(${PROJECT_NAME}"msg/Num.msg""msg/Sphere.msg""srv/AddThreeInts.srv"DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)

 便条

在 rosidl_generate_interfaces 中的第一个参数(库名称)必须与${PROJECT_NAME}相匹配(见 https://github.com/ros2/rosidl/issues/441#issuecomment-591025515)。

cmake_minimum_required(VERSION 3.8)
# 设置CMake的最低版本要求为3.8project(tutorial_interfaces)
# 定义项目名称为'tutorial_interfaces'if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 如果使用GNU C++编译器或Clang编译器,则添加编译选项-Wall -Wextra -Wpedantic# find dependencies
# 查找依赖项
find_package(ament_cmake REQUIRED)
# 查找ament_cmake包,标记为必需# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
# 取消注释以下部分以手动添加更多依赖项
# find_package(<dependency> REQUIRED)find_package(geometry_msgs REQUIRED)
# 查找geometry_msgs包,标记为必需find_package(rosidl_default_generators REQUIRED)
# 查找rosidl_default_generators包,标记为必需rosidl_generate_interfaces(${PROJECT_NAME}"msg/Num.msg""msg/Sphere.msg""srv/AddThreeInts.srv"DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)
# 生成接口文件,包括Num.msg、Sphere.msg和AddThreeInts.srv
# 依赖项为geometry_msgs,因为Sphere.msg依赖于geometry_msgsif(BUILD_TESTING)find_package(ament_lint_auto REQUIRED)# 如果启用了测试,查找ament_lint_auto包,标记为必需# the following line skips the linter which checks for copyrights# comment the line when a copyright and license is added to all source filesset(ament_cmake_copyright_FOUND TRUE)# 以下行跳过检查版权的linter# 当所有源文件添加了版权和许可证时,注释掉该行# the following line skips cpplint (only works in a git repo)# comment the line when this package is in a git repo and when# a copyright and license is added to all source filesset(ament_cmake_cpplint_FOUND TRUE)# 以下行跳过cpplint检查(仅在git仓库中有效)# 当该包在git仓库中且所有源文件添加了版权和许可证时,注释掉该行ament_lint_auto_find_test_dependencies()# 自动查找测试依赖项
endif()ament_package()
# 声明这是一个ament包

package.xml

因为接口依赖于 rosidl_default_generators 来生成特定语言的代码,您需要声明一个对它的构建工具依赖。 rosidl_default_runtime 是一个运行时或执行阶段的依赖,稍后使用接口时需要它。 rosidl_interface_packages 是您的包 tutorial_interfaces 应该关联的依赖组的名称,使用 <member_of_group> 标签声明。

108e1b14488a5933c8a8867915e95820.png

acdc77cdc27982c79bed6090570c674c.png

在 package.xml 的 <package> 元素内添加以下行:

<depend>geometry_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<?xml version="1.0"?>
<!-- 定义XML版本为1.0 --><?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<!-- 指定XML模式文件的位置和类型 --><package format="3"><!-- 定义包的格式版本为3 --><name>tutorial_interfaces</name><!-- 包的名称为'tutorial_interfaces' --><version>0.0.0</version><!-- 包的版本号为0.0.0 --><description>Creating custom msg and srv files</description><!-- 包的描述,待填写 --><maintainer email="cxy@126.com">cxy</maintainer><!-- 维护者信息,包含电子邮件地址 --><license>Apache-2.0</license><!-- 包的许可证类型为Apache-2.0 --><buildtool_depend>ament_cmake</buildtool_depend><!-- 构建工具依赖项为ament_cmake --><depend>geometry_msgs</depend><!-- 依赖项为geometry_msgs --><buildtool_depend>rosidl_default_generators</buildtool_depend><!-- 构建工具依赖项为rosidl_default_generators --><exec_depend>rosidl_default_runtime</exec_depend><!-- 运行时依赖项为rosidl_default_runtime --><member_of_group>rosidl_interface_packages</member_of_group><!-- 包属于rosidl_interface_packages组 --><test_depend>ament_lint_auto</test_depend><!-- 测试依赖项为ament_lint_auto --><test_depend>ament_lint_common</test_depend><!-- 测试依赖项为ament_lint_common --><export><!-- 导出部分 --><build_type>ament_cmake</build_type><!-- 构建类型为ament_cmake --></export>
</package>

5. 构建 tutorial_interfaces 包

现在您的自定义接口包的所有部分都已就位,您可以构建该包。在您工作区的根目录 ( ~/ros2_ws ) 中,运行以下命令:

cxy@ubuntu2404-cxy:~/ros2_ws$ colcon build --packages-select tutorial_interfaces
Starting >>> tutorial_interfaces
Finished <<< tutorial_interfaces [26.0s]                  Summary: 1 package finished [26.7s]

‍现在这些接口将能被其他 ROS 2 包发现。

6 确认消息和服务创建 

在新终端中,运行以下命令以在您的工作区中( ros2_ws )对其进行源设置:

source install/setup.bash

现在您可以通过使用 ros2 interface show 命令来确认您的 接口 创建是否成功:

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 interface show tutorial_interfaces/msg/Num
int64 num

 和

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 interface show tutorial_interfaces/msg/Sphere
geometry_msgs/Point centerfloat64 xfloat64 yfloat64 z
float64 radius

 和

cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 interface show tutorial_interfaces/srv/AddThreeInts
int64 a
int64 b
int64 c
---
int64 sum

7 测试新接口

在这一步,您可以使用在之前教程中创建的包。对节点、 CMakeLists.txt 和 package.xml 文件进行一些简单的修改,将允许您使用新的接口。

7.1 测试 Num.msg 与发布/订阅 

在对之前教程中创建的发布者/订阅者包进行一些修改(C++或 Python)后,您可以看到 Num.msg 的实际运行。由于您将标准字符串消息更改为数字消息,输出将会有些许不同。

发布者

C++ 

#include <chrono> // 包含chrono库,用于处理时间。
#include <memory> // 包含memory库,用于处理智能指针。#include "rclcpp/rclcpp.hpp" // 包含ROS2 C++客户端库的头文件。
#include "tutorial_interfaces/msg/num.hpp" // 导入自定义消息Num的头文件。using namespace std::chrono_literals; // 使用std::chrono_literals命名空间,允许使用后缀如500ms表示时间。// 定义一个MinimalPublisher类,继承自rclcpp::Node。
class MinimalPublisher : public rclcpp::Node
{
public:MinimalPublisher(): Node("minimal_publisher"), count_(0) // 初始化Node,并设置节点名称为'minimal_publisher',计数器count_初始化为0。{publisher_ = this->create_publisher<tutorial_interfaces::msg::Num>("topic", 10); // 创建发布者对象,发布Num类型的消息到'topic'话题,队列大小为10。auto timer_callback = [this](){ // 定义定时器回调函数。auto message = tutorial_interfaces::msg::Num(); // 创建Num类型的消息实例。message.num = this->count_++; // 将计数器count_的值赋给消息的num字段,并将count_加1。RCLCPP_INFO_STREAM(this->get_logger(), "Publishing: '" << message.num << "'"); // 使用流式日志记录发布的消息。publisher_->publish(message); // 发布消息。};timer_ = this->create_wall_timer(500ms, timer_callback); // 创建定时器,每500毫秒调用一次timer_callback函数。}private:rclcpp::TimerBase::SharedPtr timer_; // 定义定时器智能指针。rclcpp::Publisher<tutorial_interfaces::msg::Num>::SharedPtr publisher_; // 定义发布者对象的智能指针。size_t count_; // 定义计数器变量。
};int main(int argc, char * argv[])
{rclcpp::init(argc, argv); // 初始化ROS2。rclcpp::spin(std::make_shared<MinimalPublisher>()); // 创建MinimalPublisher对象并进入循环等待回调。rclcpp::shutdown(); // 关闭ROS2。return 0; // 返回0表示程序正常退出。
}

Python

import rclpy # 导入ROS2 Python客户端库。
from rclpy.node import Node # 从rclpy.node模块导入Node类。from tutorial_interfaces.msg import Num # 导入自定义消息Num。# 定义一个MinimalPublisher类,它继承自Node类。
class MinimalPublisher(Node):def __init__(self):super().__init__('minimal_publisher') # 初始化Node,并设置节点名称为'minimal_publisher'。self.publisher_ = self.create_publisher(Num, 'topic', 10) # 创建发布者对象,发布Num类型的消息到'topic'话题,队列大小为10。timer_period = 0.5 # 设置定时器周期为0.5秒。self.timer = self.create_timer(timer_period, self.timer_callback) # 创建定时器,调用timer_callback函数。self.i = 0 # 初始化计数器i。def timer_callback(self):msg = Num() # 创建Num类型的消息实例。msg.num = self.i # 将计数器i的值赋给消息的num字段。self.publisher_.publish(msg) # 发布消息。self.get_logger().info('Publishing: "%d"' % msg.num) # 记录日志信息,显示发布的数字。self.i += 1 # 计数器i加1。# main函数作为程序入口点。
def main(args=None):rclpy.init(args=args) # 初始化ROS2。minimal_publisher = MinimalPublisher() # 创建MinimalPublisher对象。rclpy.spin(minimal_publisher) # 进入循环等待回调。minimal_publisher.destroy_node() # 销毁节点。rclpy.shutdown() # 关闭ROS2。# 当该脚本被直接运行时,调用main函数。
if __name__ == '__main__':main()

 订阅者

C++

#include <functional> // 包含functional头文件,用于函数对象和预定义的函数。
#include <memory> // 包含memory头文件,用于管理动态内存。#include "rclcpp/rclcpp.hpp" // 包含ROS2 C++客户端库的头文件。
#include "tutorial_interfaces/msg/num.hpp" // 导入自定义消息Num。using std::placeholders::_1; // 使用占位符_1,它是std::placeholders命名空间中的一个对象。// 定义一个MinimalSubscriber类,它继承自rclcpp::Node类。
class MinimalSubscriber : public rclcpp::Node
{
public:MinimalSubscriber(): Node("minimal_subscriber") // 初始化Node,并设置节点名称为'minimal_subscriber'。{auto topic_callback = [this](const tutorial_interfaces::msg::Num & msg){ // 定义一个lambda函数作为话题回调。RCLCPP_INFO_STREAM(this->get_logger(), "I heard: '" << msg.num << "'"); // 使用流式日志记录接收到的消息。};subscription_ = this->create_subscription<tutorial_interfaces::msg::Num>( // 创建订阅对象。"topic", 10, topic_callback); // 订阅名为'topic'的话题,队列大小为10,回调函数为topic_callback。}private:rclcpp::Subscription<tutorial_interfaces::msg::Num>::SharedPtr subscription_; // 定义订阅对象的智能指针。
};// main函数作为程序入口点。
int main(int argc, char * argv[])
{rclcpp::init(argc, argv); // 初始化ROS2。rclcpp::spin(std::make_shared<MinimalSubscriber>()); // 创建MinimalSubscriber对象并进入循环等待回调。rclcpp::shutdown(); // 关闭ROS2。return 0; // 返回0表示程序正常退出。
}

Python

import rclpy # 导入ROS2 Python客户端库。
from rclpy.node import Node # 从rclpy模块导入Node类。from tutorial_interfaces.msg import Num # 导入自定义消息Num。# 定义一个MinimalSubscriber类,它继承自Node类。
class MinimalSubscriber(Node):def __init__(self):super().__init__('minimal_subscriber') # 初始化Node,并设置节点名称为'minimal_subscriber'。self.subscription = self.create_subscription(Num, # 设置订阅的消息类型为Num。'topic', # 设置订阅的话题名称为'topic'。self.listener_callback, # 设置回调函数为listener_callback。10) # 设置队列大小为10。self.subscription # 这行代码实际上没有执行任何操作,只是重复了上一行创建的订阅对象。def listener_callback(self, msg):self.get_logger().info('I heard: "%d"' % msg.num) # 定义回调函数,当接收到消息时打印消息内容。# 定义main函数作为程序入口点。
def main(args=None):rclpy.init(args=args) # 初始化ROS2。minimal_subscriber = MinimalSubscriber() # 创建MinimalSubscriber对象。rclpy.spin(minimal_subscriber) # 进入循环,等待并处理回调函数。minimal_subscriber.destroy_node() # 销毁节点。rclpy.shutdown() # 关闭ROS2。# 当该脚本被直接运行时,调用main函数。
if __name__ == '__main__':main()

CMakeLists.txt 添加以下行(仅限 C++):

#...find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)                      # CHANGEadd_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces)    # CHANGEadd_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces)  # CHANGEinstall(TARGETStalkerlistenerDESTINATION lib/${PROJECT_NAME})ament_package()

package.xml 添加以下行:

C++

<depend>tutorial_interfaces</depend>

Python

<exec_depend>tutorial_interfaces</exec_depend>

在进行上述编辑并保存所有更改后,构建包:

C++

colcon build --packages-select cpp_pubsub

Python

colcon build --packages-select py_pubsub

然后打开两个新的终端,在每个终端中运行 source ros2_ws ,并执行:

C++

ros2 run cpp_pubsub talker
ros2 run cpp_pubsub listener

Python

ros2 run py_pubsub talker
ros2 run py_pubsub listener

由于 Num.msg 仅传递一个整数,因此发布者应该只发布整数值,而不是它之前发布的字符串:

[INFO] [minimal_publisher]: Publishing: '0'
[INFO] [minimal_publisher]: Publishing: '1'
[INFO] [minimal_publisher]: Publishing: '2'
7.2 测试 AddThreeInts.srv 与服务/客户端 

通过对之前教程中创建的服务/客户端包(C++或 Python)进行一些修改,您可以看到 AddThreeInts.srv 的实际运行效果。由于您将原始的两个整数请求 srv 更改为三个整数请求 srv,所以输出会有些许不同。

服务端

C++

#include "rclcpp/rclcpp.hpp" // 包含ROS2 C++客户端库的头文件。
#include "tutorial_interfaces/srv/add_three_ints.hpp" // 包含自定义服务AddThreeInts的头文件。#include <memory> // 包含内存管理的头文件。// 定义一个处理服务请求的函数。
void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request,std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response> response)
{response->sum = request->a + request->b + request->c; // 计算请求中三个整数的和,并将结果赋值给响应对象的sum属性。RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld b: %ld c: %ld",request->a, request->b, request->c); // 使用ROS2日志系统打印请求信息。RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum); // 打印响应信息。
}int main(int argc, char **argv)
{rclcpp::init(argc, argv); // 初始化ROS2。// 创建一个Node对象。std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_server");// 创建一个服务,并指定服务类型、服务名称和处理函数。rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service =node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints", &add);RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints."); // 打印准备就绪的信息。rclcpp::spin(node); // 进入循环,等待并处理回调函数。rclcpp::shutdown(); // 关闭ROS2。
}

Python:

from tutorial_interfaces.srv import AddThreeInts # 从tutorial_interfaces服务中导入AddThreeInts服务。import rclpy # 导入rclpy模块,它是ROS2客户端库的Python接口。
from rclpy.node import Node # 从rclpy.node模块导入Node类。class MinimalService(Node): # 定义一个名为MinimalService的类,它继承自Node类。def __init__(self): # 类的初始化函数。super().__init__('minimal_service') # 调用父类的初始化函数,并设置节点名称为'minimal_service'。self.srv = self.create_service(AddThreeInts, 'add_three_ints', self.add_three_ints_callback) # 创建一个服务,服务类型为AddThreeInts,服务名称为'add_three_ints',并指定回调函数。def add_three_ints_callback(self, request, response): # 定义服务的回调函数。response.sum = request.a + request.b + request.c # 计算请求中三个整数的和,并将结果赋值给响应的sum字段。self.get_logger().info('Incoming request\na: %d b: %d c: %d' % (request.a, request.b, request.c)) # 打印请求信息。return response # 返回响应。def main(args=None): # 定义main函数。rclpy.init(args=args) # 初始化ROS2客户端库。minimal_service = MinimalService() # 创建MinimalService类的实例。rclpy.spin(minimal_service) # 将控制权交给ROS2,开始处理事件(例如服务请求)。rclpy.shutdown() # 关闭ROS2客户端库。if __name__ == '__main__': # 如果该文件作为主程序运行。main() # 调用main函数。

 客户端

C++

#include "rclcpp/rclcpp.hpp" // 包含RCLCPP库的头文件,它是ROS2客户端库的一部分。
#include "tutorial_interfaces/srv/add_three_ints.hpp" // 包含自定义服务AddThreeInts的头文件。#include <chrono> // 包含C++标准库中的chrono库,用于处理时间。
#include <cstdlib> // 包含C++标准库中的cstdlib库,提供通用工具函数。
#include <memory> // 包含C++标准库中的memory库,提供智能指针等内存管理功能。using namespace std::chrono_literals; // 使用std::chrono_literals命名空间,允许我们使用时间字面量(例如1s)。int main(int argc, char **argv) // 主函数,程序入口点。
{rclcpp::init(argc, argv); // 初始化ROS2客户端库。if (argc != 4) { // 如果命令行参数不等于4(程序名+三个整数)。RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z"); // 打印使用方法。return 1; // 返回1,表示错误。}std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client"); // 创建一个名为"add_three_ints_client"的ROS2节点。rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client = // 创建一个服务客户端,用于调用AddThreeInts服务。node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints");auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>(); // 创建一个AddThreeInts服务请求。request->a = atoll(argv[1]); // 将第一个命令行参数转换为长整型并赋值给请求的a成员。request->b = atoll(argv[2]); // 将第二个命令行参数转换为长整型并赋值给请求的b成员。request->c = atoll(argv[3]); // 将第三个命令行参数转换为长整型并赋值给请求的c成员。while (!client->wait_for_service(1s)) { // 循环等待服务变得可用。if (!rclcpp::ok()) { // 如果ROS2客户端库不再运行(例如,如果有人按下Ctrl+C)。RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting."); // 打印错误信息并退出。return 0; // 返回0,表示正常退出。}RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again..."); // 打印信息,服务不可用,再次等待。}auto result = client->async_send_request(request); // 异步发送服务请求,并返回一个包含结果的future对象。// 等待结果。if (rclcpp::spin_until_future_complete(node, result) == rclcpp::FutureReturnCode::SUCCESS) {RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum); // 如果调用成功,打印出结果之和。} else {RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_ints"); // 如果调用失败,打印错误信息。}rclcpp::shutdown(); // 关闭ROS2客户端库。return 0; // 返回0,表示程序正常退出。
}

Python

from tutorial_interfaces.srv import AddThreeInts  # 导入服务AddThreeInts
import sys
import rclpy
from rclpy.node import Nodeclass MinimalClientAsync(Node):def __init__(self):super().__init__('minimal_client_async')  # 初始化节点名为'minimal_client_async'self.cli = self.create_client(AddThreeInts, 'add_three_ints')  # 创建客户端,服务类型为AddThreeInts,服务名称为'add_three_ints'while not self.cli.wait_for_service(timeout_sec=1.0):self.get_logger().info('service not available, waiting again...')  # 如果服务不可用,则等待self.req = AddThreeInts.Request()  # 创建一个请求def send_request(self):self.req.a = int(sys.argv[1])  # 设置请求的第一个整数参数self.req.b = int(sys.argv[2])  # 设置请求的第二个整数参数self.req.c = int(sys.argv[3])  # 设置请求的第三个整数参数self.future = self.cli.call_async(self.req)  # 异步调用服务def main(args=None):rclpy.init(args=args)minimal_client = MinimalClientAsync()  # 创建MinimalClientAsync类的实例minimal_client.send_request()  # 发送请求while rclpy.ok():rclpy.spin_once(minimal_client)  # 单次旋转节点,等待响应if minimal_client.future.done():try:response = minimal_client.future.result()  # 获取响应结果except Exception as e:minimal_client.get_logger().info('Service call failed %r' % (e,))  # 如果调用服务失败,则打印错误信息else:minimal_client.get_logger().info('Result of add_three_ints: for %d + %d + %d = %d' % (minimal_client.req.a, minimal_client.req.b, minimal_client.req.c, response.sum))  # 打印服务调用结果breakminimal_client.destroy_node()  # 销毁节点rclpy.shutdown()  # 关闭rclpyif __name__ == '__main__':main()

CMakeLists.txt

添加以下行(仅限 C++):

#...find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)         # CHANGEadd_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(serverrclcpp tutorial_interfaces)                      # CHANGEadd_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(clientrclcpp tutorial_interfaces)                      # CHANGEinstall(TARGETSserverclientDESTINATION lib/${PROJECT_NAME})ament_package()

package.xml 添加以下行:

C++

<depend>tutorial_interfaces</depend>

Python

<exec_depend>tutorial_interfaces</exec_depend>

在进行上述编辑并保存所有更改后,构建包:

C++

colcon build --packages-select cpp_srvcli

Python

colcon build --packages-select py_srvcli

然后打开两个新的终端,在每个终端中运行 source ros2_ws ,并执行:

C++

ros2 run cpp_srvcli server
ros2 run cpp_srvcli client 2 3 1

Python

ros2 run py_srvcli service
ros2 run py_srvcli client 2 3 1

 摘要

在本教程中,您学习了如何在自己的包中创建自定义接口,以及如何在其他包中使用这些接口。

这个教程只是简单介绍了如何定义自定义接口。您可以在关于 ROS 2 接口中了解更多信息。

 下一步

下一教程将介绍更多在 ROS 2 中使用接口的方法。https://docs.ros.org/en/jazzy/Concepts/Basic/About-Interfaces.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1474769.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3中生成本地pdf并下载

1. 前言 前端中经常会遇到在系统中根据数据导出一个pdf文件出来,一般都是后端来实现的,既然后端可以实现,前端为什么就不行呢,正好有一次也写了这个需求,就写了个小demo 示例图: 2. 实现步骤 首先下载html2pdf.js这个库yarn add html2pdf.js // 或 npm i html2pdf.js在项…

下载,连接mysql数据库驱动(最详细)

前言 本篇博客&#xff0c;我讲讲如何连接数据库&#xff1f;我使用mysql数据库举例。 目录 下载对应的数据库jar 包 百度网盘 存有8.4.0版本压缩包&#xff1a;链接&#xff1a;https://pan.baidu.com/s/13uZtXRmuewHRbXaaCU0Xsw?pwduipy 提取码&#xff1a;uipy 复制这…

数据结构--二叉树和堆

目录 1.基本概念 2.树的遍历方法 3.满二叉树&&完全二叉树 4.逻辑结构&&物理结构 5.推理公式 6.二叉树应用--堆 7.简单实现堆 1.基本概念 &#xff08;1&#xff09;这个里面的概念还是比较多的&#xff0c;但是大部分我们只需要了解即可&#xff0c;因为…

论文略读:Large Language Models Relearn Removed Concepts

通过神经元修剪在模型编辑方面取得的进展为从大型语言模型中去除不良概念提供了希望。 然而&#xff0c;目前尚不清楚在编辑后模型是否具有重新学习修剪概念的能力——>论文通过在重新训练期间跟踪修剪神经元中的概念显著性和相似性来评估模型中的概念重新学习 研究结果表明…

嵌入式C语言面试相关知识——内存管理(不定期更新)

嵌入式C语言面试相关知识——内存管理&#xff08;不定期更新&#xff09; 一、博客声明二、自问题目1、嵌入式系统的内存布局是怎么样的&#xff1f;2、动态内存分配在嵌入式系统中的使用有什么注意事项&#xff1f;3、什么是内存碎片&#xff0c;如何减少内存碎片&#xff1f…

论文复现-基于决策树算法构建银行贷款审批预测模型(金融风控场景)

作者Toby&#xff0c;来源公众号&#xff1a;Python风控模型&#xff0c;基于决策树算法构建银行贷款审批预测模型 目录 1.金融风控论文复现 2.项目背景介绍 3.决策树介绍 4.数据集介绍 5.合规风险提醒 6.技术工具 7.实验过程 7.1导入数据 7.2数据预处理 7.3数据可…

Linux muduo 网络库

主要记录示意图和知识点框架&#xff1a; 1、阻塞、非阻塞、同步、异步 在处理IO的时候&#xff0c;阻塞和非阻塞都是同步IO&#xff0c;只有使用了特殊的API才是异步IO。 2、五种IO模型&#xff1a; 阻塞、非阻塞、IO复用、信号驱动、异步IO 3、muduo网络库 muduo网络库给用…

搭建基础库~

前言 项目中会用到工具库、函数库以及一些跟框架绑定的组件&#xff0c;如果这些基础模块每个项目都实现一套&#xff0c;维护起来那真的头大&#xff0c;你说呢&#x1f609; 搭建流程 准备工作 创建文件夹myLib、安装Git以及pnpm 目录大概就系这样子&#xff1a; myLib ├…

哈希表——C语言

哈希表&#xff08;Hash Table&#xff09;是一种高效的数据结构&#xff0c;能够在平均情况下实现常数时间的查找、插入和删除操作。 哈希表的核心是哈希函数&#xff0c;哈希函数是一个将输入数据&#xff08;通常称为“键”或“key”&#xff09;转换为固定长度的整数的函数…

python操作SQLite3数据库进行增删改查

python操作SQLite3数据库进行增删改查 1、创建SQLite3数据库 可以通过Navicat图形化软件来创建: 2、创建表 利用Navicat图形化软件来创建: 存储在 SQLite 数据库中的每个值(或是由数据库引擎所操作的值)都有一个以下的存储类型: NULL. 值是空值。 INTEGER. 值是有符…

刷爆leetcode第十期

题目一 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 首先我们要来判断下它们的根是否相等 根相等的话是否它们的左子树相等 是否…

06.C2W1.Auto-correct

往期文章请点这里 目录 OverviewAutocorrectWhat is autocorrect?How it works Building the modelMinimum edit distanceMinimum edit distance algorithmMinimum edit distance Part 2Minimum edit distance Part 3 往期文章请点 这里 Overview 本周学习目标&#xff1a;…

Spring源码十七:Bean实例化入口探索

上一篇Spring源码十六&#xff1a;Bean名称转化我们讨论doGetBean的第一个方法transformedBeanName方法&#xff0c;了解Spring是如何处理特殊的beanName&#xff08;带&符号前缀&#xff09;与Spring的别名机制。今天我们继续往方法下面看&#xff1a; doGetBean 这个方法…

CDNOW_master.txt数据分析实战

一、数据详情 该数据集是常见的销售数据集&#xff0c;数据展示的是美国1997后的商品销售数据。包含四个字段&#xff0c;分别是用户id,购买时间&#xff0c;销售量&#xff0c;与销售金额。 二、数据读取与数据清洗 导入必要的包 \s代表的许多空格作为分割&#xff0c;names重…

实现antd designable平台的组件拖拽功能

平台&#xff1a;designable设计器 github&#xff1a;designable 目录 1 背景2 技术栈3 组件拖拽和放置3.1 类型定义3.2 拖拽3.3 放置 1 背景 由于业务需求&#xff0c;我们需要实现designable平台的一个简易版的组件拖拽功能。 #mermaid-svg-QrxSDGe9YyGG3LbQ {font-family:…

CSS学习(三大特性 盒子模型)

目录 Emmet语法 1.快速生成HTML结构语法 2.快速生成CSS样式语法 CSS的复合选择器 后代选择器 子选择器 并集选择器 伪类选择器 链接伪类选择器 focus伪类选择器 CSS的三大特性 层叠性 继承性 优先级 CSS盒子模型 组成 边框 边框 内边距 外边距 块级盒子水…

GESP C++一级真题

PDF图片1-7 点赞❤️关注&#x1f60d;收藏⭐️ 互粉必回&#x1f64f;&#x1f64f;&#x1f64f;

用ThreadLocal解决线程隔离问题

存在的以下代码所示的线程隔离问题&#xff1a; package study.用ThreadLocal解决线程隔离问题;/*线程隔离 - 在多线程并发场景下&#xff0c;每个线程的变量都应该是相互独立的线程A&#xff1a;设置&#xff08;变量1&#xff09; 获取&#xff08;变量1&#xff09;线程B&a…

Agilent 安捷伦 DSO91304A 高性能示波器

Agilent 安捷伦 DSO91304A 高性能示波器 DSO91304A Infiniium 高性能示波器&#xff1a;13 GHz 13 GHz4个模拟通道高达 1 Gpts 存储器和 40 GSa/s 采样率可以提供更完整的信号迹线捕获更低的本底噪声&#xff08;50 mV/格时为 1.73 mVrms&#xff09;和深入的抖动分析功能能够…

我国网络安全领域有哪些法律法规?主要内容是什么?

1. 背景介绍 网络信息安全方面的法规在全球范围内都有相应的立法&#xff0c;我们主要的立法有《网络安全法》、《密码法》、《数据安全法》以及《个人信息保护法》。当前也有一些相关的条例和管理办法&#xff0c;接下来就为大家一一介绍。 2. 法规介绍 在中国&#xff0c;…