跳到主要内容

接口

1. ROS 2 接口类型

ROS 2 中常见自定义接口有三类:

类型用途文件后缀
MessageTopic 发布/订阅数据.msg
Service请求-响应通信.srv
Action长时间任务,带反馈和结果.action

2. 创建接口包

一般建议单独创建一个接口包,例如:

cd ~/ros2_ws/src
ros2 pkg create my_interfaces --build-type ament_cmake

目录结构建议如下:

my_interfaces/
├── CMakeLists.txt
├── package.xml
├── msg/
│ └── Person.msg
├── srv/
│ └── AddThreeInts.srv
└── action/
└── Move.action

3. 自定义 msg

创建目录和文件:

cd ~/ros2_ws/src/my_interfaces
mkdir msg
touch msg/Person.msg

编辑 msg/Person.msg

string name
int32 age
float64 height

支持的常见字段类型:

bool
byte
char
float32
float64
int8
uint8
int16
uint16
int32
uint32
int64
uint64
string

也可以使用数组:

int32[] scores
float64[3] position
string[] names

还可以嵌套已有消息类型:

std_msgs/Header header
geometry_msgs/Point position

4. 自定义 srv

创建:

mkdir srv
touch srv/AddThreeInts.srv

编辑 srv/AddThreeInts.srv

int64 a
int64 b
int64 c
---
int64 sum

说明:

请求部分
---
响应部分

5. 自定义 action

创建:

mkdir action
touch action/Move.action

编辑 action/Move.action

int32 target
---
bool success
---
int32 current

说明:

Goal
---
Result
---
Feedback

也就是:

目标
---
结果
---
反馈

6. 修改 package.xml

打开 package.xml,添加依赖:

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

如果接口里用到了其他包,比如 std_msgsgeometry_msgs,也要加:

<depend>std_msgs</depend>
<depend>geometry_msgs</depend>

一个较完整示例:

<?xml version="1.0"?>
<package format="3">
<name>my_interfaces</name>
<version>0.0.0</version>
<description>Custom interfaces for ROS 2</description>
<maintainer email="you@example.com">you</maintainer>
<license>Apache-2.0</license>

<buildtool_depend>ament_cmake</buildtool_depend>

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>

7. 修改 CMakeLists.txt

打开 CMakeLists.txt,添加:

cmake_minimum_required(VERSION 3.8)
project(my_interfaces)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Person.msg"
"srv/AddThreeInts.srv"
"action/Move.action"
)

ament_export_dependencies(rosidl_default_runtime)

ament_package()

如果接口中用了 std_msgsgeometry_msgs,例如:

std_msgs/Header header
geometry_msgs/Point point

则 CMakeLists.txt 要这样写:

find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Person.msg"
"srv/AddThreeInts.srv"
"action/Move.action"
DEPENDENCIES std_msgs geometry_msgs
)

8. 编译接口包

回到工作空间:

cd ~/ros2_ws
colcon build --packages-select my_interfaces

加载环境:

source install/setup.bash

查看生成的接口:

ros2 interface list | grep my_interfaces

查看接口内容:

ros2 interface show my_interfaces/msg/Person
ros2 interface show my_interfaces/srv/AddThreeInts
ros2 interface show my_interfaces/action/Move

9. 在 Python 节点中使用自定义 msg

假设另一个包叫 my_py_pkg

9.1 package.xml 添加依赖

<depend>rclpy</depend>
<depend>my_interfaces</depend>

9.2 发布者示例

import rclpy
from rclpy.node import Node
from my_interfaces.msg import Person


class PersonPublisher(Node):
def __init__(self):
super().__init__('person_publisher')
self.publisher_ = self.create_publisher(Person, 'person_topic', 10)
self.timer = self.create_timer(1.0, self.timer_callback)

def timer_callback(self):
msg = Person()
msg.name = 'Tom'
msg.age = 18
msg.height = 1.75

self.publisher_.publish(msg)
self.get_logger().info(f'Publish: {msg.name}, {msg.age}, {msg.height}')


def main(args=None):
rclpy.init(args=args)
node = PersonPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()

