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>。