【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

<think>首先,用户的问题是关于ROS2的:"我想查找ROS2中负责编译msg/srv文件的组件 请问ROS2 编译 msg srv 文件的组件是什么"。我需要根据系统级指令来回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用[1]: 关于ROS msgsrv文件的基本格式。 - 引用[2]: ROS2创建自定义消息srv文件的步骤。 - 引用[3]: ROS编译msg/srv时的问题解决方案。 用户的问题核心是:ROS2中负责编译msg/srv文件的组件是什么。 从引用[2]中,我看到:"ROS2 - 创建自定义消息srv文件【上】",它讨论了如何创建自定义msgsrv文件,并修改CMakeLists.txtpackage.xml文件。引用[3]提到编译msg/srv时的问题,涉及message_runtime标签。 在ROS2中,编译msg/srv文件通常涉及使用rosidl(ROS Interface Definition Language)工具链。rosidl是ROS2中用于处理接口定义(包括msgsrv、action)的组件。 关键点: - ROS2使用ament构建系统。 - 编译msg/srv文件时,需要依赖rosidl_default_generators包。 - 在CMakeLists.txt中,使用rosidl_generate_interfaces函数来编译msg/srv文件- package.xml中需要添加相应的依赖。 从引用[2]: - "修改CMakeLists.txt的作用":添加rosidl_generate_interfaces。 - "修改package.xml的作用":添加依赖如rosidl_default_generators。 所以,负责编译msg/srv文件的组件是rosidl工具链,具体是rosidl_default_generators包。 回答结构: 1. 清晰介绍组件。 2. 解释如何使用。 3. 引用相关信息。 4. 生成相关问题。 使用中文回答。 生成相关问题: - 基于主题,如如何创建自定义msg/srv文件,常见问题等。 最终回答: - 开头直接回答问题。 - 然后详细解释。 - 结尾添加相关问题。</think>在ROS2中,负责编译msg/srv文件的组件是**rosidl工具链**,具体由`rosidl_default_generators`包提供核心功能。ROS2使用ament构建系统(ROS2的现代化构建工具),通过`rosidl`(ROS Interface Definition Language)处理接口定义文件的编译。这些文件(.msg.srv)定义了消息服务的数据结构,编译后会生成目标语言(如C++或Python)的代码,供节点间通信使用。下面我将逐步解释该组件的工作原理、使用方法常见配置。 ### 1. **核心组件:rosidl_default_generators** - `rosidl_default_generators`是ROS2官方提供的包,负责将.msg.srv文件编译成可用的代码。它依赖于ROS2的接口定义语言框架(rosidl),自动解析文件中的数据类型(如int32、string)并生成序列化/反序列化代码。 - 编译过程在构建时(通过colcon或ament工具)触发,生成的文件位于包的`build`目录下,最终被链接到节点可执行文件中。 - 引用[2]中提到:"在CMakeLists.txt中使用rosidl_generate_interfaces函数来编译自定义msg/srv文件",这直接依赖于`rosidl_default_generators`[^2]。 ### 2. **如何使用该组件编译msg/srv文件** 要编译自定义msg/srv文件,需在ROS2包中配置以下文件(以创建名为`my_interface_pkg`的包为例): - **步骤1: 创建msg/srv文件** 在包的`msg/`或`srv/`目录下定义文件,例如`msg/MyMessage.msg`: ``` int32 data1 string data2 ``` 或`srv/MyService.srv`: ``` int32 a int32 b --- int32 sum ``` 引用[1]说明:"ROS消息定义文件通常十分简短易懂,都是每一行一个类型名加一个变量名的格式"[^1]。 - **步骤2: 修改CMakeLists.txt** 添加编译指令,使用`rosidl_generate_interfaces`函数: ```cmake find_package(rosidl_default_generators REQUIRED) rosidl_generate_interfaces(${PROJECT_NAME} "msg/MyMessage.msg" "srv/MyService.srv" ) ``` 引用[2]强调:"修改CMakeLists.txt的作用是启用rosidl接口生成,确保编译时调用生成器"[^2]。 - **步骤3: 修改package.xml** 添加依赖项,确保构建时加载`rosidl_default_generators`: ```xml <depend>rosidl_default_generators</depend> <exec_depend>rosidl_default_runtime</exec_depend> ``` 引用[3]指出:编译msg/srv时需正确处理依赖,如`<exec_depend>message_runtime</exec_depend>`(ROS2中`message_runtime`由`rosidl_default_runtime`替代)[^3]。 - **步骤4: 编译包** 在终端运行: ``` colcon build --packages-select my_interface_pkg ``` 编译成功后,生成的代码可在`install/my_interface_pkg/include/`中查看。 ### 3. **常见问题与注意事项** - **依赖错误**:如果package.xml缺少`rosidl_default_generators`或`rosidl_default_runtime`,编译会失败。引用[3]提到:"在ROS2新版本中,需使用`<exec_depend>`标签替代旧版`<run_depend>`"[^3]。 - **文件路径错误**:确保msg/srv文件路径在`rosidl_generate_interfaces`中正确指定,否则生成器无法定位文件- **数据类型支持**:只支持ROS基础类型(如int32、string)或已定义的自定义类型。引用[2]说明:"自定义msg/srv的数据类型需确保兼容性"[^2]。 - **性能**:编译过程高效,通常在几秒内完成,但对大型项目建议增量编译以优化速度。 ### 总结 ROS2中编译msg/srv文件的组件是`rosidl_default_generators`,它集成在ament构建系统中,通过CMakepackage.xml配置实现自动化编译。正确设置后,开发者可轻松创建自定义接口,提升节点通信的灵活性。如果需要更深入的示例,可参考ROS2官方文档或示例仓库。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值