【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.8


project(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_msgs


if(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 files
  set(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 files
  set(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 center
  float64 x
  float64 y
  float64 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)                      # CHANGE


add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces)    # CHANGE


add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces)  # CHANGE


install(TARGETS
  talker
  listener
  DESTINATION 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 Node


class 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))  # 打印服务调用结果
        break


    minimal_client.destroy_node()  # 销毁节点
    rclpy.shutdown()  # 关闭rclpy


if __name__ == '__main__':
    main()

CMakeLists.txt

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

#...


find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED)         # CHANGE


add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
  rclcpp tutorial_interfaces)                      # CHANGE


add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
  rclcpp tutorial_interfaces)                      # CHANGE


install(TARGETS
  server
  client
  DESTINATION 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.mfbz.cn/a/781611.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【21】【购物车】

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【21】【购物车】 购物车需求描述购物车数据结构数据Model抽取实现流程&#xff08;参照京东&#xff09;代码实现参考 购物车需求描述 用户可以在登录状态下将商品添加到购物车【用户购物…

【LabVIEW学习篇 - 4】:程序结构——条件结构、事件结构、禁用结构

文章目录 条件结构案例一&#xff08;布尔输入&#xff09;案例二&#xff08;整数输入&#xff09;案例三&#xff08;字符串输入&#xff09; 事件结构案例一案例二 禁用结构 条件结构 条件结构的组成部分&#xff1a; 选择器标签&#xff08;带方框的“?”&#xff09;&…

PCIe驱动开发(2)— 第一个简单驱动编写和测试

PCIe驱动开发&#xff08;2&#xff09;— 第一个简单驱动编写和测试 一、前言 教程参考&#xff1a;02_实战部分_PCIE设备测试 教程参考&#xff1a;03_PCIe设备驱动源码解析 二、驱动编写 新建hello_pcie.c文件 touch hello_pcie.c然后编写内容如下所示&#xff1a; #i…

基于java+springboot+vue实现的校园外卖服务系统(文末源码+Lw)292

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;外卖信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广…

目标检测2--yolov1中相关基础知识(边框回归、交并比、nms)介绍

文章目录 前言回归介绍基本概念线性回归非线性回归边框回归 交并比介绍定义程序实现 NMS介绍定义与原理工作原理代码实现 前言 在上篇博客目标检测1–Pytorch目标检测之yolov1中介绍了yolov1的原理&#xff0c;里面提到几个知识点现在详细介绍一下。 回归介绍 在上篇博客中提…

51单片机STC89C52RC——16.1 五项四线步进电机

目的/效果 让步进电机 正向转90度&#xff0c;逆向转90度 一&#xff0c;STC单片机模块 二&#xff0c;步进电机 2.2 什么是步进电机&#xff1f; 步进电机可以理解为&#xff1a;是一个按照固定步幅运动的“小型机器”。它与普通电机不同点在于&#xff0c;普通电机可以持…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(十二)-管道、环境变量、常用命令

大致介绍了一下管道、环境变量、一些常用的基本命令&#xff0c;可以当作学习笔记收藏学习一下&#xff01;&#xff01;&#xff01; 文章目录 前言 一、管道 二、环境变量 1.概念 2.查看 3.修改 4.常用环境变量 三、系统状况 总结 前言 大致介绍了一下管道、环境变量、一些常…

基于Java的水果商品销售网站

1 水果商品销售网站概述 1.1 课题简介 随着电子商务在当今社会的迅猛发展&#xff0c;水果在线销售已逐渐演变为一种极为便捷的购物方式&#xff0c;日益受到人们的青睐。本系统的设计初衷便是构建一个功能完备、用户体验友好的水果销售平台&#xff0c;致力于为用户提供优质、…

昇思25天学习打卡营第14天|基于MindNLP的文本解码原理

基于MindNLP的文本解码原理 文本解码 文本解码是自然语言处理中的一个关键步骤,特别是在任务如机器翻译、文本摘要、自动回复生成等领域。解码过程涉及将编码器(如语言模型、翻译模型等)的输出转换为可读的文本序列。以下是一些常见的文本解码方法和原理: 1. 自回归解码:…

常用的MRI分析软件

MRI&#xff08;磁共振成像&#xff09;分析软件种类繁多&#xff0c;涵盖了从基础图像处理到高级数据分析的各个方面。这些软件广泛应用于临床诊断、研究和教育等领域。以下是一些常用的MRI分析软件&#xff1a; 开源软件 商用软件 特殊用途软件 在线工具和云平台 这些软件各…

孟德尔随机化与痛风3

写在前面 检索检索&#xff0c;刚好发现一篇分区还挺高&#xff0c;但结果内容看上去还挺熟悉的文章&#xff0c;特记录一下。 文章 Exploring the mechanism underlying hyperuricemia using comprehensive research on multi-omics Sci Rep IF:3.8中科院分区:2区 综合性期…

# [0705] Task06 DDPG 算法、PPO 算法、SAC 算法【理论 only】

easy-rl PDF版本 笔记整理 P5、P10 - P12 joyrl 比对 补充 P11 - P13 OpenAI 文档整理 ⭐ https://spinningup.openai.com/en/latest/index.html 最新版PDF下载 地址&#xff1a;https://github.com/datawhalechina/easy-rl/releases 国内地址(推荐国内读者使用)&#xff1a; 链…

渐开线花键测量学习笔记分享

大家好&#xff0c;继续渐开线花键的相关内容&#xff0c;本期是渐开线花键测量相关的学习笔记分享&#xff1a; 花键检测项目有花键大径和小径检验&#xff1b;内花键齿槽宽和外花键齿厚&#xff0c;以及渐开线终止圆 和起始圆直径检测&#xff1b;齿距累计误差 、齿形误差 、…

Python网络爬虫:Scrapy框架的全面解析

Python网络爬虫&#xff1a;Scrapy框架的全面解析 一、引言 在当今互联网的时代&#xff0c;数据是最重要的资源之一。为了获取这些数据&#xff0c;我们经常需要编写网络爬虫来从各种网站上抓取信息。Python作为一种强大的编程语言&#xff0c;拥有许多用于网络爬虫的工具和库…

护网在即,知攻善防助力每一位安服仔~

前言 是不是已经有师傅进场了呢~ 是不是有安服&#x1f412;在值守呢~ 您是不是被网上眼花缭乱的常用应急响应工具而烦恼呢&#xff1f; 何以解忧&#xff1f;唯有知攻善防&#xff01; 创作起源&#xff1a; 驻场、护网等&#xff0c;有的客户现场只允许用客户机器&…

一.7.(2)基本运算电路,包括比例运算电路、加减运算电路、积分运算电路、微分电路等常见电路的分析、计算及应用;(未完待续)

what id the 虚短虚断虚地? 虚短&#xff1a;运放的正相输入端和反相输入端貌似连在一起了&#xff0c;所以两端的电压相等&#xff0c;即UU- 虚断&#xff1a;输入端输入阻抗无穷大 虚地&#xff1a;运放正相输入端接地&#xff0c;导致U&#xff1d;U-&#xff1d;0。 虚…

采用Java语言+开发工具 Idea+ scode数字化产科管理平台源码,产科管理新模式

采用Java语言开发工具 Idea scode数字化产科管理平台源码&#xff0c;产科管理新模式 数字化产科管理系统是现代医疗信息化建设的重要组成部分&#xff0c;它利用现代信息技术手段&#xff0c;对孕产妇的孕期管理、分娩过程及产后康复等各个环节进行数字化、智能化管理&#xf…

lua中判断2个表是否相等

当我们获取 table 长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数&#xff0c;而导致无法正确取得 table 的长度&#xff0c;而且还会出现奇怪的现象。例如&#xff1a;t里面有3个元素&#xff0c;但是因为最后一个下表是5和4&#xff0c;却表现出不一…

SpringBoot3+Vue3开发园区管理系统

介绍 在当今快速发展的城市化进程中&#xff0c;高效、智能的园区管理成为了提升居民生活品质、优化企业运营环境的关键。为此&#xff0c;我们精心打造了全方位、一体化的园区综合管理系统&#xff0c;该系统深度融合了园区管理、楼栋管理、楼层管理、房间管理以及车位管理等…

微信小程序消息通知(一次订阅)

在微信公众平台配置通知模版 通过wx.login获取code发送给后端 let that this // 登陆codewx.login({success: function (res) {if (res.code) {// 发送code到后端换取openid和session_keythat.setData({openCode: res.code})console.log(that.data.openCode, openCode);// 调…