跳到主要内容

数据打包与解析

Topic 提供两项与链路相关的功能:

  • Topic::PackData() / Topic::PackRaw():把一条消息按 topic 的运行时契约打成 packet;
  • Topic::Server:把收到的字节流解析成 packet,再投递回已注册的 topic。

打包和解析使用以下信息:

  • topic 名称 CRC32;
  • payload 固定长度;
  • payload 对齐要求;
  • packet 头尾 CRC 校验。

打包一条强类型消息​

LibXR::Topic::Domain domain("msg");
auto topic = LibXR::Topic::CreateTopic<double>("temperature", &domain);

LibXR::Topic::PackedData<double> packet;
topic.PackData(36.5, packet, LibXR::MicrosecondTimestamp(123456));

如果不传时间戳,会自动使用 Topic::NowTimestamp():

topic.PackData(36.5, packet);

PackData() 直接打包传入的 payload,与 Publish() 相互独立。

用 raw payload 打包​

已有按 Topic 类型契约排列的字节块时,使用 PackRaw():

double value = 72.72;
uint8_t raw_packet[LibXR::Topic::PACK_BASE_SIZE + sizeof(double)] = {};

topic.PackRaw(LibXR::ConstRawData(value),
LibXR::RawData(raw_packet, sizeof(raw_packet)),
LibXR::MicrosecondTimestamp(6006));

PackRaw() 的约束:

  • data.size_ 必须等于该 topic 的 PayloadSize();
  • 输出缓冲区至少要容纳 PACK_BASE_SIZE + payload_size;
  • 空指针返回 ErrorCode::PTR_NULL;
  • 尺寸不对返回 ErrorCode::SIZE_ERR;
  • 输出缓冲区不够返回 ErrorCode::NO_BUFF。

packet 格式​

头部固定为 16 字节,完整 packet 额外再跟一个尾部 CRC8:

字段字节数说明
prefix1固定前缀 0x5A
data_len_raw3小端 24 位 payload 长度
topic_name_crc324topic 名称 CRC32 键
timestamp_us_raw6小端 48 位微秒时间戳
version1协议版本 0x01
pack_header_crc81头部 CRC8
payloadN负载字节
trailing crc81整包尾 CRC8

也就是说,完整大小是:

LibXR::Topic::PACK_BASE_SIZE + payload_size

其中 PACK_BASE_SIZE = 17。

解析字节流​

Topic::Server 是一个状态机解析器。典型用法:

LibXR::Topic::Server server(512);
server.Register(topic);

size_t delivered = server.ParseData(LibXR::ConstRawData(packet));

如果在回调或 ISR 路径喂数据,使用:

server.ParseDataFromCallback(LibXR::ConstRawData(packet), true);

Server 的处理步骤:

  • 在输入流中同步到下一条 packet 起点;
  • 校验头部和尾部 CRC;
  • 根据 topic_name_crc32 找到已注册的 topic;
  • 按 topic 的类型契约把 payload 发布给订阅者。

注册 topic 的要求​

server.Register(topic);

注册时检查两项条件(ASSERT,只在 Debug 构建下生效):

  • payload_size + PACK_BASE_SIZE 不超过 Server 的缓冲区长度;
  • payload_alignment <= CACHE_LINE_SIZE。

Server(buffer_length) 的 buffer_length 同时是内部字节队列的容量:它决定能接收的最大 packet,也限制每次 ParseData() 传入的数据量。一次传入的数据超过队列剩余空间时,这批数据整体丢弃。

解析时的兼容规则​

Server 把 packet 发布到 Topic 时,按以下规则处理长度不一致的 payload:

  • 如果收到的 payload 短于 topic 固定大小,只保证前缀部分有效,后半段未定义;
  • 如果收到的 payload 长于 topic 固定大小,只保留前缀部分,多余字节直接截断。

这条规则用于兼容 payload 长度有偏差的上游。

最小链路示例​

LibXR::Topic::Domain domain("msg");
auto topic = LibXR::Topic::CreateTopic<double>("temperature", &domain);

double rx_value = 0.0;
auto cb = LibXR::Topic::Callback::Create(
[](bool, double* target, LibXR::MicrosecondTimestamp, double& data)
{
*target = data;
},
&rx_value);
topic.RegisterCallback(cb);

LibXR::Topic::PackedData<double> packet;
topic.PackData(48.48, packet, LibXR::MicrosecondTimestamp(4004));

LibXR::Topic::Server server(512);
server.Register(topic);
server.ParseData(LibXR::ConstRawData(packet));
代码块已折叠

这条链路的结果是:Server 解析 packet 后,会把 payload 重新发布到该 topic,然后由回调订阅者接收。

接口速览​

接口作用
PackedData<T>一条完整 packet 的强类型字节布局
PackData()用 topic 契约打包强类型 payload
PackRaw()用 topic 契约打包 raw payload
Server::Register()注册可接收 packet 的 topic
Server::ParseData()普通上下文解析输入字节流
Server::ParseDataFromCallback()回调 / ISR 路径解析输入字节流

进程内的发布订阅见 Topic 页面,Linux 进程间共享大 payload 见 LinuxSharedTopic。