接口
1. ROS 2 接口类型
ROS 2 中常见自定义接口有三类:
| 类型 | 用途 | 文件后缀 |
|---|---|---|
| Message | Topic 发布/订阅数据 | .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_msgs、geometry_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_msgs 或 geometry_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 使用