首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

rosbag2 源码详细分析

rosbag2 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosbag2
版本:0.15.16(Humble),子包 19 个,许可证 Apache 2.0

rosbag2 是 ROS 2 录制与回放 的完整实现栈:可插拔存储后端(SQLite3 / MCAP)、消息/文件级压缩、序列化格式转换、C++/Python API,以及通过 ros2 bag 暴露的 CLI。设计文档见 ros2/design rosbags

范围说明:本仓库不含 ROS 1 bag 读取插件(rosbag2_bag_v2 在独立仓库);Humble 默认安装 sqlite3 + zstd 插件,MCAP 需额外安装 rosbag2_storage_mcap


1. 总体认识

1.1 核心职责

能力 实现位置 说明
存储抽象 rosbag2_storage ReadOnlyInterface / ReadWriteInterface,pluginlib 加载
默认 SQLite3 rosbag2_storage_default_plugins .db3 文件,默认 storage_id=sqlite3
MCAP 格式 rosbag2_storage_mcap Foxglove 生态,单文件/索引友好
读写 API rosbag2_cpp Reader / WriterSequentialReader/Writer
与 ROS 图集成 rosbag2_transport Recorder / Player,GenericSubscription/Publisher
压缩 rosbag2_compression + _zstd MESSAGE / FILE 两种模式
Python 绑定 rosbag2_py pybind11 暴露 Recorder/Player/convert 等
CLI ros2bag 注册到 ros2cli.commandbag 命令
消息/服务定义 rosbag2_interfaces 回放控制服务、split 事件

1.2 在 ROS 2 栈中的位置

用户ros2bagrosbag2_pyrosbag2_transportrosbag2_cpprosbag2_storage + pluginsrosbag2_compressionROS 2 运行时ros2 bag record / playVerbExtension\nrecord/play/info/convert/reindex/listpybind11\nRecorder / Player / bag_rewriteRecorder\nGenericSubscriptionPlayer\nGenericPublisher + TimeControllerClockReaderWriterFactoryWriter → SequentialWriterReader → SequentialReaderMessageCache\n双缓冲StorageFactory\npluginlibsqlite3mcapSequentialCompressionWriterSequentialCompressionReaderzstd pluginrclcpp / rmwDDS 图
对比项 录制 (Recorder) 回放 (Player)
ROS 接口 GenericSubscription 订阅 live topic GenericPublisher 重放历史消息
时间源 系统时钟或 /clock(sim time) TimeControllerClock 控制回放节奏
写入/读取 Writer::write(SerializedMessage) Reader::read_next()
默认存储 SequentialWriter + sqlite3 SequentialReader,压缩 bag 自动选 SequentialCompressionReader
发现 周期性 topic discovery(可关闭) 无 discovery,topic 来自 bag metadata

1.3 命令行分层(与 ros2cli 集成)

1
2
3
4
5
6
7
8
ros2                          ← ros2cli 主入口
└── bag ← entry point: ros2bag.command.bag:BagCommand
├── record ← ros2bag.verb.record:RecordVerb
├── play ← ros2bag.verb.play:PlayVerb
├── info ← ros2bag.verb.info:InfoVerb
├── convert ← ros2bag.verb.convert:ConvertVerb
├── reindex ← ros2bag.verb.reindex:ReindexVerb
└── list ← ros2bag.verb.list:ListVerb

ros2bag/setup.py 通过 setuptools entry_points 注册;BagCommand 使用 add_subparsers_on_demand 按需加载 verb 插件,与 ros2 topic 等命令模式一致。


2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
rosbag2/
├── README.md # 用户文档(record/play/split/compression/convert)
├── rosbag2/ # ★ metapackage 0.15.16
├── rosbag2_storage/ # ★ 存储抽象 (~2500 行 C++)
│ ├── include/rosbag2_storage/
│ │ ├── storage_interfaces/ # ReadOnly / ReadWrite / BaseWrite
│ │ ├── serialized_bag_message.hpp
│ │ ├── bag_metadata.hpp
│ │ ├── topic_metadata.hpp
│ │ ├── storage_options.hpp
│ │ └── storage_factory.hpp
│ └── src/.../storage_factory_impl.hpp # pluginlib 加载逻辑
├── rosbag2_storage_default_plugins/ # SQLite3 插件
├── rosbag2_storage_mcap/ # MCAP 插件
├── rosbag2_cpp/ # ★ 核心 API (~9400 行)
│ ├── writer.hpp / reader.hpp
│ ├── writers/sequential_writer.cpp
│ ├── readers/sequential_reader.cpp
│ ├── cache/message_cache.hpp # 双缓冲写缓存
│ └── reindexer.cpp
├── rosbag2_compression/ # 压缩读写包装 (~3100 行)
├── rosbag2_compression_zstd/ # zstd compressor 插件
├── rosbag2_transport/ # ★ Recorder/Player (~8600 行)
│ ├── recorder.cpp / player.cpp
│ ├── bag_rewrite.cpp
│ └── reader_writer_factory.cpp
├── rosbag2_py/ # Python 绑定
├── ros2bag/ # CLI (Python)
├── rosbag2_interfaces/ # msg/srv 定义
├── sqlite3_vendor / zstd_vendor / mcap_vendor / shared_queues_vendor
├── rosbag2_tests / rosbag2_test_common
└── rosbag2_performance/ # 性能基准

2.1 子包一览

包名 类型 职责
rosbag2 metapackage 聚合依赖,默认带 sqlite3 + zstd
rosbag2_storage 存储接口、metadata IO、StorageFactory
rosbag2_storage_default_plugins 插件 sqlite3SqliteStorage
rosbag2_storage_mcap 插件 mcap 存储
rosbag2_cpp Reader/Writer、顺序读写、Reindexer、Info
rosbag2_transport Recorder、Player、bag_rewrite、工厂
rosbag2_compression SequentialCompressionReader/Writer
rosbag2_compression_zstd 插件 zstd 压缩器
rosbag2_py pybind11 绑定
ros2bag CLI ros2 bag 命令
rosbag2_interfaces 接口 WriteSplitEvent、Playback 控制 srv
sqlite3_vendor vendor 打包 sqlite3
zstd_vendor vendor 打包 zstd
mcap_vendor vendor 打包 mcap C++ 库
shared_queues_vendor vendor moodycamel 无锁队列(Player 预读)
rosbag2_tests 测试 集成测试
rosbag2_test_common 测试 公共测试工具
rosbag2_storage_mcap_testdata 测试 MCAP 测试数据
rosbag2_performance_benchmarking 工具 性能对比

3. 三层插件体系

rosbag2 的可扩展性来自 三类独立插件,均通过 pluginlib 或工厂模式注册:

3.1 存储插件 (Storage)

基类rosbag2_storage::storage_interfaces::ReadWriteInterface(读写合一)
只读变体ReadOnlyInterface(部分插件可只读打开)

加载路径storage_factory_impl.hpp):

  1. storage_options.storage_id 非空 → 按 ID 加载对应 class(如 sqlite3mcap
  2. 若为空且为 → 遍历已注册插件,逐个 open() 尝试,首个成功者选用
  3. 读模式下若 ReadOnly 插件失败 → 回退尝试 ReadWrite 插件以 READ_ONLY 打开
1
2
3
4
<!-- rosbag2_storage_default_plugins/plugin_description.xml -->
<class name="sqlite3"
type="rosbag2_storage_plugins::SqliteStorage"
base_class_type="rosbag2_storage::storage_interfaces::ReadWriteInterface"/>

SQLite3 实现要点sqlite_storage.cpp):

  • 表结构存储 topic metadata 与 serialized message blob
  • 支持 storage_config_uri YAML 配置 SQLite pragma(read/write 分节)
  • get_minimum_split_file_size() 返回最小 split 阈值(默认约 86KB,与 CLI -b 说明一致)
  • 支持 IOFlag::READ_ONLY / READ_WRITE / APPEND

MCAProsbag2_storage_mcap):面向 robotics 日志的单文件格式,索引与 Foxglove Studio 兼容;通过 --storage mcap 选用。

3.2 序列化格式转换 (Serialization Converter)

目的:bag 内存储的序列化格式可与录制时 RMW 格式不同(如 cdr ↔ protobuf 实验性路径)。

  • ConverterOptionsinput_serialization_format / output_serialization_format
  • SerializationFormatConverterFactoryInterface 加载 *_converter 插件
  • SequentialWriter::open() 若输入/输出格式不同则创建 converter_,在 write() 路径转换

Recorder 打开 writer 时传入:

1
2
writer_->open(storage_options_,
{rmw_get_serialization_format(), record_options_.rmw_serialization_format});

即:输入为当前 RMW 格式,输出为 CLI --serialization-format 指定格式(默认同 RMW)。

3.3 压缩插件 (Compression)

配置compression_options.hpp):

字段 含义
compression_format zstd(来自 rosbag2_compression_zstd
compression_mode NONE / FILE / MESSAGE
compression_queue_size 异步压缩队列深度
compression_threads 压缩线程数(0 → 硬件并发数)

工厂选择reader_writer_factory.cpp):

  • 录制:compression_format 非空 → SequentialCompressionWriter 包装 SequentialWriter

  • 回放:读 metadata.yamlcompression_format 非空 → SequentialCompressionReader

  • MESSAGE 模式:每条消息压缩后写入 storage

  • FILE 模式:关闭 bag 文件时对整文件压缩(split 时按文件压缩)


4. 数据模型

4.1 SerializedBagMessage

单条 bag 消息的最小单元(serialized_bag_message.hpp):

1
2
3
4
5
struct SerializedBagMessage {
std::shared_ptr<rcutils_uint8_array_t> serialized_data;
rcutils_time_point_value_t time_stamp; // 纳秒时间戳
std::string topic_name;
};
  • 不做 ROS 类型反序列化;payload 为 RMW 序列化字节流
  • Recorder 从 rclcpp::SerializedMessage 拷贝 buffer 与时间戳写入

4.2 TopicMetadata

1
2
3
4
5
6
struct TopicMetadata {
std::string name;
std::string type; // 如 sensor_msgs/msg/Image
std::string serialization_format; // 如 cdr
std::string offered_qos_profiles; // YAML 序列化的 QoS 列表
};

回放时 Player 用 metadata 中的 type 创建 GenericPublisher,并按 recorded QoS(或 override)发布。

4.3 BagMetadata 与 metadata.yaml

BagMetadatabag_metadata.hppversion = 5):

字段 说明
storage_identifier sqlite3mcap
relative_file_paths bag 目录内相对路径列表
files[] 每个分片的 path、starting_time、duration、message_count
starting_time / duration / message_count 整 bag 统计
topics_with_message_count[] topic 元数据 + 消息计数
compression_format / compression_mode 压缩信息

MetadataIo 负责读写 bag 目录下的 metadata.yamlSequentialReader 打开时先读 metadata,再按 relative_file_paths 解析分片路径(version < 4 的路径规则不同,见 resolve_relative_paths)。

典型目录结构:

1
2
3
4
my_bag/
├── metadata.yaml
├── my_bag_0.db3 # sqlite3 分片
└── my_bag_0.db3.zstd # FILE 压缩时可能出现

4.4 StorageOptions

录制/回放/convert 共用(storage_options.hpp):

选项 默认 说明
uri bag 目录路径
storage_id 写时默认 sqlite3 存储插件 ID
max_bagfile_size 0 字节,超过则 split
max_bagfile_duration 0 秒,超过则 split
max_cache_size 0 消息缓存条数,0=直写磁盘
storage_preset_profile “” 预设配置名
storage_config_uri “” 插件专用 YAML(如 SQLite pragma)
snapshot_mode false 环形缓冲 + ~/snapshot 服务

5. rosbag2_cpp:读写核心

5.1 Writer 门面

rosbag2_cpp::Writer 持有 BaseWriterInterface 实现(默认 SequentialWriter),提供:

  • open(storage_options, converter_options)
  • create_topic / remove_topic
  • write(SerializedBagMessage)write(SerializedMessage, topic, type, time)
  • take_snapshot()(snapshot 模式)
  • add_event_callbacks(如 split 事件)

线程安全Writer 层有 mutex,防止并发 open/close/write 竞态。

5.2 SequentialWriter 流程

  1. open()storage_factory_->open_read_write() 创建插件实例
  2. init_metadata():初始化 BagMetadata,记录首个相对路径
  3. create_topic():写 topic 行到 storage + 更新 metadata
  4. write()
    • 可选 converter 转换序列化格式
    • max_cache_size > 0MessageCache 双缓冲;否则直接 storage_->write()
    • 更新 duration、message_count
  5. close():刷 cache、写最终 metadata.yaml

Split 逻辑:当单文件 size 或 duration 超阈值,关闭当前 storage、新建分片文件,更新 relative_file_paths,触发 write_split_callback(Recorder 发布 WriteSplitEvent)。

默认常量:

1
static constexpr char const * kDefaultStorageID = "sqlite3";

5.3 MessageCache(双缓冲)

message_cache.hpp 实现 greedy consumer 双缓冲:

  • Producer(subscription 回调)push 到主 buffer
  • Consumer(磁盘写入线程)swap_buffers 取走数据
  • buffer 满时 丢弃 消息并计数(性能告警信号)
  • flush 状态用于 shutdown 时排空

设计目标:避免慢速磁盘阻塞 DDS 回调线程。

5.4 SequentialReader 流程

  1. open():读 metadata → 打开首个 storage 分片
  2. has_next() / read_next():顺序返回 SerializedBagMessage
  3. 当前分片读完 → 自动打开 relative_file_paths 中下一个
  4. 支持 seek(timestamp)set_filter(StorageFilter)(topic 白名单)
  5. 若存在 converter,读出的消息可转换为目标序列化格式

5.5 Reindexer

metadata.yaml 损坏或与 .db3 不一致时,Reindexer::reindex() 扫描目录内数据库文件,重建 metadata(reindexer.cpp)。CLI:ros2 bag reindex


6. rosbag2_transport:录制路径

6.1 Recorder 架构