9.3 订阅者示例

import rclpy
from rclpy.node import Node
from my_interfaces.msg import Person


class PersonSubscriber(Node):
def __init__(self):
super().__init__('person_subscriber')
self.subscription = self.create_subscription(
Person,
'person_topic',
self.listener_callback,
10
)

def listener_callback(self, msg):
self.get_logger().info(
f'Receive: name={msg.name}, age={msg.age}, height={msg.height}'
)


def main(args=None):
rclpy.init(args=args)
node = PersonSubscriber()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()

10. 在 C++ 节点中使用自定义 msg

10.1 package.xml 添加依赖

<depend>rclcpp</depend>
<depend>my_interfaces</depend>

10.2 CMakeLists.txt 添加依赖

find_package(rclcpp REQUIRED)
find_package(my_interfaces REQUIRED)

add_executable(person_pub src/person_pub.cpp)
ament_target_dependencies(person_pub rclcpp my_interfaces)

install(TARGETS
person_pub
DESTINATION lib/${PROJECT_NAME}
)

10.3 C++ 发布者示例

#include "rclcpp/rclcpp.hpp"
#include "my_interfaces/msg/person.hpp"

class PersonPublisher : public rclcpp::Node
{
public:
PersonPublisher() : Node("person_publisher")
{
publisher_ = this->create_publisher<my_interfaces::msg::Person>(
"person_topic", 10);

timer_ = this->create_wall_timer(
std::chrono::seconds(1),
std::bind(&PersonPublisher::timer_callback, this));
}

private:
void timer_callback()
{
auto msg = my_interfaces::msg::Person();
msg.name = "Tom";
msg.age = 18;
msg.height = 1.75;

publisher_->publish(msg);
RCLCPP_INFO(
this->get_logger(),
"Publish: name=%s age=%d height=%.2f",
msg.name.c_str(),
msg.age,
msg.height
);
}

rclcpp::Publisher<my_interfaces::msg::Person>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<PersonPublisher>());
rclcpp::shutdown();
return 0;
}

11. 命令行测试

发布自定义消息

ros2 topic pub /person_topic my_interfaces/msg/Person "{name: 'Tom', age: 18, height: 1.75}"

查看话题

ros2 topic echo /person_topic

调用自定义服务

ros2 service call /add_three_ints my_interfaces/srv/AddThreeInts "{a: 1, b: 2, c: 3}"

12. 常见错误

12.1 找不到接口包

错误类似:

ModuleNotFoundError: No module named 'my_interfaces'

解决:

cd ~/ros2_ws
colcon build
source install/setup.bash

也可以检查:

echo $ROS_PACKAGE_PATH
ros2 pkg list | grep my_interfaces

12.2 没有添加 member_of_group

如果 package.xml 缺少:

<member_of_group>rosidl_interface_packages</member_of_group>

可能导致接口包识别异常。


12.3 CMakeLists.txt 缺少生成接口

必须有:

rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Person.msg"
)

12.4 接口文件命名不规范

推荐:

Person.msg
AddThreeInts.srv
Move.action

不要用:

person.msg
add_three_ints.srv

ROS 2 接口文件名一般使用 大驼峰命名法


13. 最小完整配置

如果你只想创建一个 Person.msg,最小配置如下。

package.xml

<?xml version="1.0"?>
<package format="3">
<name>my_interfaces</name>
<version>0.0.0</version>
<description>Custom interfaces</description>
<maintainer email="you@example.com">you</maintainer>
<license>Apache-2.0</license>

<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>

CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(my_interfaces)

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Person.msg"
)

ament_export_dependencies(rosidl_default_runtime)
ament_package()

msg/Person.msg

string name
int32 age
float64 height

编译:

cd ~/ros2_ws
colcon build --packages-select my_interfaces
source install/setup.bash

验证:

ros2 interface show my_interfaces/msg/Person

如果你只是想快速记住流程,可以概括为:

创建接口包

新建 msg/srv/action 文件

修改 package.xml

修改 CMakeLists.txt

colcon build

source install/setup.bash

在节点中 import/include 使用