4. 自定义消息类型
任务目标
在 ROS2 中,当标准消息类型(如 std_msgs/String)无法表达结构化数据时,需要自定义消息。本案例将定义一个包含设备编号、温度值、时间戳和状态标志四个字段的消息类型,并基于该类型完成一个发布者与订阅者的最小通信闭环。
实现步骤
整个过程分为三步:创建消息定义文件、修改构建配置、编写并运行发布订阅节点。以下均在单个 ROS2 工作空间中完成。
1. 创建消息定义
在功能包 custom_msg_demo 中新建目录 msg,创建文件 msg/DeviceStatus.msg:
int32 device_id
float32 temperature
builtin_interfaces/Time timestamp
bool is_alarm
2. 配置构建文件
package.xml 中需添加依赖声明:
<depend>builtin_interfaces</depend>
<depend>rclcpp</depend>
<depend>std_msgs</depend>
CMakeLists.txt 中需包含消息生成与依赖查找:
find_package(builtin_interfaces REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/DeviceStatus.msg"
DEPENDENCIES builtin_interfaces
)
3. 发布订阅节点完整代码
在 src/publisher_node.cpp 中实现发布端:
#include "rclcpp/rclcpp.hpp"
#include "custom_msg_demo/msg/device_status.hpp"
using namespace std::chrono_literals;
class StatusPublisher : public rclcpp::Node
{
public:
StatusPublisher() : Node("status_publisher"), count_(0)
{
publisher_ = this->create_publisher<custom_msg_demo::msg::DeviceStatus>("device_status", 10);
timer_ = this->create_wall_timer(1s, [this]() { publish_status(); });
}
private:
void publish_status()
{
auto msg = custom_msg_demo::msg::DeviceStatus();
msg.device_id = 1001;
msg.temperature = 36.5f + static_cast<float>(count_ % 10) * 0.1f;
msg.timestamp = this->now();
msg.is_alarm = (count_ % 5 == 0);
publisher_->publish(msg);
RCLCPP_INFO(this->get_logger(), "发布: id=%d, temp=%.2f, alarm=%d",
msg.device_id, msg.temperature, msg.is_alarm);
count_++;
}
rclcpp::Publisher<custom_msg_demo::msg::DeviceStatus>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
int count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<StatusPublisher>());
rclcpp::shutdown();
return 0;
}
在 src/subscriber_node.cpp 中实现订阅端:
#include "rclcpp/rclcpp.hpp"
#include "custom_msg_demo/msg/device_status.hpp"
class StatusSubscriber : public rclcpp::Node
{
public:
StatusSubscriber() : Node("status_subscriber")
{
subscription_ = this->create_subscription<custom_msg_demo::msg::DeviceStatus>(
"device_status", 10, [this](const custom_msg_demo::msg::DeviceStatus::SharedPtr msg) {
RCLCPP_INFO(this->get_logger(), "收到: id=%d, temp=%.2f, alarm=%d, time=%d.%d",
msg->device_id, msg->temperature, msg->is_alarm,
msg->timestamp.sec, msg->timestamp.nanosec);
});
}
private:
rclcpp::Subscription<custom_msg_demo::msg::DeviceStatus>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<StatusSubscriber>());
rclcpp::shutdown();
return 0;
}
在 CMakeLists.txt 末尾添加可执行文件与依赖:
add_executable(publisher_node src/publisher_node.cpp)
ament_target_dependencies(publisher_node rclcpp custom_msg_demo)
add_executable(subscriber_node src/subscriber_node.cpp)
ament_target_dependencies(subscriber_node rclcpp custom_msg_demo)
install(TARGETS publisher_node subscriber_node
DESTINATION lib/${PROJECT_NAME})
运行与验证
在工作空间根目录执行以下命令:
colcon build --packages-select custom_msg_demo
source install/setup.bash
然后分别打开两个终端,均先执行 source install/setup.bash,再运行:
ros2 run custom_msg_demo publisher_node
ros2 run custom_msg_demo subscriber_node
通过条件:
- 发布端终端每秒输出一条包含设备编号、温度、报警标志的日志;
- 订阅端终端同步输出对应消息,且字段值与发布端一致;
- 温度值随计数递增而变化,报警标志每 5 次触发一次,证明多字段传输正确。
常见问题:若构建时提示找不到消息头文件,请确认
rosidl_generate_interfaces 已正确调用且 colcon build 后重新 source install/setup.bash。若运行节点时提示类型不可用,检查 package.xml 中是否已声明 <depend>rosidl_default_runtime</depend>。