RealSense RealDDS Flexible 消息主题完全指南:IDL 结构、JSON/CBOR 编解码与 QoS 策略
【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense
本文围绕 LibreRealSense 仓库中 RealDDS 子系统(third-party/realdds)的Flexible 消息主题展开。Flexible 是一种"万能载荷"型 DDS 消息格式,设备发现(device-info)、控制(control)、通知(notification)、元数据(metadata)等所有"非流式"话题都复用它。读完本文,你将掌握 Flexible 消息的 IDL 数据结构、JSON/CBOR/CUSTOM 三种数据格式的编码规则、C++ 封装flexible_msg的读写用法、其 QoS 约定,以及如何通过官方脚本在 DDS 网络上直接发送 Flexible 消息进行联调验证。
什么是 Flexible 消息
在 RealDDS 的话题体系中,数据流(图像、IMU)走 ROS2 兼容的专用消息类型,而大量控制面信息——设备信息、客户端请求、服务端通知、帧元数据——格式各异、演进频繁,为每种消息单独定义结构体成本过高。Flexible 消息正是为此设计:只要数据体积不超过 IDL 规定的上限,它可以承载任意格式的任意数据。
依据 flexible/readme.md,该目录下的文件"绝大部分由 IDL 自动生成",因此理解 Flexible 消息的第一步就是阅读其 IDL 定义。
IDL 数据结构与字段语义
Flexible 消息的权威定义位于 flexible.idl,其核心结构如下(仓库内实际 IDL,注意与 readme 中示例的差异,见下文"大小上限"一节):
module realdds { module topics { module raw { enum flexible_data_format { FLEXIBLE_DATA_JSON, FLEXIBLE_DATA_CBOR, FLEXIBLE_DATA_CUSTOM }; struct flexible { flexible_data_format data_format; octet version[4]; // in decreasing importance, so version[0] is highest sequence<octet,32768> data; // bound to 32KB }; }; }; };结构体包含三个字段:
| 字段 | 类型 | 语义 |
|---|---|---|
data_format | 枚举flexible_data_format | 声明data载荷的编码格式,取值FLEXIBLE_DATA_JSON/FLEXIBLE_DATA_CBOR/FLEXIBLE_DATA_CUSTOM |
version | octet[4](4 字节数组) | 消息格式版本号,按重要性递减排列,version[0]为最高位字节 |
data | sequence<octet, 32768> | 实际载荷字节序列,上限 32768 字节(32KB) |
由 IDL 生成的 C++ 类型realdds::topics::raw::flexible位于 flexible.h,枚举以uint32_t为底层类型,结构体则通过data_format()、version()、data()三组访问器暴露成员,并附带 CDR 序列化/反序列化接口(serialize/deserialize)、最大序列化尺寸计算(getMaxCdrSerializedSize)等 FastDDS 生成代码的标配方法,可直接用于 FastDDS 的DataWriter/DataReader。
大小上限:readme 与 IDL 的差异说明
readme 中给出的示例将data序列上限写为4096(4K 字节),并声称"当前上限为 4K 字节";而仓库中实际的 flexible.idl 已将上限定为32768(32KB)。撰写代码时请以仓库内的 IDL 文件为准——该上限直接决定了单条 Flexible 消息可携带的最大字节数,超出部分在序列化阶段即会失败。这也解释了为何 metadata、device-info 这类 JSON 消息体积虽小,但控制/通知类消息仍被约束在数十 KB 以内。
版本字段的约定
按 readme 说明,当前所有 Flexible 消息的版本一律为0。不过在 C++ 封装层(见下文)中,版本号以uint32_t形式传入,并在序列化时被拆分为 4 个八位字节,version[0]对应最高字节——这一"高位在前"的字节序约定在跨端解析版本号时务必保持一致。
三种数据格式:JSON、CBOR 与 CUSTOM
readme 明确指出:"格式通常是 JSON,但也可以是其他格式(CUSTOM 和 CBOR 当前已定义但并未真正投入使用)"。三种格式的语义如下:
- JSON:
data是 JSON 文本的字符表示(每字符 1 字节),即 UTF-8/ASCII 文本字节流。这是当前 RealDDS 控制面消息的事实标准格式。 - CBOR:
data是同一份 JSON 数据的二进制表示(Concise Binary Object Representation),体积更小、解析更快,适合对载荷大小敏感的场景。 - CUSTOM:字节由客户端使用自己的数据结构自行解释,RealDDS 不提供任何内置解析逻辑。
源码级的编解码实现
C++ 封装类 flexible_msg 通过构造函数与访问方法将上述语义落地,其实现位于 flexible-msg.cpp:
- JSON 编码:
flexible_msg( rsutils::json const & j, uint32_t version = 0 )将 JSON 对象j.dump()为字符串后逐字节拷入std::vector<uint8_t>,data_format固定为JSON。 - CBOR 编码:
flexible_msg( data_format format, rsutils::json const & j, uint32_t version = 0 )中,当format == CBOR时调用rsutils::json::to_cbor( j )生成二进制载荷;若传入CUSTOM与 JSON 组合,实现会直接抛出runtime_error("invalid format for json flexible message"),印证了 CUSTOM 目前仅支持原始字节、不支持 JSON 转换。 - 解码:
json_data()根据_data_format分发——JSON 载荷用rsutils::json::parse( begin, end )解析文本,CBOR 载荷用rsutils::json::from_cbor( ... )反解,而对非 JSON 数据(如 CUSTOM)则抛出"non-json flexible data is still unsupported"异常。从源码结构看,CUSTOM 载荷目前主要面向custom_data<T>()模板方法——它把_data缓冲区按reinterpret_cast<T const*>直接映射为自定义结构体指针,适用于发送方与接收方共享同一套内存布局定义的场景。
版本号的字节序处理
to_raw()(将flexible_msg转回原始raw::flexible)展示了版本号的完整打包逻辑:
raw_msg.version()[0] = _version >> 24 & 0xFF; raw_msg.version()[1] = _version >> 16 & 0xFF; raw_msg.version()[2] = _version >> 8 & 0xFF; raw_msg.version()[3] = _version & 0xFF;反向解包则发生在flexible_msg( raw::flexible && )构造函数中,将 4 个字节重新拼回uint32_t。这一"整型版本号 ↔ 4 字节大端数组"的双向转换是阅读或扩展 Flexible 消息时最容易踩坑的细节。
话题类型与 DDS 命名
Flexible 消息对应的 DDS topic type 为:
realdds::topics::raw::flexible
在实际使用中,话题名称并不固定为 "flexible"——Flexible 是一种"被复用的消息格式",而非单一话题。依据 topics/readme.md 的话题层级,以下话题全部使用 Flexible 消息格式:
realsense/ ├── device-info — 设备发现广播(Flexible/JSON) ├── <model>_<serial>/ — 每台设备的话题根目录 │ ├── notification — 服务端通知、响应、日志(Flexible/JSON) │ ├── control — 客户端对服务端的请求(Flexible/JSON) │ └── metadata — 可选的流元数据(Flexible/JSON,QoS 例外) rt/realsense/ — ROS2 兼容的数据流(非 Flexible)典型载荷示例
设备发现(device-info)——见 discovery.md,JSON 中的name与topic-root为必填字段:
{ "name": "Intel RealSense D405", "serial": "123622270732", "product-line": "D400", "topic-root": "realsense/D405_123622270732" }服务端通知(notification)——见 notifications.md,所有通知必须是 JSON 对象并以id字段标识类型,未识别的字段和通知会被客户端忽略:
{ "id": "some-message-id", "message": "this is a field value" }帧元数据(metadata)——见 metadata.md,timestamp是客户端与图像帧同步的关键字段:
{ "stream-name": "Color", "header": {"frame-number": 1234, "timestamp": 123456789, "timestamp-domain": 0}, "metadata": {"Exposure": 123, "Gain": 456} }metadata 的消息格式完全由 Flexible 承载——这既是"灵活"的体现,也带来了协议约定成本:metadata 的字段名必须与rs2_frame_metadata_to_string返回的名字一致、值必须为整型long long,否则会被忽略或标记为缺失。
QoS 策略:可靠传输与一个例外
依据 flexible/readme.md,所有 Flexible 话题通常使用可靠(reliable)传输(与采用 best-effort 的数据流形成对比),除非另有说明:
- Reliability(可靠性):
RELIABLE - Durability(持久性):
VOLATILE
唯一的例外是 metadata 话题:由于元数据体积小、频率高、丢失后不影响图像本身(客户端仅失去与该帧关联的元数据),metadata.md 将其 QoS 定为BEST_EFFORT+VOLATILE。设计权衡在于:best-effort 下消息可能丢失,但图像流不会因元数据缺失而中断;而 device-info、control、notification 属于必须送达的控制面消息,因此坚持 RELIABLE。
从 IDL 到代码:FastDDSGen 生成流程
Flexible 消息的 C++ 头文件、PubSubTypes、TypeObject 均由 FastDDS 的FastDDSGen工具从 IDL 生成。由于该工具依赖 Java 且版本挑剔,官方推荐的流程是使用 Docker 镜像离线生成(详见 topics/readme.md,当前仓库生成所使用的是 FastDDS 2.10.6):
从 eProsima 下载 FastDDS 套件 Docker 镜像并加载:
docker load -i /mnt/c/work/ubuntu-fastdds-suite\ v2.10.6.tar在
third-party/realdds/目录下,对每个 topic(此处为flexible)执行容器内生成:for topic in flexible do cd include/realdds/topics/${topic} cid=`docker run -itd --privileged ubuntu-fastdds:v2.10.6` docker exec $cid mkdir /idl /idl/out docker cp *.idl $cid:idl/ docker exec -w /idl/out $cid fastddsgen -cs -typeobject /idl/`ls -1 *.idl` docker cp $cid:/idl/out . docker kill $cid cd out for cxx in *.cxx; do mv -- "$cxx" "../../../../../src/topics/${cxx%.cxx}.cpp"; done mv -- *TypeObject.h "../../../../../src/topics/" mv * .. cd .. rmdir out cd ../../../.. done该脚本自动完成
.cxx→.cpp重命名、输出文件归位等操作。生成后仍需手工处理:更新
.cpp中的#include(例如#include "flexible.h"需改为#include <realdds/topics/flexible/flexible.h>),并将版权头替换为 LibRealSense 的版权声明。
仓库内 flexible.h 文件头部的// This file was generated by the tool gen.注释即为上述流程的产物标记,对应的 flexiblePubSubTypes.h、flexibleTypeObject.cpp 等文件分布在include/realdds/topics/flexible/与 src/topics 中。
实战:用 topic-send.py 收发 Flexible 消息
RealDDS 提供了 Python 绑定(pyrealdds)与两个脚本工具,可直接在 DDS 网络上收发 Flexible 消息,非常适合验证话题连通性与 JSON 载荷格式。发送脚本位于 scripts/topic-send.py,接收脚本为同目录下的topic-sink.py。
向任意话题发送 JSON Flexible 消息
python topic-send.py --topic /my/topic --message '{"data":"value"}'脚本内部调用dds.message.flexible.create_topic( participant, topic_path )创建话题、以dds.message.flexible( message ).write_to( writer )发送——即将 JSON 对象按上文描述的 JSON 编码规则序列化为 Flexible 载荷。发送成功后write_to返回该样本的唯一序列号;若传入--ack参数,脚本会调用writer.wait_for_acks(...)等待对端确认(可靠传输下写入返回不代表对方已收到,等待 ack 才能确保投递)。
向真实设备发送控制消息
python topic-send.py --device /realsense/D555_<serial_number> --message '{"id":"ping"}'该模式先以dds.device( participant, info )构造设备代理、等待其就绪,再通过device.send_control( message, wait_for_reply )将请求发送到设备的control话题并等待回复——回复同样是 Flexible 格式的 JSON。
关键命令行参数
| 参数 | 说明 |
|---|---|
--device <path> | 设备话题根目录(如/realsense/D555_<serial>),指定后消息发往该设备的 control 话题 |
--topic <path> | 任意 DDS 话题路径,与--device二选一(互斥) |
--message <json> | 内联 JSON 消息 |
--message-file <file> | 从 JSON 文件读取消息(-表示 stdin),与--message互斥 |
--blob <file> | 以 blob 消息发送二进制文件(需配合--topic) |
--domain <0-232> | DDS 域编号,默认 0;不同域之间互不可见 |
--ack | 发送后等待对端确认 |
--debug/--quiet | 开启调试输出 / 静默模式 |
脚本中对 Flexible/控制类消息使用默认的可靠 QoS(dds.topic_writer.qos()返回 reliable 配置),而对 blob 大文件则额外配置了流控参数(max-bytes-per-period256 × 1470 字节、周期 250ms),用于避免大块数据瞬时打爆接收端缓冲——这是理解 Flexible 话题与流式话题在传输策略上差异的又一佐证。
总结
Flexible 消息是 RealDDS 控制面的"通用信封":以 flexible.idl 中"格式枚举 + 4 字节版本号 + 32KB 上限字节序列"的三段式结构,承载了设备发现、控制请求、服务端通知与帧元数据四类话题。其默认 QoS 为RELIABLE/VOLATILE,metadata 话题则降级为BEST_EFFORT以换取流式场景的鲁棒性;JSON 是当前事实标准编码,CBOR 为二进制备选,CUSTOM 留给自定义结构体直读。无论是阅读 flexible-msg.cpp 的编解码实现、复用 FastDDSGen 生成新 IDL,还是用 topic-send.py 在网络上直接发送载荷进行调试,本文给出的结构、字节序与 QoS 约定都能作为最直接的参照。
【免费下载链接】librealsenseRealSense SDK项目地址: https://gitcode.com/GitHub_Trending/li/librealsense
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考