Recorder 继承 rclcpp::Node,核心成员:

  • Writer(通过 ReaderWriterFactory::make_writer 创建,可能为压缩 writer)
  • subscriptions_GenericSubscription 映射
  • topics_discovery 异步线程(除非 --no-discovery
  • paused_ 原子标志
  • snapshot 模式下的 ~/snapshot 服务

6.2 record() 主流程

1
2
3
4
5
6
7
record()
├─ writer_->open(storage_options, converter_options)
├─ [snapshot_mode] 创建 Snapshot 服务
├─ 注册 write_split 回调 → 发布 WriteSplitEvent
├─ subscribe_topics(初始 topic 集)
├─ [可选] std::async topics_discovery 循环
└─ 等待 spin(CLI 侧 rclpy/py 驱动 executor)

6.3 订阅与写入

关键顺序(避免竞态):

1
2
3
4
5
6
7
void Recorder::subscribe_topic(const TopicMetadata & topic) {
writer_->create_topic(topic); // 必须先注册 topic
auto sub = create_generic_subscription(..., [this](SerializedMessage msg) {
if (!paused_.load())
writer_->write(msg, topic_name, topic_type, get_clock()->now());
});
}

QoS 适配

  • 默认 Rosbag2QoS::adapt_request_to_offers() 根据 publisher 的 offered QoS 选择 subscription QoS
  • --qos-profile-overrides-path 可 per-topic 覆盖
  • discovery 过程中若新 publisher QoS 与已订阅不兼容,会 warn_if_new_qos_for_subscribed_topic

Topic 选择

  • 显式 topic 列表、-a(全部)、-e / -x 正则过滤
  • get_requested_or_available_topics() 合并请求与图发现结果
  • --include-hidden-topics / --include-unpublished-topics 扩展发现范围

Sim timeuse_sim_time 时 Recorder 使用 /clock;在收到首个 clock 前不应写消息(README 警告 time=0 问题)。

键盘控制:空格切换 pause/resume(keyboard_handler)。


7. rosbag2_transport:回放路径

7.1 Player 架构

  • Reader + TimeControllerClock(速率、pause、seek)
  • moodycamel::ReaderWriterQueue 预读队列(read_ahead_queue_size,默认 1000)
  • GenericPublisher per topic
  • 可选 /clock 定时发布(clock_publish_frequency
  • 控制服务:pauseresumetoggle_pausedset_rateseekburstplay_next 等(rosbag2_interfaces

7.2 play() 主流程

1
2
3
4
5
6
7
8
9
10
play()
loop (若 play_options_.loop):
├─ [delay] sleep
├─ reader_->seek(starting_time_); clock_->jump(...)
├─ async load_storage_content() // 后台读 bag 填队列
├─ wait_for_filled_queue()
├─ play_messages_from_queue()
│ └─ clock_->sleep_until(msg.timestamp) // 节奏控制
│ └─ generic_publisher->publish(serialized)
└─ [可选] wait_for_all_acked

时间控制TimeControllerClock 用 steady clock 映射 bag 时间戳,支持 rate 倍速、pauseseek

QoS:默认从 bag metadata 恢复;PlayOptions::topic_qos_profile_overrides 可覆盖。

Remappingtopic_remapping_options 支持回放时改名。


8. bag_rewrite 与 convert

rosbag2_transport::bag_rewrite()bag_rewrite.hpp)是 通用 bag 变换引擎

输入:多个 StorageOptions(多个源 bag)
输出:多个 (StorageOptions, RecordOptions) 对(目标 bag 配置)

能力组合:

场景 实现方式
合并多 bag 多 input → 单 output
格式转换 storage_id(sqlite3 → mcap)
压缩/解压 RecordOptions.compression_*
序列化转换 rmw_serialization_format
过滤 topic output RecordOptions.topics
Split output max_bagfile_size / duration

CLI:ros2 bag convert -i input_options.yaml -o output_options.yaml
Python:rosbag2_py.bag_rewrite()
C++:rosbag2_transport::bag_rewrite()


9. rosbag2_py 与 ros2bag CLI

9.1 pybind11 模块(_transport.cpp 等)

暴露类型:

  • StorageOptions / RecordOptions / PlayOptions
  • Recorder / Player
  • bag_rewrite / Reindexer / Info
  • get_registered_writers() / get_registered_compressors() 等 introspection

RecordVerb 典型用法:

1
2
3
from rosbag2_py import Recorder, RecordOptions, StorageOptions
recorder = Recorder('my_recorder', '', None)
recorder.record(storage_options, record_options)

CLI 解析参数后构造 C++ 对象,在 Python 侧 rclpy.spin 或直接调用阻塞式 record()

9.2 各 verb 职责

Verb 调用链
record RecordVerbrosbag2_py.RecorderRecorder::record
play PlayVerbrosbag2_py.PlayerPlayer::play
info InfoVerbrosbag2_py info API → 读 metadata
convert ConvertVerbbag_rewrite
reindex ReindexVerbReindexer
list ListVerb → 列出已注册 storage/compression 插件

10. rosbag2_interfaces

类型 名称 用途
msg WriteSplitEvent 录制分片通知(closed/opened file)
msg ReadSplitEvent 回放切换分片(内部)
srv Snapshot snapshot 模式触发落盘
srv Pause / Resume / TogglePaused / IsPaused 回放控制
srv SetRate / GetRate 倍速
srv Seek 跳转时间点
srv Burst / PlayNext 单步/突发播放

Recorder 发布 events/write_split;Player 提供 playback 控制服务(默认 namespace 下)。


11. 高级功能摘要

11.1 Bag splitting

  • CLI:-b 字节阈值、-d 秒阈值
  • 二者同时启用时 先触达者 split
  • SequentialWriter 负责切换文件;metadata 记录所有分片

11.2 Snapshot 模式

  • StorageOptions.snapshot_mode = true
  • Writer 维护环形缓冲,仅保留最近 N 消息
  • 调用 ~/snapshot 服务将缓冲 flush 到磁盘

11.3 QoS override 文件

YAML 指定 topic → QoS profile,用于录制订阅或回放发布,解决 recorded QoS 与当前系统不匹配问题。

11.4 性能相关选项

  • max_cache_size:录制写缓存
  • compression_queue_size / compression_threads:压缩流水线
  • read_ahead_queue_size:回放预读
  • SQLite storage_config_uri pragma tuning(如 WAL、synchronous)

12. 与 ROS 1 rosbag 对比

维度 ROS 1 rosbag rosbag2
存储格式 单一 .bag(自定义) 插件化(sqlite3/mcap/…)
消息表示 录制时反序列化再存 默认存 RMW 序列化字节(更高效、格式可转换)
元数据 bag 内嵌 目录 + metadata.yaml + 数据文件
QoS 无 DDS QoS 录制/回放 offered_qos_profiles
压缩 bz2 zstd,MESSAGE/FILE 模式
CLI rosbag record/play ros2 bag record/play/...
工具链 紧耦合 分层:storage / cpp / transport / py
Sim time /clock 同样支持,实现于 Recorder/Player

ROS 1 bag 直接读取需额外 rosbag2_bag_v2 插件(本仓库不含)。


13. 端到端序列图

13.1 录制

SqliteStorageMessageCacheWriterRecorderGenericSubscriptionDDSSqliteStorageMessageCacheWriterRecorderGenericSubscriptionDDSalt[not paused]close 时写 metadata.yamlSerializedMessagecallbackwrite(msg, topic, time)push (if cache enabled)flush batchwrite(SerializedBagMessage)

13.2 回放

DDSGenericPublisherPlayerReadAheadQueueReaderStorageDDSGenericPublisherPlayerReadAheadQueueReaderStorageloop[load_storage_con-tent]loop[play_messages_fr-om_queue]open(uri)read metadata + messagesenqueue messagespop messageclock.sleep_until(ts)publish(serialized)publish

14. 调试与常见问题

现象 排查方向
录不到消息 topic 是否 discovery 到;是否 start_paused;sim time 下是否收到 /clock
QoS 不匹配丢包 对比 ros2 topic info -v 与 bag metadata QoS;尝试 overrides YAML
回放无订阅者 检查 topic remapping、filter、类型名是否一致
metadata 损坏 ros2 bag reindex
转换失败 检查 storage_id、compression、serialization converter 是否安装
性能差 / 丢消息 增大 max_cache_size;检查 MessageCache dropped 计数;磁盘 IO
插件加载失败 ros2 bag list 看注册插件;pluginlib 库路径 / AMENT_PREFIX_PATH

日志:各层使用 ROS_BAG_LOG / RCLCPP_*;storage 层 ROSBAG2_STORAGE_LOG_*

实用命令

1
2
3
4
ros2 bag info my_bag
ros2 bag list # 已注册 storage/compressor
ros2 bag record -a --storage mcap
ros2 bag play my_bag --rate 2.0 --clock 100

15. 源码阅读顺序

  1. 数据模型serialized_bag_message.hpptopic_metadata.hppbag_metadata.hppstorage_options.hpp
  2. 插件加载storage_factory_impl.hppplugin_description.xml(sqlite3)
  3. 写路径writer.hppsequential_writer.cppmessage_cache.hpp
  4. 读路径reader.hppsequential_reader.cpp
  5. 压缩reader_writer_factory.cppsequential_compression_writer.cpp
  6. 录制record_options.hpprecorder.cppsubscribe_topic / record
  7. 回放play_options.hppplayer.cppplay / play_messages_from_queue
  8. CLIros2bag/setup.pyverb/record.pyrosbag2_py/_transport.cpp
  9. 高级bag_rewrite.cppreindexer.cpp

16. 小结

rosbag2 目录实现 ROS 2 可插拔日志栈:底层 storage 插件 持久化序列化字节;rosbag2_cpp 提供顺序读写、缓存与 metadata;rosbag2_transport 连接 rclcpp 图(GenericSubscription/Publisher)与时间控制;rosbag2_compressionros2bag/rosbag2_py 完成压缩与用户接口。

理解任意 bug 或性能问题,通常从 Recorder::subscribe_topicWriter::write → storage 插件(录制)或 Player::playReader::read_next → GenericPublisher(回放)两条主链入手,并核对 metadata.yaml 与 QoS/serialization 配置 是否一致。

rosidl_dds 源码详细分析

rosidl_dds 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl_dds
子包数量:1


1. 定位

rosidl_dds 目录含 1 个 ROS 2 包,工作区路径见下。


2. 子包列表

包名 版本 说明
rosidl_generator_dds_idl 0.8.1 Generate the DDS interfaces for ROS interfaces.

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

5. 小结

rosidl_dds 为单包仓库,提供 rosidl_generator_dds_idl 功能。

rosidl_defaults 源码详细分析

rosidl_defaults 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl_defaults
子包数量:2


1. 定位

rosidl_defaults 目录含 2 个 ROS 2 包,工作区路径见下。


2. 子包列表

包名 版本 说明
rosidl_default_generators 1.2.1 A configuration package defining the default ROS interface g…
rosidl_default_runtime 1.2.1 A configuration package defining the runtime for the ROS int…

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

5. 小结

rosidl_defaults 为含 2 个子包的源码树,是 ROS 2 Humble 发行版的一部分。

rosidl_python 源码详细分析

rosidl_python 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl_python
子包数量:1


1. 定位

rosidl_python 目录含 1 个 ROS 2 包,工作区路径见下。


2. 子包列表

包名 版本 说明
rosidl_generator_py 0.14.6 Generate the ROS interfaces in Python.

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

5. 小结

rosidl_python 为单包仓库,提供 rosidl_generator_py 功能。

rosidl_runtime_py 源码详细分析

rosidl_runtime_py 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl_runtime_py
子包数量:1


1. 定位

rosidl_runtime_py 目录含 1 个 ROS 2 包,工作区路径见下。


2. 子包列表

包名 版本 说明
rosidl_runtime_py 0.9.3 Runtime utilities for working with generated ROS interfaces …

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

5. 小结

rosidl_runtime_py 为单包仓库,提供 rosidl_runtime_py 功能。

rosidl_typesupport_fastrtps 源码详细分析

rosidl_typesupport_fastrtps 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl_typesupport_fastrtps
版本:2.2.4(Humble),子包 3 个,许可证 Apache 2.0

rosidl_typesupport_fastrtps 是 ROS 2 在 Fast-DDS / Fast-RTPS RMW 下的 CDR 序列化 typesupport 插件:为每条消息/服务生成 fastcdr::Cdr 读写代码,并通过 message_type_support_callbacks_t 回调表暴露给 rmw_fastrtps_cpp。本仓库 不实现 DDS 传输层——那是 rmw_fastrtps 的职责;此处只负责 ROS 消息 ↔ CDR 字节流

范围说明:分发层(rosidl_typesupport_c/cpp)在独立仓库 rosidl_typesupport;本仓库是 RMW 实际调用的 concrete typesupport 插件 之一。Humble 默认 RMW 为 rmw_fastrtps_cpp,因此本插件为数据面关键路径。


1. 总体认识

1.1 核心职责

能力 实现位置 说明
Fast-CDR 序列化生成 rosidl_typesupport_fastrtps_c/cpp Empy 模板生成 per-message CDR 代码
回调结构定义 message_type_support.h cdr_serialize / cdr_deserialize 等函数指针
typesupport handle 生成源文件 rosidl_message_type_support_t + identifier
wstring 转换 wstring_conversion.cpp ROS u16string ↔ FastCDR wstring
CMake 发现 FastRTPS fastrtps_cmake_module FindFastRTPS.cmake 包装 fastcdr/fastrtps
ament index 注册 两 generator 包 注册为 rosidl_typesupport_c/cpp 插件

1.2 在 ROS 2 栈中的位置

rosidl 工具链rosidl_typesupport_fastrtpsrmw_fastrtps网络"dlopen libpkg__fastrtps_cpp""callbacks->cdr_serialize"rosidl_generator_c/cpp\nstruct 定义rosidl_typesupport_c/cpp\n分发层 maprosidl_typesupport_fastrtps_c\nC struct CDRrosidl_typesupport_fastrtps_cpp\nC++ struct CDRfastrtps_cmake_moduleMessageTypeSupport\nTypeSupport::serializeROSmessageFast-DDS DataWriter/ReaderCDR 字节 + encapsulation
对比项 rosidl_typesupport(分发层) rosidl_typesupport_fastrtps(本仓库)
identifier rosidl_typesupport_cpp rosidl_typesupport_fastrtps_cpp
data 字段 type_support_map_t message_type_support_callbacks_t
序列化 Fast-CDR 实现
消费者 rcl/rclcpp 入口 rmw_fastrtps 直接读 callbacks
库名 libpkg__rosidl_typesupport_cpp.so libpkg__rosidl_typesupport_fastrtps_cpp.so

1.3 端到端 publish 数据路径(简化)

1
2
3
4
5
6
7
8
9
rclcpp::Publisher::publish(msg)
→ rosidl_typesupport_cpp dispatch handle
→ dlopen libstd_msgs__rosidl_typesupport_fastrtps_cpp.so
→ get_message_type_support_handle<std_msgs::msg::String>()
→ rmw_fastrtps_cpp::MessageTypeSupport(callbacks)
→ TypeSupport::serializeROSmessage()
ser.serialize_encapsulation()
callbacks->cdr_serialize(ros_message, ser)
→ Fast-DDS 发送 CDR buffer

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rosidl_typesupport_fastrtps/
├── fastrtps_cmake_module/ # FindFastRTPS.cmake
│ └── cmake/Modules/FindFastRTPS.cmake
├── rosidl_typesupport_fastrtps_cpp/ # ★ C++ 消息 CDR + 回调结构定义
│ ├── include/rosidl_typesupport_fastrtps_cpp/
│ │ ├── message_type_support.h # callbacks 结构体(C/C++ 共用)
│ │ ├── service_type_support.h
│ │ ├── message_type_support_decl.hpp
│ │ └── wstring_conversion.hpp
│ ├── src/identifier.cpp, wstring_conversion.cpp
│ ├── resource/*.em # Empy 生成模板
│ └── cmake/rosidl_typesupport_fastrtps_cpp_generate_interfaces.cmake
└── rosidl_typesupport_fastrtps_c/ # ★ C 消息 CDR(依赖 C++ 包头)
├── resource/*_c.cpp.em / *_c.h.em
└── cmake/rosidl_typesupport_fastrtps_c_generate_interfaces.cmake

2.1 子包一览

包名 版本 职责
fastrtps_cmake_module 2.2.4 提供 FindFastRTPS.cmake,查找 fastcdr/fastrtps
rosidl_typesupport_fastrtps_cpp 2.2.4 C++ typesupport 生成器 + 运行时辅助库
rosidl_typesupport_fastrtps_c 2.2.4 C typesupport 生成器(序列化 rosidl_runtime_c__* 类型)

3. fastrtps_cmake_module

3.1 作用

Historically ROS 2 用统一 CMake 模块查找 eProsima 组件。FindFastRTPS.cmake 封装:

1
2
3
find_package(fastcdr REQUIRED CONFIG)
find_package(fastrtps REQUIRED CONFIG)
# 设置 FastRTPS_INCLUDE_DIR, FastRTPS_LIBRARIES, FastCDR_LIBRARIES

注意:本仓库生成代码 直接使用 fastcdr#include <fastcdr/Cdr.h>),不直接在模板里调用 FastRTPS API。FindFastRTPS 主要供 下游 RMW 与依赖链使用;generator 的 find_package(fastcdr REQUIRED CONFIG) 才是编译关键。

3.2 质量等级

QL 4(辅助模块),与两个 typesupport 包(QL 1)不同。


4. message_type_support_callbacks_t — 核心契约

定义于 rosidl_typesupport_fastrtps_cpp/message_type_support.hC 与 C++ 生成代码共用

1
2
3
4
5
6
7
8
9
typedef struct message_type_support_callbacks_t {
const char * message_namespace_;
const char * message_name_;

bool (* cdr_serialize)(const void * untyped_ros_message, eprosima::fastcdr::Cdr & cdr);
bool (* cdr_deserialize)(eprosima::fastcdr::Cdr & cdr, void * untyped_ros_message);
uint32_t (* get_serialized_size)(const void *);
size_t (* max_serialized_size)(char & bounds_info);
} message_type_support_callbacks_t;

4.1 四个回调的含义

回调 用途
cdr_serialize 将 ROS 消息写入 FastCDR(已去 encapsulation)
cdr_deserialize 从 FastCDR 读出 ROS 消息
get_serialized_size 运行时计算 实际 序列化大小(含变长 string/sequence)
max_serialized_size 编译期/初始化期计算 最大 大小,并返回 bounds 分类

4.2 bounds_info 与零拷贝优化

1
2
3
#define ROSIDL_TYPESUPPORT_FASTRTPS_UNBOUNDED_TYPE 0x00
#define ROSIDL_TYPESUPPORT_FASTRTPS_BOUNDED_TYPE 0x01
#define ROSIDL_TYPESUPPORT_FASTRTPS_PLAIN_TYPE 0x03

rmw_fastrtps_cpp::TypeSupport::set_members() 读取 max_serialized_size(bounds_info)

  • PLAIN:POD 型消息,内存布局与 CDR 一致 → 可 loan/sample 零拷贝 优化
  • BOUNDED:有界(无有界 string/sequence 外的 unbounded)
  • UNBOUNDED:含 unbounded string/sequence
1
2
3
4
5
// rmw_fastrtps_cpp/src/type_support_common.cpp
auto data_size = members->max_serialized_size(bounds_info);
max_size_bound_ = 0 != (bounds_info & ROSIDL_TYPESUPPORT_FASTRTPS_BOUNDED_TYPE);
is_plain_ = bounds_info == ROSIDL_TYPESUPPORT_FASTRTPS_PLAIN_TYPE;
m_typeSize = 4 + data_size; // + encapsulation,4 字节对齐

空消息特殊处理:plain 且 size=0 时加 dummy byte。

4.3 service_type_support_callbacks_t

1
2
3
4
5
6
typedef struct service_type_support_callbacks_t {
const char * service_namespace_;
const char * service_name_;
const rosidl_message_type_support_t * request_members_;
const rosidl_message_type_support_t * response_members_;
} service_type_support_callbacks_t;

Service handle 的 data 指向此结构;RMW 分别对 Request/Response 消息 callbacks 创建 RequestTypeSupport / ResponseTypeSupport


5. 代码生成(Empy 模板)

5.1 生成入口

C++rosidl_typesupport_fastrtps_cpp/__init__.py):

1
2
3
4
5
6
mapping = {
'idl__rosidl_typesupport_fastrtps_cpp.hpp.em':
'detail/%s__rosidl_typesupport_fastrtps_cpp.hpp',
'idl__type_support.cpp.em':
'detail/dds_fastrtps/%s__type_support.cpp',
}

Crosidl_typesupport_fastrtps_c/__init__.py):

1
2
3
4
5
6
mapping = {
'idl__rosidl_typesupport_fastrtps_c.h.em':
'detail/%s__rosidl_typesupport_fastrtps_c.h',
'idl__type_support_c.cpp.em':
'detail/%s__type_support_c.cpp',
}

5.2 CMake 扩展注册

rosidl_typesupport_fastrtps_cpp-extras.cmake.in

1
2
3
4
5
6
7
8
if(NOT fastcdr_FOUND)
message(STATUS "Could not find eProsima Fast CDR - skip rosidl_typesupport_fastrtps_cpp")
else()
ament_register_extension(
"rosidl_generate_idl_interfaces"
"rosidl_typesupport_fastrtps_cpp"
"rosidl_typesupport_fastrtps_cpp_generate_interfaces.cmake")
endif()

前置条件

  • C++:__rosidl_generator_cpp 必须先存在
  • C:__rosidl_generator_c + rosidl_typesupport_fastrtps_cpp

ament index(构建时注册为可用插件):

1
2
3
4
# rosidl_typesupport_fastrtps_cpp/CMakeLists.txt
ament_index_register_resource("rosidl_typesupport_cpp")
# rosidl_typesupport_fastrtps_c/CMakeLists.txt
ament_index_register_resource("rosidl_typesupport_c")

5.3 生成的 CMake Target

Target 后缀 产物
__rosidl_typesupport_fastrtps_cpp lib{pkg}__rosidl_typesupport_fastrtps_cpp.so
__rosidl_typesupport_fastrtps_c lib{pkg}__rosidl_typesupport_fastrtps_c.so

链接依赖:fastcdrrmw::rmwrosidl_runtime_*librosidl_typesupport_fastrtps_cpp.so(C 包)。


6. C++ 生成模板详解(msg__type_support.cpp.em

6.1 生成结构概览

对每条消息 MyMsg,生成:

  1. 公开 CDR 函数typesupport_fastrtps_cpp 命名空间)
    cdr_serialize / cdr_deserialize / get_serialized_size / max_serialized_size_MyMsg
  2. 静态包装函数(type-erased,void*
  3. message_type_support_callbacks_t 静态实例
  4. rosidl_message_type_support_t handle
  5. C++ 模板特化 + extern “C” 导出符号

6.2 handle 组装

1
2
3
4
5
6
7
8
9
10
11
12
13
static message_type_support_callbacks_t MyMsg__callbacks = {
"my_pkg::msg", "MyMsg",
_MyMsg__cdr_serialize,
_MyMsg__cdr_deserialize,
_MyMsg__get_serialized_size,
_MyMsg__max_serialized_size
};

static rosidl_message_type_support_t MyMsg__handle = {
rosidl_typesupport_fastrtps_cpp::typesupport_identifier,
&MyMsg__callbacks,
get_message_typesupport_handle_function,
};
  • identifier"rosidl_typesupport_fastrtps_cpp"
  • funcrosidl_runtime_c 提供的链式查找;对 concrete handle,identifier 匹配时直接返回自身

导出符号(供分发层 dlsym):

1
2
ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(
rosidl_typesupport_fastrtps_cpp, my_pkg, msg, MyMsg)()

6.3 字段序列化规则(模板逻辑摘要)

IDL 类型 C++ 序列化方式
基本数值 cdr << ros_message.field
boolean cdr << (value ? true : false)
固定数组(基本类型) cdr << ros_message.array
固定数组(嵌套/msg) 循环 cdr_serialize(element)
unbounded/bounded sequence(基本类型) cdr << size + serializeArray 或逐元素
string cdr << ros_message.str
wstring u16string_to_wstringcdr << wstr
嵌套消息 调用依赖类型的 typesupport_fastrtps_cpp::cdr_serialize

反序列化对称实现;bounded sequence 检查 size > maximum_sizestd::runtime_error

6.4 max_serialized_size 与 plain 判定

模板递归计算 CDR alignment,累加 current_alignment

  • 基本类型按 sizeof + Cdr::alignment
  • 嵌套类型调用 max_serialized_size_DependentType
  • string 按 bounded 与否估算 upper bound
  • 最后用 offsetof 比较 in-memory 布局与 CDR 大小,确认 is_plain

7. C 生成模板差异(msg__type_support_c.cpp.em

C 侧操作 rosidl_generator_c 生成的 C struct

概念 C++ 生成 C 生成
字符串 std::string rosidl_runtime_c__String + string_functions.h
序列 std::vector rosidl_runtime_c__*__Sequence
嵌套类型 pkg::msg::Type pkg__msg__Type + __functions.h
callbacks 结构 message_type_support.h 同左(复用 C++ 包头)
identifier rosidl_typesupport_fastrtps_c__identifier

C handle 示例:

1
2
3
4
5
static rosidl_message_type_support_t MyMsg__type_support = {
rosidl_typesupport_fastrtps_c__identifier,
&__callbacks_MyMsg,
get_message_typesupport_handle_function,
};

依赖关系:C 包 <depend>rosidl_typesupport_fastrtps_cpp</depend>,因 callbacks 结构体定义在 C++ 包头中。


8. Service 与 Action

8.1 Service

srv__type_support.cpp.em

  1. 对 Request/Response 各调用一次 msg__type_support.cpp.em
  2. 组装 service_type_support_callbacks_t,指向两个 message 的 fastrtps symbol:
1
2
3
4
5
static service_type_support_callbacks_t MySrv__callbacks = {
"my_pkg::srv", "MySrv",
ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(..., MySrv_Request)(),
ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(..., MySrv_Response)(),
};

RMW 侧 RequestTypeSupport / ResponseTypeSupport 从 service callbacks 取出 request/response 的 message callbacks

8.2 Action

无独立 action 模板;Action 的 Goal/Result/Feedback/SendGoal/GetResult/FeedbackMessage 作为 普通 message/service 在 IDL 展开后走同一套 msg/srv 模板(与 rosidl 工具链 Action 衍生类型一致)。


9. 运行时辅助库

9.1 librosidl_typesupport_fastrtps_cpp.so

编译产物(非 per-package):

  • identifier.cpptypesupport_identifier = "rosidl_typesupport_fastrtps_cpp"
  • wstring_conversion.cpp — u16 ↔ wstring

wstring 转换(FastCDR 使用 std::wstring,ROS 使用 char16_t/u16string):

1
2
3
4
5
void u16string_to_wstring(const std::u16string & u16str, std::wstring & wstr) {
wstr.resize(u16str.size());
for (size_t i = 0; i < u16str.size(); ++i)
wstr[i] = static_cast<wchar_t>(u16str[i]);
}

rmw_fastrtps_dynamic_cpp 在动态类型路径中也复用这些转换函数。

9.2 C 包运行时

C 包主要提供 generator + rosidl_typesupport_fastrtps_c__identifier;wstring 实现在 C++ 包中,C 侧通过 rosidl_typesupport_fastrtps_c/wstring_conversion.hpp 包装调用。


10. 与 rmw_fastrtps 的衔接

10.1 TypeSupport 包装

rmw_fastrtps_cpp/src/type_support_common.cpp本仓库 callbacks 的直接消费者

1
2
3
4
5
6
7
8
9
10
11
bool TypeSupport::serializeROSmessage(
const void * ros_message, eprosima::fastcdr::Cdr & ser, const void * impl) const
{
ser.serialize_encapsulation();
if (has_data_) {
auto callbacks = static_cast<const message_type_support_callbacks_t *>(impl);
return callbacks->cdr_serialize(ros_message, ser);
}
ser << (uint8_t)0; // empty message dummy
return true;
}

创建 publisher 时(subscription.cpp 等):

1
2
auto callbacks = static_cast<const message_type_support_callbacks_t *>(type_support->data);
MessageTypeSupport ts(callbacks);

type_support 来自 RMW,已通过 rosidl_typesupport_cpp 分发层解析到 fastrtps concrete handle

10.2 rmw_fastrtps_cpp vs rmw_fastrtps_dynamic_cpp

RMW typesupport 使用
rmw_fastrtps_cpp 编译期类型,直接用生成的 callbacks
rmw_fastrtps_dynamic_cpp 运行时动态类型,仍依赖 fastrtps CDR 规则 + wstring 工具

两包均 build_depend 本仓库的 rosidl_typesupport_fastrtps_c/cpp


11. 编译开关与环境变量

变量 效果
FASTRTPS_STATIC_DISABLE=TRUE 跳过两个 typesupport 包构建(early return()
未安装 fastcdr rosidl_typesupport_fastrtps_cpp 扩展不注册,无法生成 fastrtps typesupport

Humble 通过 rosidl_default_generators 默认拉入本插件;缺失时 ros2 bag/DDS 通信会在运行时 dlopen 失败。


12. 安装布局示例

std_msgs 为例:

1
2
3
4
5
6
7
8
9
10
11
install/std_msgs/lib/
├── libstd_msgs__rosidl_typesupport_fastrtps_cpp.so
└── libstd_msgs__rosidl_typesupport_fastrtps_c.so

install/std_msgs/include/std_msgs/std_msgs/
├── msg/detail/dds_fastrtps/string__type_support.cpp # 仅 build 树
└── msg/detail/string__rosidl_typesupport_fastrtps_cpp.hpp

install/lib/
├── librosidl_typesupport_fastrtps_cpp.so # 公共辅助库
└── librosidl_typesupport_fastrtps_c.so # (若 C 包也建 runtime lib)

publish 热路径:per-package__rosidl_typesupport_fastrtps_cpp 库。


13. 与 rosidl_typesupport 分发层的关系

回顾分发层加载逻辑(见 rosidl_typesupport源码详细分析.md):

  1. rosidl_typesupport_cpp handle 的 map 含 "rosidl_typesupport_fastrtps_cpp"
  2. dlopen("libstd_msgs__rosidl_typesupport_fastrtps_cpp.so")
  3. dlsym("rosidl_typesupport_fastrtps_cpp__get_message_type_support_handle__std_msgs__msg__String")
  4. 返回 §6.2 中的 concrete handle(data → callbacks)

本仓库不负责 dispatch;只实现 map 中 被指向的目标库


14. 序列图:CDR 序列化

Fast-DDSfastcdr::Cdrmessage_type_support_callbacks_tMessageTypeSupportrmw_fastrtps_cpprclcpp NodeFast-DDSfastcdr::Cdrmessage_type_support_callbacks_tMessageTypeSupportrmw_fastrtps_cpprclcpp Nodetype_support 已通过 dispatch 解析为 fastrtps handlermw_publish(msg, type_support)serializeROSmessage(msg, ser, callbacks)serialize_encapsulation()cdr_serialize(msg, cdr)cdr << fields ...write(CDR buffer)

15. 调试与常见问题

现象 排查
Could not load library pkg__rosidl_typesupport_fastrtps_cpp 接口包未用 default generators 构建;或 fastcdr 缺失导致未生成
CDR deserialize 异常 两端 RMW/消息定义不一致;检查 .msg 变更后是否全量 rebuild
wstring 相关崩溃 wchar_t 宽度平台差异;查 wstring_conversion 测试
plain 类型未零拷贝 max_serialized_size 判定非 PLAIN(含 string/sequence)
仅 C 节点失败 __rosidl_typesupport_fastrtps_c 或未 link

实用命令

1
2
3
4
5
6
7
8
# 确认插件已注册
ls install/share/rosidl_typesupport_fastrtps_cpp/cmake

# 查看某消息 fastrtps 库
ls install/lib/lib*fastrtps*

# 查看生成 CDR 源(build 树)
ls build/std_msgs/rosidl_typesupport_fastrtps_cpp/std_msgs/msg/detail/dds_fastrtps/

16. 与 introspection typesupport 对比

维度 fastrtps(本仓库) introspection
主要消费者 rmw_fastrtps(DDS 线格式) rclpy、ros2 topic echo
data 内容 CDR 函数指针 字段 offset/类型表
依赖 fastcdr 无 fastcdr
性能 数据面热路径 反射/转换

同一消息 同时 生成两种插件库,由分发层按 identifier 选择。


17. 源码阅读顺序

  1. 契约message_type_support.hservice_type_support.h
  2. RMW 消费方rmw_fastrtps_cpp/src/type_support_common.cpp
  3. C++ 生成模板resource/msg__type_support.cpp.em(serialize 与 max_size 两段)
  4. C 生成模板resource/msg__type_support_c.cpp.em
  5. Serviceresource/srv__type_support.cpp.em
  6. CMake 生成rosidl_typesupport_fastrtps_cpp_generate_interfaces.cmake
  7. 扩展注册rosidl_typesupport_fastrtps_cpp-extras.cmake.in
  8. 分发层衔接rosidl_typesupport 仓库 type_support_dispatch.hpp
  9. 辅助wstring_conversion.cppFindFastRTPS.cmake

18. 小结

rosidl_typesupport_fastrtps 仓库实现 ROS 2 消息在 Fast-DDS RMW 下的 CDR 序列化 typesupport

  • rosidl_typesupport_fastrtps_cpp/c:Empy 生成 per-message 的 fastcdr 读写与 message_type_support_callbacks_t
  • fastrtps_cmake_module:CMake 查找 eProsima 依赖
  • rmw_fastrtps_cpp:读取 callbacks,封装 encapsulation,交给 Fast-DDS

理解 DDS 通信问题需串联 本仓库生成的 CDR 代码rmw_fastrtps 的 TypeSupport 包装;理解 typesupport 加载失败则需同时查看 rosidl_typesupport 分发层 的 dlopen 逻辑。

rosidl_typesupport 源码详细分析

rosidl_typesupport 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl_typesupport
版本:2.0.2(Humble),子包 2 个,许可证 Apache 2.0

rosidl_typesupport 仓库实现 ROS 2 typesupport 分发层(dispatch layer):为每个消息/服务/动作生成「中间 handle」,在运行时按 RMW 所需的 具体 typesupport identifier(如 rosidl_typesupport_fastrtps_cpp)动态加载对应共享库并返回真正的序列化实现。本仓库 不实现 DDS 线格式序列化——那是 rosidl_typesupport_fastrtps_*rosidl_typesupport_introspection_* 等插件的职责。

范围说明:本仓库仅含 rosidl_typesupport_crosidl_typesupport_cpp。具体 RMW/DDS typesupport 在 rosidl_typesupport_fastrtps 仓库;introspection 在 rosidl 仓库的 rosidl_typesupport_introspection_* 包。


1. 总体认识

1.1 为什么需要 typesupport 分发层?

ROS 2 允许同一消息类型存在 多种 typesupport 实现

Typesupport 插件 identifier 示例 用途
rosidl_typesupport_fastrtps_cpp 同上 Fast-DDS CDR 序列化(RMW 数据面)
rosidl_typesupport_introspection_cpp 同上 字段反射(Python、ros2 topic echo
rosidl_typesupport_fastrtps_c 同上 C 侧 Fast-DDS
rosidl_typesupport_introspection_c 同上 C 侧 introspection

用户代码和 RCL 通常持有 分发层 handle(identifier = rosidl_typesupport_cpp),在 publish/take 时 RMW 传入 自身需要的 identifier,分发层通过 dlopen 加载 lib{pkg}__{identifier}.sodlsym 取得具体实现。

1.2 核心职责

能力 rosidl_typesupport_c rosidl_typesupport_cpp
运行时 dispatch 库 librosidl_typesupport_c.so librosidl_typesupport_cpp.so
动态加载逻辑 type_support_dispatch.hpp 同左(C++ 版,逻辑一致)
type_support_map type_support_map.h 复用 C 版头文件
代码生成 每消息 *_type_support.cpp 每消息 *_type_support.cpp + C++ 模板特化
CMake 扩展 rosidl_typesupport_c_generate_interfaces.cmake rosidl_typesupport_cpp_generate_interfaces.cmake
ament index 消费 rosidl_typesupport_c 资源 消费 rosidl_typesupport_cpp 资源

1.3 在 ROS 2 栈中的位置

编译期 - rosidl_generate_interfaces每接口包 install/lib运行时"dlopen libmy_msgs__rosidl_typesupport_fastrtps_cpp".idlrosidl_generator_c/cpprosidl_typesupport_c 生成器rosidl_typesupport_cpp 生成器rosidl_typesupport_fastrtps_* 生成器rosidl_typesupport_introspection_* 生成器libmy_msgs__rosidl_typesupport_cpp.so\n(dispatch + map)libmy_msgs__rosidl_typesupport_fastrtps_cpp.so\n(CDR 序列化)libmy_msgs__rosidl_typesupport_introspection_cpp.so\n(反射)rclcpp::Publisher\nget_message_type_support_handle<T>()rosidl_typesupport_cpp\nget_message_typesupport_handle_functionrmw / Fast-DDS\nidentifier=fastrtps_cpp
对比项 分发层 (rosidl_typesupport_*) 具体插件 (fastrtps / introspection)
库名 固定 librosidl_typesupport_cpp.so lib{pkg}__rosidl_typesupport_fastrtps_cpp.so
每消息生成 是(map + 入口符号) 是(序列化/反射实现)
identifier rosidl_typesupport_cpp rosidl_typesupport_fastrtps_cpp
动态加载 发起方(加载其他库) 被加载方

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
rosidl_typesupport/
├── README.md
├── LICENSE
├── rosidl_typesupport_c/ # ★ C 分发层
│ ├── include/rosidl_typesupport_c/
│ │ ├── type_support_map.h # 核心 map 结构
│ │ ├── message_type_support_dispatch.h
│ │ ├── service_type_support_dispatch.h
│ │ └── identifier.h
│ ├── src/
│ │ ├── identifier.c
│ │ ├── message_type_support_dispatch.cpp
│ │ ├── service_type_support_dispatch.cpp
│ │ └── type_support_dispatch.hpp # dlopen 逻辑
│ ├── cmake/
│ │ ├── get_used_typesupports.cmake # 发现可用插件
│ │ └── rosidl_typesupport_c_generate_interfaces.cmake
│ ├── resource/*.em # 生成模板
│ └── rosidl_typesupport_c/__init__.py
└── rosidl_typesupport_cpp/ # ★ C++ 分发层
├── include/rosidl_typesupport_cpp/
│ ├── message_type_support_dispatch.hpp
│ ├── service_type_support_dispatch.hpp
│ └── identifier.hpp
├── src/ # 结构与 C 包对称
├── cmake/rosidl_typesupport_cpp_generate_interfaces.cmake
└── resource/*.em

两包结构 高度对称:C 包提供 type_support_map_t 定义;C++ 包依赖 C 包并复用同一 map 结构。


3. 核心数据结构

3.1 rosidl_message_type_support_t

定义于 rosidl_runtime_c/message_type_support_struct.hrosidl 仓库):

1
2
3
4
5
struct rosidl_message_type_support_t {
const char * typesupport_identifier;
const void * data;
rosidl_message_typesupport_handle_function func;
};
  • identifier:本 handle 所属 typesupport 层
  • data:具体 typesupport 私有数据,或指向 type_support_map_t
  • func:按 identifier 查找/加载其他 typesupport 的回调

3.2 type_support_map_t

定义于 rosidl_typesupport_c/type_support_map.h

1
2
3
4
5
6
7
typedef struct type_support_map_t {
const size_t size;
const char * package_name;
const char * const * typesupport_identifier; // 插件名数组
const char * const * symbol_name; // dlsym 符号名数组
void ** data; // 缓存已加载的 SharedLibrary*
} type_support_map_t;

运行时填充data[i] 初始为 NULL;首次请求 identifier typesupport_identifier[i] 时,dlopen 加载库并缓存指针。

3.3 分发层 identifier

全局符号 字符串值
C rosidl_typesupport_c__typesupport_identifier "rosidl_typesupport_c"
C++ rosidl_typesupport_cpp::typesupport_identifier "rosidl_typesupport_cpp"

4. 运行时 dispatch 算法

实现于 src/type_support_dispatch.hpp(C/C++ 两包逻辑相同)。

4.1 get_typesupport_handle_function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template<typename TypeSupport>
const TypeSupport * get_typesupport_handle_function(
const TypeSupport * handle, const char * identifier)
{
// 1. 精确匹配:请求的 identifier 就是本 handle 的 identifier
if (strcmp(handle->typesupport_identifier, identifier) == 0) {
return handle;
}

// 2. 本 handle 是分发层(rosidl_typesupport_cpp/c)
if (handle->typesupport_identifier == rosidl_typesupport_cpp::typesupport_identifier) {
const type_support_map_t * map = static_cast<const type_support_map_t *>(handle->data);
for (size_t i = 0; i < map->size; ++i) {
if (strcmp(map->typesupport_identifier[i], identifier) != 0) continue;

// 3. 懒加载共享库
if (!map->data[i]) {
// library_basename = "{package_name}__{identifier}"
// 例: std_msgs__rosidl_typesupport_fastrtps_cpp
lib = new rcpputils::SharedLibrary(
rcpputils::get_platform_library_name(library_basename));
map->data[i] = lib;
}

// 4. dlsym 取得具体 typesupport 入口函数
sym = lib->get_symbol(map->symbol_name[i]);
func = reinterpret_cast<const TypeSupport *(*)(void)>(sym);
return func(); // 返回 concrete handle
}
}
return nullptr;
}

库命名规则{package_name}__{typesupport_identifier}
例如 std_msgs__rosidl_typesupport_fastrtps_cpplibstd_msgs__rosidl_typesupport_fastrtps_cpp.so

符号命名规则rosidl_typesupport_interface/macros.h):

1
{typesupport_name}__get_message_type_support_handle__{package}__{subfolder}__{MessageName}

例:rosidl_typesupport_fastrtps_cpp__get_message_type_support_handle__std_msgs__msg__String

4.2 序列图

libpkg__fastrtps_cpp.sotype_support_maprosidl_typesupport_cpprmw_fastrtpslibpkg__fastrtps_cpp.sotype_support_maprosidl_typesupport_cpprmw_fastrtpsalt[首次加载]使用 concrete handle 做 CDR serializefunc(handle, "rosidl_typesupport_fastrtps_cpp")查找 map 中 matching identifierdlopen("libpkg__rosidl_typesupport_fastrtps_cpp.so")dlsym(get_message_type_support_handle__...)concrete rosidl_message_type_support_t*

5. 编译期代码生成

5.1 CMake 扩展注册

rosidl_typesupport_cpp-extras.cmake.in

1
2
3
4
5
get_used_typesupports(_typesupports "rosidl_typesupport_cpp")
ament_register_extension(
"rosidl_generate_idl_interfaces"
"rosidl_typesupport_cpp"
"rosidl_typesupport_cpp_generate_interfaces.cmake")

rosidl_generate_interfaces() 执行 ament_execute_extensions("rosidl_generate_idl_interfaces") 时被调用。

前置依赖:必须先有 __rosidl_generator_cpp target(C 包则要求 __rosidl_generator_c)。

5.2 get_used_typesupports() — 发现可用插件

rosidl_typesupport_c/cmake/get_used_typesupports.cmake

  1. ament_index_get_resources(available_typesupports "rosidl_typesupport_cpp")
    读取所有向 ament index 注册了 rosidl_typesupport_cpp 资源的包(如 fastrtps、introspection)
  2. 可选过滤:CMake 变量或环境变量 STATIC_ROSIDL_TYPESUPPORT_CPP(C 侧为 STATIC_ROSIDL_TYPESUPPORT_C),分号分隔插件名
  3. 输出 typesupports 列表传给 generator

构建日志示例:

1
Using all available rosidl_typesupport_cpp: rosidl_typesupport_fastrtps_cpp;rosidl_typesupport_introspection_cpp

仅一个插件时标记为 single,生成器走 静态直连 优化路径(见 §5.4)。

5.3 生成命令

1
2
3
4
5
6
7
8
add_custom_command(
OUTPUT ${_generated_sources}
COMMAND Python3::Interpreter
ARGS ${rosidl_typesupport_cpp_BIN}
--generator-arguments-file "${generator_arguments_file}"
--typesupports ${typesupports}
...
)

Python 入口 rosidl_typesupport_cpp/__init__.py

1
2
3
def generate_cpp(generator_arguments_file, type_supports):
mapping = {'idl__type_support.cpp.em': '%s__type_support.cpp'}
return generate_files(..., additional_context={'type_supports': type_supports})

每个 IDL 文件生成一个 {msg_name}__type_support.cpp,内含该文件中所有 message/service/action 的分发代码。

5.4 两种生成模式

模式 A:多 typesupport(默认,动态 dispatch)

Empy 模板 msg__type_support.cpp.em 生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
static const type_support_map_t String_message_typesupport_map = {
2, // size
"std_msgs",
&String_message_typesupport_ids.typesupport_identifier[0],
&String_message_typesupport_symbol_names.symbol_name[0],
&String_message_typesupport_data.data[0],
};

static const rosidl_message_type_support_t String_message_type_support_handle = {
::rosidl_typesupport_cpp::typesupport_identifier,
&String_message_typesupport_map,
::rosidl_typesupport_cpp::get_message_typesupport_handle_function,
};

map 中 typesupport_identifier 数组在编译期填入,例如:

1
2
"rosidl_typesupport_fastrtps_cpp",
"rosidl_typesupport_introspection_cpp",

模式 B:单一 typesupport(静态优化)

get_used_typesupports 只返回一个插件时,生成器 跳过 map,直接转发:

1
2
3
4
5
6
template<>
const rosidl_message_type_support_t *
get_message_type_support_handle<std_msgs::msg::String>() {
return ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME(
rosidl_typesupport_fastrtps_cpp, std_msgs, msg, String)();
}

CMake 同时 静态链接 该 typesupport target:

1
2
3
if(NOT typesupports MATCHES ";")
target_link_libraries(... PRIVATE ${target}__${typesupports})
endif()

限制:多 typesupport + BUILD_SHARED_LIBS=OFF 会 fatal error(无法静态链接多个插件)。

5.5 生成的 CMake Target

Target 产物
{pkg}__rosidl_typesupport_c lib{pkg}__rosidl_typesupport_c.so
{pkg}__rosidl_typesupport_cpp lib{pkg}__rosidl_typesupport_cpp.so

{pkg}__rosidl_typesupport_fastrtps_cpp并列安装lib/


6. C 与 C++ 分发层差异

维度 rosidl_typesupport_c rosidl_typesupport_cpp
dispatch 函数 rosidl_typesupport_c__get_message_typesupport_handle_function rosidl_typesupport_cpp::get_message_typesupport_handle_function
C++ 入口 extern "C" 符号 额外提供 template<> get_message_type_support_handle<T>()
依赖 rosidl_runtime_c rosidl_runtime_c + rosidl_runtime_cpp + 依赖 C 包
使用者 RMW C API、Python C 扩展 rclcpp 模板 publish、rosidl_typesupport_cpp::get_message_type_support_handle<T>()
生成源文件扩展名 .cpp(内含 extern "C" .cpp

C++ 侧模板声明位于 rosidl_runtime_cpp/include/rosidl_typesupport_cpp/message_type_support.hpp

1
2
template<typename T>
const rosidl_message_type_support_t * get_message_type_support_handle();

每个消息在生成的 __type_support.cpp显式特化


7. 与 rosidl 工具链的衔接

7.1 在 rosidl_generate_interfaces 中的顺序

1
2
3
4
5
6
7
rosidl_generator_c
→ rosidl_generator_cpp
→ rosidl_typesupport_c
→ rosidl_typesupport_cpp
→ rosidl_typesupport_introspection_c/cpp
→ rosidl_typesupport_fastrtps_c/cpp
→ rosidl_generator_py

introspection / fastrtps 的 CMake 同样注册 rosidl_generate_idl_interfaces,且通常依赖 generator 已生成的 struct 头文件。

7.2 ament index 注册链

具体 typesupport 插件在 自身 CMakeLists 中注册 index 资源:

1
2
# rosidl_typesupport_fastrtps_cpp/CMakeLists.txt
ament_index_register_resource("rosidl_typesupport_cpp")

get_used_typesupports() 据此发现插件。若某插件未安装,不会进入 map,对应 RMW 在运行时会加载失败。

7.3 rosidl_typesupport_c_packages

package.xml 中:

1
<group_depend>rosidl_typesupport_cpp_packages</group_depend>

Bloom 打包时确保二进制发行版包含常见 RMW typesupport 依赖;源码构建时由 workspace 中已 clone 的 fastrtps 等包满足。


8. 运行时调用路径示例

8.1 rclcpp publish

1
2
3
4
5
6
7
8
9
10
rclcpp::Publisher<std_msgs::msg::String>::publish(msg)
→ rosidl_typesupport_cpp::get_message_type_support_handle<std_msgs::msg::String>()
返回 pkg 生成的 dispatch handle(identifier=rosidl_typesupport_cpp)
→ rcl_publish(..., ros_message, type_support)
→ rmw 内部调用 type_support->func(handle, rmw_typesupport_identifier)
rmw_typesupport_identifier = "rosidl_typesupport_fastrtps_cpp"
→ get_message_typesupport_handle_function(...)
→ dlopen libstd_msgs__rosidl_typesupport_fastrtps_cpp.so
→ 返回 fastrtps concrete handle
→ Fast-DDS CDR 序列化 → 网络发送

8.2 Python / introspection

Python 绑定通常请求 rosidl_typesupport_introspection_c identifier,同一 dispatch 机制加载 libpkg__rosidl_typesupport_introspection_c.so,取得字段 layout 用于 C ↔ Python 转换。


9. 测试与质量

两包均为 Quality Level 1,含:

测试 内容
test_message_type_support_dispatch.cpp map 查找、dlopen、dlsym、错误路径
test_service_type_support_dispatch.cpp service handle 分发
benchmark_type_support_dispatch.cpp dispatch 性能基准
test_cli_extension.py generator CLI 参数

测试构造 mock type_support_map_t,用 rosidl_typesupport_cpp__test_type_support1/2 等假库验证加载逻辑;故意放置非共享库文件验证错误处理。


10. 配置与调优

10.1 限制编译期 typesupport 集合

减少 map 大小、缩短构建时间、缩小 install 体积:

1
2
3
# 仅保留 fastrtps(示例)
export STATIC_ROSIDL_TYPESUPPORT_CPP=rosidl_typesupport_fastrtps_cpp
colcon build --packages-select my_msgs

CMake 中等价:set(STATIC_ROSIDL_TYPESUPPORT_CPP "rosidl_typesupport_fastrtps_cpp")

10.2 静态链接注意

  • 单一 typesupport:可静态链接,无 dlopen 开销
  • 多 typesupport + 静态库模式:不支持(CMake fatal error)
  • 默认 BUILD_SHARED_LIBS=ON 使用动态 dispatch

10.3 常见问题

现象 原因 排查
Could not load library pkg__rosidl_typesupport_fastrtps_cpp 未构建/未安装 fastrtps typesupport ls install/lib/lib*fastrtps*
Failed to find symbol ... 插件与消息包版本不匹配 全部 rebuild
Python 导入消息失败 缺 introspection 库 安装 rosidl_typesupport_introspection_c
自定义 RMW 找不到 typesupport 新插件未注册 ament index 插件 CMake 加 ament_index_register_resource

11. 与关联仓库对比

仓库/包 角色
本仓库 rosidl_typesupport_c/cpp 分发层 + 代码生成
rosidl_typesupport_interface(rosidl 仓库) 符号命名宏
rosidl_typesupport_introspection_*(rosidl 仓库) 反射 typesupport 插件
rosidl_typesupport_fastrtps_* Fast-DDS CDR 插件
rosidl_runtime_c rosidl_message_type_support_t 结构定义
rosidl_generator_c/cpp 消息 struct 生成(非 typesupport)

记忆口诀:generator 造 数据 struct;typesupport 插件造 序列化/反射rosidl_typesupport_cpp路由表 把 RMW 请求转到正确插件。


12. 端到端库依赖图(单接口包)

std_msgs 为例,install/lib/ 中典型产物:

1
2
3
4
5
6
7
8
libstd_msgs__rosidl_generator_c.so          # C struct + functions
libstd_msgs__rosidl_generator_cpp.so # 头文件(常为 INTERFACE)
libstd_msgs__rosidl_typesupport_c.so # ★ C dispatch
libstd_msgs__rosidl_typesupport_cpp.so # ★ C++ dispatch
libstd_msgs__rosidl_typesupport_fastrtps_c.so
libstd_msgs__rosidl_typesupport_fastrtps_cpp.so
libstd_msgs__rosidl_typesupport_introspection_c.so
libstd_msgs__rosidl_typesupport_introspection_cpp.so

运行时 rclcpp 主要链接/加载 __rosidl_typesupport_cpp;RMW 再经 dispatch 加载 __rosidl_typesupport_fastrtps_cpp


13. 源码阅读顺序

  1. 数据结构rosidl_typesupport_c/type_support_map.hrosidl_runtime_c/message_type_support_struct.h
  2. dispatch 核心rosidl_typesupport_cpp/src/type_support_dispatch.hpp
  3. 对外 APImessage_type_support_dispatch.hpp / identifier.hpp
  4. 生成模板resource/msg__type_support.cpp.em(多/单 typesupport 分支)
  5. CMakeget_used_typesupports.cmakerosidl_typesupport_cpp_generate_interfaces.cmake
  6. 扩展注册rosidl_typesupport_cpp-extras.cmake.in
  7. 测试test/test_message_type_support_dispatch.cpp
  8. 下游rclcppget_message_type_support_handle<T>() 调用链;rosidl_typesupport_fastrtps_cpp 中 concrete 实现

14. 小结

rosidl_typesupport 仓库的 2 个包是 ROS 2 类型系统中连接「接口包」与「多种 RMW/语言 typesupport 插件」的 枢纽

  • 编译期:扫描 ament index 中已注册插件,为每条消息生成 type_support_map 或静态直连代码,产出 lib{pkg}__rosidl_typesupport_{c,cpp}.so
  • 运行期:通过 get_message_typesupport_handle_function 按 identifier dlopen lib{pkg}__{plugin}.so,实现 RMW、Python、工具链对同一消息类型的 插件化 访问

理解 publish 失败或 typesupport 符号缺失时,应同时检查 dispatch 库具体插件库 是否都已正确生成并存在于 LD_LIBRARY_PATH / install lib/ 中。

rosidl 源码详细分析

rosidl 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rosidl
版本:3.1.8(Humble),子包 12 个,许可证 Apache 2.0

rosidl 是 ROS 2 接口定义语言(IDL)工具链的核心仓库:将 .msg / .srv / .action(或原生 .idl)解析为 AST,经 Empy 模板 生成 C/C++ 结构体与函数,并注册 typesupport 扩展点;运行时通过 rosidl_message_type_support_t 与 RMW/DDS 衔接。

范围说明:本仓库含 编译期 核心(adapter / parser / generator / runtime / introspection typesupport)。Python 生成器rosidl_generator_py)、通用 typesupport 分发rosidl_typesupport_cpp)、Fast-DDS typesupportrosidl_typesupport_fastrtps_*)、默认生成器聚合rosidl_default_generators)位于 独立仓库,见 §12。


1. 总体认识

1.1 核心职责

能力 实现位置 说明
Legacy 格式适配 rosidl_adapter .msg/.srv/.action.idl
IDL 解析 rosidl_parser Lark 语法 → definition.py AST
CMake 编排 rosidl_cmake rosidl_generate_interfaces()
C 代码生成 rosidl_generator_c struct + init/fini + type_support 声明
C++ 代码生成 rosidl_generator_cpp struct + builder + traits
C 运行时 rosidl_runtime_c String、序列、message_type_support_t
C++ 运行时 rosidl_runtime_cpp traits、bounded_vector、type_support 声明
Typesupport 接口 rosidl_typesupport_interface 符号命名宏
Introspection TS rosidl_typesupport_introspection_* 字段反射(Python/rqt/ros2 topic echo)
CLI 工具 rosidl_cli rosidl generate / translate
集成测试 rosidl_typesupport_introspection_tests introspection 正确性验证

1.2 在 ROS 2 栈中的位置

接口作者rosidl 仓库关联仓库 - 非本目录ROS 2 运行时foo.msg / bar.srv / Baz.actionrosidl_adapter\n.msg → .idlrosidl_parser\nLark ASTrosidl_cmake\nrosidl_generate_interfacesrosidl_generator_crosidl_generator_cpprosidl_runtime_crosidl_runtime_cpprosidl_typesupport_introspection_*rosidl_generator_pyrosidl_typesupport_cpprosidl_typesupport_fastrtps_*rosidl_default_generatorsrcl / rclcpprmw → DDS 序列化
阶段 输入 输出
适配 Chatter.msg Chatter.idl(OMG IDL 模块语法)
解析 .idl 文本 IdlFile / Message / Service / Action AST
生成 AST + Empy 模板 *.h / *.hpp / __functions.c / typesupport 源
链接 多个 __rosidl_* 可链接的 interface package
运行 rosidl_message_type_support_t RMW publish/take、Python 绑定、introspection

1.3 端到端流水线

1
2
3
4
5
6
7
8
9
10
11
package.xml + CMakeLists.txt
└── rosidl_generate_interfaces(${PROJECT_NAME} "msg/Foo.msg" DEPENDENCIES std_msgs)
├── [非 .idl] rosidl_adapter → 生成 build/.../msg/Foo.idl
├── rosidl_parser → AST(构建时由 generator 调用)
└── ament_execute_extensions("rosidl_generate_idl_interfaces")
├── rosidl_generator_c → lib...__rosidl_generator_c.so
├── rosidl_generator_cpp → 头文件库 __rosidl_generator_cpp
├── rosidl_typesupport_introspection_c/cpp
├── [外部] rosidl_generator_py
├── [外部] rosidl_typesupport_cpp
└── [外部] rosidl_typesupport_fastrtps_c/cpp

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
rosidl/
├── rosidl_adapter/ # ★ .msg/.srv/.action → .idl (Python)
│ ├── rosidl_adapter/
│ │ ├── parser.py # Legacy 格式解析(与 parser 包不同)
│ │ ├── msg/ srv/ action/ # convert_*_to_idl
│ │ └── resource/*.idl.em # Empy 模板
│ └── cmake/rosidl_adapt_interfaces.cmake
├── rosidl_parser/ # ★ IDL 语法解析 (Lark)
│ ├── rosidl_parser/
│ │ ├── grammar.lark
│ │ ├── parser.py
│ │ └── definition.py # AST 类型定义 (~765 行)
│ └── bin/idl2png
├── rosidl_cmake/ # ★ CMake 宏与 generate_files 工具
│ ├── cmake/rosidl_generate_interfaces.cmake
│ ├── cmake/rosidl_get_typesupport_target.cmake
│ └── rosidl_cmake/__init__.py # Empy 驱动 generate_files()
├── rosidl_generator_c/ # C struct + functions
│ ├── resource/*.em
│ └── rosidl_generator_c/__init__.py
├── rosidl_generator_cpp/ # C++ struct + builder + traits
│ ├── resource/*.em
│ └── rosidl_generator_cpp/__init__.py
├── rosidl_runtime_c/ # C 运行时类型与 type_support 结构
├── rosidl_runtime_cpp/ # C++ traits、bounded_vector
├── rosidl_typesupport_interface/# typesupport 符号宏
├── rosidl_typesupport_introspection_c/
├── rosidl_typesupport_introspection_cpp/
├── rosidl_typesupport_introspection_tests/
└── rosidl_cli/ # rosidl 命令行

2.1 子包一览

包名 语言 职责
rosidl_adapter Python Legacy 接口 → IDL
rosidl_parser Python IDL → AST
rosidl_cmake CMake+Python 构建编排、generate_files
rosidl_generator_c Python+Empy 生成 C 代码
rosidl_generator_cpp Python+Empy 生成 C++ 代码
rosidl_runtime_c C 字符串、序列、type_support 结构体
rosidl_runtime_cpp C++ traits、初始化策略
rosidl_typesupport_interface C 头文件 符号命名约定
rosidl_typesupport_introspection_c Python+Empy C introspection typesupport
rosidl_typesupport_introspection_cpp Python+Empy C++ introspection typesupport
rosidl_typesupport_introspection_tests C++ introspection 测试框架
rosidl_cli Python 独立 CLI(非 colcon 主路径)

3. rosidl_adapter:Legacy 格式 → IDL

3.1 入口

CMake 调用 rosidl_adapt_interfaces(),最终执行:

1
2
# rosidl_adapter/main.py
convert_to_idl(basepath, package_name, relative_path, output_dir)

按后缀分发:

后缀 函数 输出目录
.msg convert_msg_to_idl output_dir/msg/
.srv convert_srv_to_idl output_dir/srv/
.action convert_action_to_idl output_dir/action/

3.2 Legacy 解析器(rosidl_adapter/parser.py

rosidl_parser 独立:专门解析 ROS 1 风格 .msg 文本。

支持的 primitive 类型

1
2
3
bool, byte, char, float32, float64,
int8/uint8, int16/uint16, int32/uint32, int64/uint64,
string, wstring, duration, time

语法元素

  • 注释:#
  • 常量:NAME=123
  • 数组:type[N] 固定长度,type[] / type[<=N] 动态/有界序列
  • Service:以 --- 分隔 request/response
  • Action:三段 --- 分隔 goal/result/feedback

命名校验:package/field 名小写+下划线;message 名 PascalCase(兼容 ROS 1 的宽松模式可选)。

3.3 类型映射(MSG → IDL)

1
2
3
4
5
6
7
8
9
# rosidl_adapter/msg/__init__.py
MSG_TYPE_TO_IDL = {
'bool': 'boolean',
'byte': 'octet',
'char': 'uint8',
'float32': 'float',
'float64': 'double',
# ...
}

3.4 IDL 模板(Empy)

msg.idl.em 将解析结果渲染为 OMG IDL 模块:

1
2
3
4
5
6
7
8
9
10
11
module test_msgs {
module msg {
struct Test {
boolean bool_value;
octet byte_value;
uint8 char_value;
float float32_value;
// ...
};
};
};

注释通过 @verbatim 注解保留。输出编码为 iso-8859-1(兼容非 ASCII 注释)。

Service / Action 模板(srv.idl.emaction.idl.em)生成对应 module 结构;Action 在 adapter 阶段仅转换用户定义的三段,衍生类型(SendGoal/GetResult 等)由 rosidl_parserAction 类在解析 IDL 后构建。


4. rosidl_parser:IDL → AST

4.1 解析器实现

  • 语法引擎:Lark(grammar.lark
  • 入口parse_idl_file(IdlLocator)IdlFile(locator, content)
  • 调试idl2png 可将语法树导出为 PNG
1
2
3
4
# parser.py
_parser = Lark(grammar, start='specification', maybe_placeholders=False)
tree = _parser.parse(idl_string)
content = extract_content_from_ast(tree)

4.2 AST 类型体系(definition.py

类型 说明
BasicType IDL 基本类型(int32booleanoctet…)
NamespacedType pkg::msg::Message 命名空间类型
Array / BoundedSequence / UnboundedSequence 数组与序列
BoundedString / UnboundedString 字符串(含 wstring)
Member 结构体字段
Constant 模块级常量
Structure struct 定义
Message 包装 Structure
Service request/response 两个 Message
Action goal/result/feedback + 衍生 service/message
Include #include 依赖
Annotation @verbatim
IdlContent 单文件全部内容
IdlLocator (basepath, relative_path) 定位器

命名后缀常量(全栈统一):

1
2
3
4
5
6
7
8
SERVICE_REQUEST_MESSAGE_SUFFIX = '_Request'
SERVICE_RESPONSE_MESSAGE_SUFFIX = '_Response'
ACTION_GOAL_SUFFIX = '_Goal'
ACTION_RESULT_SUFFIX = '_Result'
ACTION_FEEDBACK_SUFFIX = '_Feedback'
ACTION_GOAL_SERVICE_SUFFIX = '_SendGoal'
ACTION_RESULT_SERVICE_SUFFIX = '_GetResult'
ACTION_FEEDBACK_MESSAGE_SUFFIX = '_FeedbackMessage'

4.3 Action 衍生类型

用户 .action 文件只定义 goal/result/feedback 三段;Action 类自动构造:

衍生类型 成员
{Action}_SendGoal 服务 Request: goal_id (UUID) + goal;Response: accepted + stamp
{Action}_GetResult 服务 Request: goal_id;Response: status + result
{Action}_FeedbackMessage goal_id + feedback

隐式依赖:builtin_interfaces/msg/Time.idlunique_identifier_msgs/msg/UUID.idl

因此 action 包必须在 package.xml 中依赖 action_msgs(CMake 会检查)。


5. rosidl_cmake:构建编排

5.1 rosidl_generate_interfaces

核心步骤rosidl_generate_interfaces.cmake):

  1. 校验接口文件存在;支持 basepath:relative_path 元组
  2. 分离 .idl 与 legacy 文件
  3. 对 legacy 文件调用 rosidl_adapt_interfaces 生成 .idl
  4. .srv 额外在 build 目录生成 _Request.msg / _Response.msg(供 linter/索引)
  5. 收集 DEPENDENCIES 中的 IDL 文件
  6. 注册 ament index rosidl_interfaces
  7. ament_execute_extensions("rosidl_generate_idl_interfaces") — 触发所有 generator/typesupport
  8. 安装 .idl / .msgshare/${PROJECT_NAME}/

package.xml 要求:安装接口的包须声明:

1
<member_of_group>rosidl_interface_packages</member_of_group>

5.2 扩展点机制

各 generator 通过 ament_register_extension 注册:

1
2
3
4
5
# rosidl_generator_c/cmake/register_c.cmake
ament_register_extension(
"rosidl_generate_idl_interfaces"
"rosidl_generator_c"
"rosidl_generator_c_generate_interfaces.cmake")

拓扑顺序:后注册的 extension 可声明对先生成 target 的依赖。例如 introspection 要求:

1
2
3
if(NOT TARGET ${target}__rosidl_generator_cpp)
message(FATAL_ERROR "rosidl_generator_cpp must be executed before introspection...")
endif()

典型顺序:generator_c → generator_cpp → typesupport_*(introspection、fastrtps、cpp、py)

5.3 generate_files() — 共享生成框架

rosidl_cmake/__init__.py 中:

  1. 读取 JSON generator arguments(idl_tuplestemplate_diroutput_dir
  2. 对每个 IDL:parse_idl_file(locator) 得 AST
  3. Empy 渲染模板映射表
  4. 增量构建:比较 template/idl 与已有输出 mtime

C 与 C++ generator 仅 mapping 表 不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# rosidl_generator_c
mapping = {
'idl.h.em': '%s.h',
'idl__struct.h.em': 'detail/%s__struct.h',
'idl__functions.c.em': 'detail/%s__functions.c',
...
}

# rosidl_generator_cpp
mapping = {
'idl.hpp.em': '%s.hpp',
'idl__struct.hpp.em': 'detail/%s__struct.hpp',
'idl__builder.hpp.em': 'detail/%s__builder.hpp',
'idl__traits.hpp.em': 'detail/%s__traits.hpp',
...
}

5.4 rosidl_get_typesupport_target

供同包内其他 target 依赖生成的 typesupport 库:

1
2
3
rosidl_get_typesupport_target(cpp_typesupport_target
"${PROJECT_NAME}__rosidl_typesupport_cpp" "rosidl_typesupport_cpp")
target_link_libraries(my_node ${cpp_typesupport_target})

6. rosidl_generator_c / rosidl_generator_cpp

6.1 生成产物(以 std_msgs/msg/String 为例)

C 侧rosidl_generator_c/):

文件 内容
string.h 对外 include
detail/string__struct.h C struct 定义
detail/string__functions.h/.c init/fini/copy/resize
detail/string__type_support.h type_support 声明

C++ 侧rosidl_generator_cpp/):

文件 内容
string.hpp 对外 include
detail/string__struct.hpp String_<ContainerAllocator> 模板 struct
detail/string__builder.hpp fluent builder API
detail/string__traits.hpp data_type()name()has_fixed_size 等特化
detail/string__type_support.hpp C++ type_support 声明

6.2 类型映射

IDL → CBASIC_IDL_TYPES_TO_C):

  • booleanbool
  • octetuint8_t
  • charsigned char
  • wcharuint16_t

IDL → C++MSG_TYPE_TO_CPP):

  • 基本类型 → uint32_t
  • stringstd::basic_string<char, ...>(allocator 模板参数)
  • 嵌套类型 → pkg::msg::Type_<ContainerAllocator>
  • 固定数组 → std::array<T, N>
  • 动态序列 → std::vector<T, Allocator>

6.3 生成的 CMake Target

每个 interface package 产生多个库 target,后缀模式:

Target 后缀 内容
__rosidl_generator_c C struct + functions 源文件
__rosidl_generator_cpp 仅头文件(INTERFACE 库)
__rosidl_typesupport_introspection_c introspection C 实现
__rosidl_typesupport_introspection_cpp introspection C++ 实现
[外部] __rosidl_typesupport_cpp C++ typesupport 分发
[外部] __rosidl_typesupport_fastrtps_* Fast-DDS 序列化

7. rosidl_runtime_c / rosidl_runtime_cpp

7.1 C 运行时

字符串string.h):

1
2
3
4
5
typedef struct rosidl_runtime_c__String {
char * data;
size_t size; // 不含 '\0'
size_t capacity;
} rosidl_runtime_c__String;

宽字符串u16string.h
动态数组primitives_sequence.h + *_functions.h
有界序列sequence_bound.hstring_bound.h

Type support 结构message_type_support_struct.h):

1
2
3
4
5
struct rosidl_message_type_support_t {
const char * typesupport_identifier;
const void * data;
rosidl_message_typesupport_handle_function func;
};
  • typesupport_identifier:如 "rosidl_typesupport_introspection_cpp"
  • func:按 identifier 链式查找其他 typesupport 的 handle
  • ROSIDL_GET_MSG_TYPE_SUPPORT(Pkg, msg, MsgName):获取默认 C typesupport 符号

类似结构存在于 service_type_support_struct.haction_type_support_struct.h

消息初始化message_initialization.h):ROSIDL_RUNTIME_C_MSG_INIT_ALL / ZERO / DEFAULTS_ONLY 等策略。

7.2 C++ 运行时

traitstraits.hpp)提供通用模板与工具函数:

  • rosidl_generator_traits::value_to_yaml() — 各类型 YAML 序列化
  • has_fixed_size<T> / has_bounded_size<T> — 类型属性 trait
  • is_message<T> / is_service_request<T> / is_action_goal<T> — 类型分类(由 generator 特化)

bounded_vector.hpp:有界动态数组的 C++ 容器实现。

message_initialization.hpp:C++ 侧对应初始化枚举。


8. Typesupport 体系

8.1 接口层(rosidl_typesupport_interface

统一 符号命名,避免各 typesupport 插件冲突:

1
2
3
4
#define ROSIDL_TYPESUPPORT_INTERFACE__MESSAGE_SYMBOL_NAME( \
typesupport_name, package_name, interface_type, message_name) \
typesupport_name ## __get_message_type_support_handle ## __ \
package_name ## __ ## interface_type ## __ ## message_name

示例符号:rosidl_typesupport_introspection_cpp__get_message_type_support_handle__std_msgs__msg__String

8.2 Introspection Typesupport

用途:运行时反射消息字段——ros2 topic echorclpy、参数转换、调试工具。

核心结构message_introspection.hpp):

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct MessageMember_s {
const char * name_;
uint8_t type_id_; // ROS_TYPE_FLOAT, ROS_TYPE_MESSAGE, ...
size_t string_upper_bound_;
const rosidl_message_type_support_t * members_; // 嵌套消息
bool is_array_;
size_t array_size_;
bool is_upper_bound_;
uint32_t offset_; // 字段在 struct 内偏移
const void * default_value_;
// size/get/fetch/assign/resize 函数指针...
} MessageMember;

Identifieridentifier.cpp):

1
const char * typesupport_identifier = "rosidl_typesupport_introspection_cpp";

Generator 为每个消息生成 MessageMembers 静态表 + get_message_type_support_handle() 导出函数。

8.3 Typesupport 链与 RMW

运行时 publish 路径(简化):

1
2
3
4
5
rclcpp::Publisher::publish(msg)
→ rosidl_typesupport_cpp::get_message_type_support_handle<Message>()
→ 按 rmw 选择的 typesupport_identifier 查找
→ rosidl_typesupport_fastrtps_cpp(DDS CDR 序列化)
→ rmw_publish

Introspection typesupport 不参与 DDS 线格式序列化,但 Python 层需要它把 C struct 转为 Python 对象。


9. rosidl_cli

独立于 colcon 的 命令行工具rosidl_cli/cli.py):

1
2
rosidl generate ...   # 调用已注册 generator 扩展
rosidl translate ... # 格式转换(如 msg → idl)

通过 rosidl_cli/extensions.pyentry_points 可扩展子命令;主要用于工具开发/debug,普通接口包构建走 CMake 路径


10. 完整示例:从 .msg 到可链接库

10.1 作者侧

my_msgs/msg/Velocity.msg

1
2
float64 linear
float64 angular

CMakeLists.txt

1
2
3
4
5
6
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Velocity.msg"
)
ament_export_dependencies(rosidl_default_runtime)
ament_package()

package.xml

1
2
3
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

10.2 构建时发生的事

typesupport_*rosidl_generator_*rosidl_adapterCMaketypesupport_*rosidl_generator_*rosidl_adapterCMakeconvert Velocity.msgbuild/.../msg/Velocity.idlament_execute_extensionsparse_idl + Empy 渲染Velocity.h/hpp, __functions.cintrospection + fastrtps + ...libmy_msgs__rosidl_* targets

10.3 安装布局

1
2
3
4
5
6
7
8
9
10
11
install/my_msgs/
├── share/my_msgs/
│ ├── msg/Velocity.msg
│ ├── msg/Velocity.idl
│ └── cmake/... # exported targets
├── include/my_msgs/my_msgs/
│ └── msg/velocity.hpp
└── lib/
├── libmy_msgs__rosidl_generator_c.so
├── libmy_msgs__rosidl_typesupport_introspection_c.so
└── libmy_msgs__rosidl_typesupport_fastrtps_cpp.so

11. Service 与 Action 的处理差异

类型 Adapter 输出 Parser 产物 额外生成
Message 单 struct IDL Message 1 组 msg 文件
Service request/response struct Service + 两个 Message Request/Response 消息 + srv typesupport
Action goal/result/feedback struct Action + 衍生 3 类 Goal/Result/Feedback + SendGoal/GetResult + FeedbackMessage

CMake 对 .srv 还会在 build 目录拆分出 _Request.msg / _Response.msg,便于 rosidl_interfaces index 与 linter。


12. 关联仓库(非本目录)

仓库路径(Humble) 职责
rosidl_default_generators ros2/rosidl_defaults 聚合默认 generator + typesupport 依赖
rosidl_default_runtime ros2/rosidl_defaults 运行时依赖聚合
rosidl_generator_py ros2/rosidl_python Python 模块生成
rosidl_typesupport_c/cpp ros2/rosidl_typesupport typesupport 分发层
rosidl_typesupport_fastrtps_* ros2/rosidl_typesupport_fastrtps Fast-DDS CDR 序列化
rosidl_typesupport_introspection_* 本仓库 反射 typesupport

find_package(rosidl_default_generators) 会拉入上述外部 generator,使 rosidl_generate_interfaces 一次触发完整工具链。


13. 与 ROS 1 genmsg 对比

维度 ROS 1 genmsg rosidl
源格式 .msg only .msg/.srv/.action + .idl
中间表示 内部 AST 标准 OMG IDL
生成语言 C++(roscpp) C + C++ + Python
Typesupport 无插件概念 多 typesupport 插件
字符串 std::string rosidl_runtime_c__String / 可配置 allocator
数组 固定/vector 固定 array + bounded/unbounded sequence
Action 无原生 原生 .action + 衍生类型
构建 catkin msg 宏 ament rosidl_generate_interfaces + extensions

14. 调试与常见问题

现象 排查方向
rosidl_generate_interfaces 报错缺 action_msgs action 包未声明依赖
缺少 rosidl_interface_packages group package.xml 未加 member_of_group
找不到 typesupport symbol 未 link __rosidl_typesupport_cpp target
修改 .msg 未生效 检查 build 目录缓存;clean rebuild
IDL 解析失败 rosidl translateidl2png 看语法树
Python 导入失败 确认 rosidl_generator_py 已执行(外部包)
introspection 字段偏移错误 查看 rosidl_typesupport_introspection_tests 对应用例

实用命令

1
2
3
4
5
6
7
8
# 查看某包安装了哪些接口
cat install/share/my_msgs/rosidl_interfaces

# 单独转换 msg → idl(debug)
rosidl translate msg my_msgs/msg/Velocity.msg --output-idl ...

# 查看生成头文件
ls build/my_msgs/rosidl_generator_cpp/my_msgs/msg/

15. 源码阅读顺序

  1. 走通样例:手写最小 msg 包,colcon build 后对照 build/install/ 生成物
  2. 适配层rosidl_adapter/parser.pymsg/__init__.pyresource/msg.idl.em
  3. IDL ASTrosidl_parser/definition.pyparser.pygrammar.lark
  4. CMake 编排rosidl_generate_interfaces.cmakeregister_c.cmake
  5. 生成框架rosidl_cmake/__init__.pygenerate_files()
  6. C 生成rosidl_generator_c/__init__.py + resource/idl__struct.h.em
  7. C++ 生成rosidl_generator_cpp/__init__.py + __traits.hpp.em
  8. 运行时message_type_support_struct.htraits.hpp
  9. Introspectionmessage_introspection.hpprosidl_typesupport_introspection_cpp/resource/msg__type_support.cpp.em
  10. 外部衔接rosidl_typesupport_cpp(另一仓库)→ rmw_fastrtps

16. 小结

rosidl 目录实现 ROS 2 强类型接口的编译期核心rosidl_adapter 兼容 Legacy 格式,rosidl_parser 提供标准 IDL AST,rosidl_cmake 通过 ament 扩展点 编排多 generator/typesupport 并行生成,rosidl_runtime_* 定义运行时公共类型与 type_support 结构,rosidl_typesupport_introspection_* 提供字段反射能力。

理解「.msg 如何变成 DDS 线上的字节」需串联 本仓库生成链路外部 typesupport_fastrtps + rmw 两段;调试接口问题时,从 rosidl_generate_interfaces 的 build log 与生成的 detail/*__struct.hpp 入手最为直接。

rpyutils 源码详细分析

rpyutils 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rpyutils
子包数量:1


1. 定位

rpyutils 目录含 1 个 ROS 2 包,工作区路径见下。


2. 子包列表

包名 版本 说明
rpyutils 0.2.2 Package containing various utility types and functions for P…

3. 在 ROS 2 Humble 栈中的关系

ros2总览.md 分层图。


4. 推荐阅读顺序

  1. 阅读各子包 package.xml 2. 入口源码 3. 下游依赖方

5. 小结

rpyutils 为单包仓库,提供 rpyutils 功能。

rviz 源码详细分析

rviz 源码详细分析

工作区路径:/home/cp/work2/ros2Learn/ros2_humble/src/ros2/rviz
版本:11.2.26(Humble),子包 8 个,许可证 BSD 3-Clause(Willow Garage / OSRF)。

rviz 仓库实现 ROS 2 的 3D 可视化工具 rviz2:Qt 图形界面 + Ogre3D 渲染引擎 + pluginlib 插件体系(Display / Tool / Panel / ViewController / FrameTransformer)。用户通过订阅 ROS 话题、查询坐标变换,在 3D 场景中绘制激光、点云、机器人模型、TF 等。

范围说明:本仓库为 rviz2 核心;ROS 1 版 rviz 在 ros-visualization/rviz。Humble 尚未移植 Stereo 等少数 ROS 1 功能(见 README)。


1. 总体认识

1.1 核心职责

能力 实现位置 说明
可执行入口 rviz2 main.cppVisualizerApp → Qt 事件循环
应用框架 rviz_common 管理器、Display/Tool/Panel、属性树、配置
3D 渲染 rviz_rendering Ogre 封装、点云/网格/形状等可视对象
内置插件 rviz_default_plugins LaserScan、TF、RobotModel、Marker 等
Ogre 依赖 rviz_ogre_vendor 打包 Ogre3D + CMake 模块
Assimp 依赖 rviz_assimp_vendor 网格/DAE/STL 加载
渲染测试 rviz_rendering_tests mesh loader 等单元测试
视觉回归测试 rviz_visual_testing_framework 截图对比式 GUI 测试

1.2 在 ROS 2 栈中的位置

用户rviz2rviz_commonrviz_default_pluginsrviz_renderingROS 2ros2 run rviz2 rviz2main.cpp\nQApplication + VisualizerAppVisualizationFrame\n(Qt 主窗口)VisualizationManager\n(核心调度)DisplayFactory\npluginlibToolManager / ViewManagerTransformationManager\n(可插拔 TF)LaserScanDisplayTFDisplayRobotModelDisplayRenderSystem\n(Ogre::Root)PointCloud / Shape / ...rclcpp\nsubscription / clocktf2 / tf2_ros
对比项 ROS 1 rviz ROS 2 rviz(本仓库)
节点 API roscpp rclcpp + 独立 RosClientAbstraction
TF 硬编码 tf 可插拔 FrameTransformer(默认 tf2)
QoS 有限 QosProfileProperty per Display
时间 /clock 支持 rclcpp::Clock + time jump 处理
插件基类包 librviz rviz_common

1.3 主循环(一帧)

VisualizationManager::onUpdate()(定时器驱动,约 30Hz):

1
2
3
4
5
6
7
1. executor_->spin_some(10ms)     // 处理 ROS 回调
2. frame_manager_->update() // 坐标系
3. root_display_group_->update() // 各 Display 更新 Ogre 对象
4. view_manager_->update() // 相机/视角
5. selection_manager_->update()
6. current_tool_->update()
7. ogre_root_->renderOneFrame() // Ogre 渲染(限帧 ≥10ms)

2. 仓库结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
rviz/
├── README.md
├── docs/
│ ├── FEATURES.md
│ ├── plugin_development.md # ★ 插件开发指南
│ └── migration_guide.md
├── rviz2/ # 可执行 (~180 行)
│ └── src/main.cpp
├── rviz_common/ # ★ 框架核心 (~34100 行 C++)
│ ├── include/rviz_common/
│ │ ├── visualization_manager.hpp
│ │ ├── display.hpp / ros_topic_display.hpp
│ │ ├── message_filter_display.hpp
│ │ ├── tool.hpp / panel.hpp / view_controller.hpp
│ │ ├── display_context.hpp
│ │ ├── properties/ # Qt 属性树
│ │ ├── factory/pluginlib_factory.hpp
│ │ ├── transformation/
│ │ └── ros_integration/
│ └── src/rviz_common/
├── rviz_rendering/ # Ogre 封装 (~13400 行)
│ ├── render_system.hpp
│ └── objects/ point_cloud, shape, grid, ...
├── rviz_default_plugins/ # 内置插件 (~51300 行)
│ ├── displays/ laser_scan, image, marker, ...
│ ├── tools/ move_camera, select, ...
│ ├── view_controllers/
│ ├── panels/
│ └── plugins_description.xml
├── rviz_ogre_vendor/
├── rviz_assimp_vendor/
├── rviz_rendering_tests/
└── rviz_visual_testing_framework/

2.1 子包一览

包名 规模 职责
rviz2 极小 可执行文件、文档
rviz_common Qt UI 框架、管理器、插件 API
rviz_rendering Ogre 场景对象、RenderSystem 单例
rviz_default_plugins 最大 默认 Display/Tool/Panel/View/Transformer
rviz_ogre_vendor vendor Ogre3D 源码/预编译 + FindOGRE
rviz_assimp_vendor vendor Assimp 模型加载
rviz_rendering_tests 测试 rendering 层测试
rviz_visual_testing_framework 测试 端到端视觉测试框架

3. rviz2:程序入口

rviz2/src/main.cpp 流程:

  1. rclcpp::remove_ros_arguments() — 剥离 ROS 参数,剩余给 QApplication
  2. rviz_common::set_logging_handlers() — Qt/Ogre 日志转发到 RCLCPP_*
  3. 构造 VisualizerApp(std::make_unique<RosClientAbstraction>())
  4. vapp.init(argc, argv) → 创建 VisualizationFrame、初始化 ROS
  5. qapp.exec() — Qt 主循环

VisualizerAppvisualizer_app.cpp)负责:

  • 解析命令行(-d 加载 .rviz 配置等)
  • 创建 VisualizationFrame
  • 协调 ROS shutdown 与 Qt 退出

4. rviz_common:应用框架

4.1 核心类关系

1
2
3
4
5
6
7
8
9
10
11
VisualizationFrame (QMainWindow)
└── VisualizationManager (extends DisplayContext)
├── DisplayGroup (root_display_group_)
├── DisplayFactory (PluginlibFactory<Display>)
├── ToolManager
├── ViewManager (ViewController 插件)
├── TransformationManager (FrameTransformer 插件)
├── FrameManager (fixed frame / 坐标变换查询)
├── SelectionManager / HandlerManager
├── RenderPanel (嵌入 Ogre RenderWindow)
└── rclcpp::executors (spin_some)

DisplayContextdisplay_context.hpp)是 Display/Tool 插件看到的 窄接口:提供 getSceneManager()getFrameManager()getRosNodeAbstraction()getTransformationManager() 等,便于单元测试 mock。

4.2 Display 插件体系

基类层次

1
2
3
4
5
properties::BoolProperty
└── Display # 所有可视插件基类
└── _RosTopicDisplay # 带 Topic/QoS 属性
└── RosTopicDisplay<MessageType> # 模板订阅
└── MessageFilterDisplay<MessageType> # + tf2::MessageFilter
基类 适用场景
Display 无 ROS 订阅(Grid、Axes)
RosTopicDisplay<T> 普通话题订阅 + processMessage()
MessageFilterDisplay<T> 需要 TF 同步 的消息(LaserScan、PointCloud2)

Display 生命周期

  1. initialize(context) — 获得 DisplayContext、创建 Ogre::SceneNode
  2. onInitialize() — 子类设置订阅/属性
  3. setEnabled(true) → 订阅话题、显示场景节点
  4. 每帧 update(wall_dt, ros_dt) — 动画、衰减、状态更新
  5. reset() / onDisable() — 清数据、退订

属性树:Display 继承 BoolProperty,子属性(Topic、Color、Size 等)挂到 Qt Model,由 Displays 面板 编辑;load()/save() 写入 .rviz YAML。

4.3 MessageFilterDisplay 与 TF

MessageFilterDisplay 组合:

  • message_filters::Subscriber + rclcpp::Subscription
  • tf2_ros::MessageFilter — 仅当变换可用时投递消息
  • Fixed Frame 来自 FrameManager

典型回调链(LaserScan):

1
2
3
4
sensor_msgs/LaserScan 到达
→ MessageFilter (等待 transform 到 fixed frame)
→ LaserScanDisplay::processMessage()
→ laser_geometry 投影 / PointCloudCommon 更新 Ogre 点云

4.4 可插拔坐标变换(ROS 2 新特性)

TransformationManager 通过 PluginlibFactory<FrameTransformer> 加载:

插件 位置 行为
TFFrameTransformer rviz_default_plugins 标准 tf2(默认)
IdentityFrameTransformer rviz_common 恒等变换(无 TF 时 fallback)

GUI Transformation 面板 切换插件。依赖 TF 的 Display 应使用 TransformerGuardrviz_default_plugins),在错误 transformer 下自动禁用。

FrameManager 封装对当前 FrameTransformer 的查询,供 Display 将数据变换到 Fixed Frame

4.5 Tool / Panel / ViewController

类型 基类 示例
Tool rviz_common::Tool Move Camera、Select、2D Nav Goal、Publish Point
Panel rviz_common::Panel Displays、Selection、Time、Tool Properties、Views
ViewController rviz_common::ViewController Orbit、XY Orbit、First Person、TopDownOrtho

Tool 处理 RenderPanel 鼠标/键盘事件;ViewController 控制 Ogre::Camera 位姿。

4.6 插件加载:PluginlibFactory

1
2
3
4
// pluginlib_factory.hpp
class_loader_ = new pluginlib::ClassLoader<Type>(
package.toStdString(), base_class_type.toStdString());
// 例如 package="rviz_common", base="rviz_common::Display"

VisualizationManager::createDisplay(class_lookup_name)DisplayFactory::make() → pluginlib 实例化。

插件注册(rviz_default_plugins/CMakeLists.txt):

1
pluginlib_export_plugin_description_file(rviz_common plugins_description.xml)

4.7 ROS 集成层

RosClientAbstraction / RosNodeAbstraction

  • 封装 rclcpp::initrclcpp::Node 创建
  • Display 通过 context_->getRosNodeAbstraction() 获取节点,在同一 executor 上 spin_some
  • 支持 --ros-args、匿名节点名等

与纯 rclcpp 节点不同,rviz 在 GUI 线程定时 spin,而非单独 rclcpp::spin 线程(部分 Display 仍可能用异步回调)。

4.8 配置持久化

  • 格式:YAML(.rviz 文件)
  • 读写YamlConfigReader / YamlConfigWriter + Config 树形结构
  • 内容:Display 列表、属性值、ViewController、Transformation 插件、窗口布局等

启动:ros2 run rviz2 rviz2 -d my_config.rviz


5. rviz_rendering:Ogre 渲染层

5.1 RenderSystem 单例

RenderSystem::get() 管理:

  • Ogre::Root 初始化
  • OpenGL 渲染插件加载(OgreGLPlugin
  • Ogre::OverlaySystem
  • makeRenderWindow(window_id, w, h) — 绑定 Qt 窗口 X11/Win/Cocoa ID

Qt 集成RenderPanel 将原生窗口 handle 交给 Ogre 创建 RenderWindow

5.2 可视对象(objects/)

用途
PointCloud / PointCloudRenderable 点云(激光、DepthCloud)
Shape 立方体、球、箭头等
Grid 地面网格
BillboardLine 路径、多边形线
MovableText 3D 文本
WrenchVisual / EffortVisual 力/力矩可视化
CovarianceVisual 协方差椭圆

Mesh 加载mesh_loader + Assimprviz_assimp_vendor)加载 DAE/STL/OGRE mesh。

5.3 与 Display 的分工

  • Display(rviz_common/plugins):ROS 消息、TF、属性、何时更新
  • rendering 对象:如何在 Ogre 里画(材质、顶点缓冲、LOD)

例:LaserScanDisplay 使用 rviz_default_plugins::PointCloudCommon,内部持有 rviz_rendering::PointCloud


6. rviz_default_plugins:内置插件

6.1 plugins_description.xml

单文件注册 Display / Tool / Panel / ViewController / FrameTransformer(节选):

1
2
3
4
5
<class name="rviz_default_plugins/LaserScan"
type="rviz_default_plugins::displays::LaserScanDisplay"
base_class_type="rviz_common::Display">
<message_type>sensor_msgs/msg/LaserScan</message_type>
</class>

class name 即 GUI 中 Add Display 对话框里的 lookup name。

6.2 Display 分类(README 已移植列表)

类别 代表 Display 消息类型
传感器 LaserScan, PointCloud2, Image, Camera sensor_msgs
机器人 RobotModel, TF, Odometry urdf, tf2, nav_msgs
地图 Map, GridCells nav_msgs
标注 Marker, MarkerArray, InteractiveMarker visualization_msgs
几何 Pose, Path, Polygon, Wrench geometry_msgs
其他 Grid, Axes, DepthCloud, Effort

6.3 LaserScanDisplay 实现要点

1
2
class LaserScanDisplay : public
rviz_common::MessageFilterDisplay<sensor_msgs::msg::LaserScan>
  • TransformerGuard 确保 tf2 transformer 生效
  • laser_geometry::LaserProjection 将 scan 转为点云
  • PointCloudCommon 管理颜色、衰减、Ogre 点云对象
  • update() 中处理 transformer 切换 与点云衰减

6.4 RobotModelDisplay

  • 订阅 robot_description(参数或 topic)
  • urdf + kdl / tf2 更新关节角
  • Ogre 场景图挂载 link mesh(Assimp 加载)

6.5 工具与面板

Tools:MoveCamera、FocusCamera、Measure、Select、Interact、InitialPose、Goal、PublishPoint

Panels:Displays、Help、Selection、Time、Tool Properties、Views、Transformation

ViewControllers:Orbit、XYOrbit、FirstPerson、FrameAligned、ThirdPersonFollower、TopDownOrtho


7. Vendor 包

7.1 rviz_ogre_vendor

  • 提供固定版本 Ogre3D(Humble 为 1.x 分支)
  • 导出 CMake:find_package(rviz_ogre_vendor)Ogre 目标
  • rviz_rendering / rviz_common 链接 Ogre

7.2 rviz_assimp_vendor

  • 包装 Assimp
  • rviz_rendering mesh_loader 用于 COLLADA/STL 等

7.3 fastrtps_cmake_module 类比

rviz 不依赖 DDS 消息栈做渲染;vendor 仅图形栈。与 rosidl_typesupport_fastrtps 无直接关系。


8. 插件开发概要

详见 docs/plugin_development.md

8.1 五步开发 Display

  1. 继承 DisplayRosTopicDisplay<T> / MessageFilterDisplay<T>
  2. PLUGINLIB_EXPORT_CLASS(my_plugin::MyDisplay, rviz_common::Display)
  3. 编写 plugins_description.xml
  4. pluginlib_export_plugin_description_file(rviz_common ...)
  5. target_link_libraries(... rviz_common::rviz_common rviz_rendering::rviz_rendering)

8.2 扩展点汇总

插件类型 基类
Display rviz_common::Display
Panel rviz_common::Panel
Tool rviz_common::Tool
ViewController rviz_common::ViewController
FrameTransformer rviz_common::transformation::FrameTransformer

9. 测试体系

9.1 rviz_rendering_tests

  • Ogre 环境 fixture
  • mesh_loader、ogre media 导出测试

9.2 rviz_visual_testing_framework

  • Page Object 模式驱动 Qt GUI
  • 发布测试 TF/消息 → 截图 → 与 golden image 对比
  • 用于回归 Display 渲染结果

10. 端到端序列图:LaserScan 显示

RenderSystemPointCloud (Ogre)FrameManagertf2::MessageFilterLaserScanDisplayVisualizationManagerrclcpp激光节点RenderSystemPointCloud (Ogre)FrameManagertf2::MessageFilterLaserScanDisplayVisualizationManagerrclcpp激光节点publish /scanspin_some()LaserScan callback查询 fixed frame 变换okprocessMessage()更新顶点update()renderOneFrame()

11. 与 ROS 2 其他组件关系

组件 关系
tf2 / tf2_ros 默认 FrameTransformer;MessageFilter 同步
robot_state_publisher RobotModel 显示 link 姿态
urdf 解析 robot_description
interactive_markers InteractiveMarkerDisplay
rclcpp QoS Display 可配置 reliability/durability
ros2 bag play rviz 订阅回放话题 + /clock

12. 调试与常见问题

现象 排查
Fixed Frame 红字 TF 树不完整;检查 ros2 run tf2_tools view_frames
话题无显示 QoS 不兼容;Display 属性中改 QoS
插件找不到 pluginlib 描述文件是否 export;AMENT_PREFIX_PATH
Ogre 黑屏 显卡/OpenGL;LIBGL_* 环境变量
sim time 不动 Time 面板是否 Pause;是否订阅 /clock
RobotModel 空白 robot_description 参数;mesh 路径 package://

常用命令

1
2
3
4
ros2 run rviz2 rviz2
ros2 run rviz2 rviz2 -d install/share/nav2_bringup/rviz/nav2_default_view.rviz
# 调试 TF
ros2 run tf2_tools view_frames

13. 与 ROS 1 rviz 架构对比

维度 ROS 1 ROS 2(本仓库)
GUI Qt Qt(相同范式)
渲染 Ogre Ogre(rviz_rendering 拆分)
插件库 librviz rviz_common + pluginlib
TF tf 内置 可插拔 Transformer
配置 .rviz YAML 兼容思路,格式演进
代码组织 单包为主 8 包分层

14. 源码阅读顺序

  1. 启动链rviz2/main.cppvisualizer_app.cppvisualization_frame.cpp
  2. 主循环visualization_manager.cpponUpdate()
  3. 插件 APIdisplay.hppros_topic_display.hppmessage_filter_display.hpp
  4. 工厂pluginlib_factory.hppadd_display_dialog.cpp
  5. 示例 Displaylaser_scan_display.cpp + point_cloud_common.cpp
  6. 渲染render_system.cppobjects/point_cloud.cpp
  7. TF 插件transformation_manager.cpptf_frame_transformer.cpp
  8. 插件清单plugins_description.xml
  9. 插件开发docs/plugin_development.md

15. 小结

rviz 仓库以 rviz_common 为 Qt+ROS 调度核心,rviz_rendering 为 Ogre 绘图引擎,rviz_default_plugins 提供开箱即用的 Display/Tool/Panel,rviz2 仅为薄入口。数据路径是:rclcpp 订阅 →(可选 TF 滤波)→ Display 处理消息 → rendering 对象更新 → VisualizationManager 驱动 Ogre 渲染

自定义可视化应优先继承 RosTopicDisplayMessageFilterDisplay,并理解 Fixed FrameTransformationManager;性能问题则关注 PointCloud 更新频率与 renderOneFrame 限帧逻辑。