ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Serial Studio 帧注解层(Frame Annotation Layer)深度指南:用 JS 解码器给原始字节流标注语义

Serial Studio 帧注解层(Frame Annotation Layer)深度指南:用 JS 解码器给原始字节流标注语义 Serial Studio 帧注解层Frame Annotation Layer深度指南用 JS 解码器给原始字节流标注语义【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-StudioSerial Studio 的帧注解层spec 0059为终端Terminal原始字节流引入了一套可叠加的注解系统用户可以编写 JavaScript 解码器把字节区间标注为起始符、载荷、CRC、结束符等带颜色的类class并分组到多行row轨道上配套提供轨道条、可筛选表格含 CSV 导出与按类提取的载荷视图。读完本文你将掌握注解层的完整设计动机、数据模型与解码器 API、四个面板视图的用法以及如何在项目中编写和部署自己的帧解码器。本文以仓库中的 规划文档、规格文档 与 任务清单 为骨架结合 Annotations.h / Annotations.cpp 源码与 测试用例 展开。为什么需要注解层字节流无人解释的中间层Serial Studio 能看到设备发送的每一个字节但默认并不解释它们。终端显示原始字节hex/文本解析器把带分隔符的帧变成数据集但中间层——哪些字节是头部、哪些是 CRC、哪些是载荷、载荷是什么意思——只存在于用户的脑子里或者被 JS/Lua 解析器在提取数值时随手丢弃。当设备使用二进制协议Modbus RTU、MAVLink、带长度字节与校验和的厂商私有包时调试意味着在控制台里手工数字节。规格文档spec.md把问题概括得很清楚逻辑分析仪logic analyzer查看器用户能在同一份采集数据上得到一叠解码器行bits → bytes → packets而 Serial Studio 无法在它已经拥有的字节流上提供同样的体验。注解层正是为填补这一空白而设计它不是新的协议解码库spec 明确声明首日不内置任何 sigrok 式的解码器而是地基 一两个可运行示例——用户可自行编写的解码器附着的基板substrate。总体设计一个模型三个组件四个视图根据 plan.md 的 Approach 段落整个特性由三个 C 类构成全部位于Console命名空间组件职责源码位置AnnotationModel唯一数据模型注解记录、字符串驻留表、解码器声明的行/类、有界原始字节副本同时是表格视图背后的QAbstractTableModelAnnotations.hAnnotationDecoder在独立QJSEngine中运行用户 JS 解码器受JsWatchdog200 ms保护按数据块chunk节奏调用带字节结转carry-overAnnotations.hAnnotationFilter行/类过滤代理服务表格视图Annotations.h三者由Console::Handler统一持有构造函数中以this为父对象普通new无单例通路从hotpathRxData/hotpathRxDeviceData仅当前设备向解码器喂数据并作为annotations、annotationDecoder、annotationFilter三个属性暴露给 QML。ConsoleAnnotations.qml面板由控制台 ribbon 开关切换展示四个标签页轨道条track strip、可筛选表格含 CSV 导出、载荷视图某一类的 hex/文本、解码器编辑器Apply/Enable/Clear。解码器代码与启用状态持久化在widgetSettings(console)下。数据模型有界的、可提取载荷的注解仓库Annotation 记录结构一条注解在 Annotations.h 中定义为struct Annotation { static constexpr int kTextLevels 3; qint64 start; // 起始绝对字节偏移含 qint64 end; // 结束绝对字节偏移含 qint32 row; // 解码器声明行索引 qint32 cls; // 解码器声明类索引 qint32 texts[kTextLevels]; // 3 级驻留文本 id由长到短 };要点在于texts[3]同一含义提供由长到短的三个渲染文本如 Sync byte 0xFE / SYNC / S窄跨度也能显示点内容。文本在模型内以整数 id 存储字符串驻留而非重复拷贝。五个有界容量常量Annotations.h 定义了所有边界常量值含义kMaxAnnotations65536注解记录上限容量裁剪kMaxTexts4096驻留文本表上限kMaxRetainedBytes1 MiB120保留原始字节窗口上限kMaxRows16解码器可声明的最大行数kMaxClasses64最大类数驻留文本表 id 0 被固定为溢出占位符...当文本数量超过kMaxTexts后新文本全部折叠到 id 0绝不增长见internTextAnnotations.cpp。这正是验收标准 AC2 的机制——1M 条注解、8 个不同文本时驻留表只存 8 个字符串模型内存由终端保留窗口约束。数据流暂存 → 提交 → 裁剪写入路径是一个典型的分批提交模式annotate(start, end, row, cls, texts)Annotations.cpp——JS 可调用的入口仅做参数合法性检查end start、越界行/类一律忽略并把记录暂存到m_pending待处理超过kMaxAnnotations时先裁剪掉前 1/4。因此解码器每秒发出数千条注解也只按 UI tick 合并成一次模型事务。commitPending()Annotations.cpp——以一次beginInsertRows/endInsertRows批量发布超容量时先dropOldest裁剪最旧记录若发现乱序注解a.start m_items.back().start则清掉有序标志。ingestBytes(bytes)Annotations.cpp——追加原始字节并推进绝对偏移计数器超过kMaxRetainedBytes时移除头部多余字节并调用trimToRetainedBytes()丢弃早于保留窗口的注解。trimToRetainedBytes()Annotations.cpp保证了需求 R1终端丢掉旧字节时结束位置早于保留窗口的注解随之丢弃。存储用std::dequeAnnotation前缀裁剪只花在真正被丢弃的记录上见dropOldest注释Annotations.cpp不会复制幸存数据。轨道条渲染从注解到像素几何轨道条不走每标记一个 scene-graph item 的老路。collectRunsAnnotations.cpp把同一类、当前像素尺度下无法区分的相邻注解合并成SpanRun运行段每像素尺度最多合并一个簇宽窗口下退化为逐像素密度标记而非单团色块trackStripAnnotations.cpp则把运行段打包成一个扁平的 typed-array 几何数据{geometry, labels, shortLabels, classes, count}其中 geometry 每段 6 个数x、宽度、start、end、class、合并计数——这正是规格 R5 的每个声明行一条覆盖轨道选择能放入跨度像素宽度的最长texts[i]的实现。仅当运行段数 ≤kLabelledSpanBudget256时才附带文本标签避免海量标注下每 tick 的文本开销。载荷提取与 CSV 导出payloadBytes(cls, maxBytes)Annotations.cpp按流顺序拼接某类所有注解覆盖的字节直接索引保留窗口内的原始字节m_bytes并跳过已滚出窗口的区间payloadHex/payloadText分别给出大写空格分隔 hex 与 UTF-8 文本QML 面板上限为 65536 字节。exportCsv(path)Annotations.cpp写出start,end,length,row,class,text六列表头字段带引号转义且显式 flush 后检查QTextStream状态——因为QTextStream有缓冲不 flush 的话最后一块数据会在析构时才落盘设备错误将无法上报。这正是 AC3CSV 重新打开后每行一条注解的依据。解码器运行时JS、看门狗与失败闩锁解码器协议解码器是一个全局 JS 对象Annotations.cpp 的compile()强制校验decoder { rows: [frames, fields], // 行名数组≤16 classes: [ // 类定义数组≤64name 可选 color {name: address, color: #4e79a7}, // ... ], decode: function(bytes, offset, ctx, size) { // 返回本次消费的字节数 // bytes: 当前结转后的字节Uint8Array 包装 // offset: 这批字节的绝对起始偏移 // ctx: AnnotationModel可调用 ctx.annotate(...) // size: 显式长度避免脚本循环里反复做字符串→索引转换 return consumed; // 未消费部分结转到下一块 } }ctx.annotate(start, end, row, cls, texts)即上文模型入口texts数组不必填满 3 级模型会用最近的较长渲染回退见text()Annotations.cpp。行与类必须在rows/classes中预先声明——readLayout()Annotations.cpp读取失败数组为空时编译报错 decoder.rowsanddecoder.classesmust be non-empty arrays。看门狗、结转与失败闩锁看门狗每次decode()调用都经DataModel::JsWatchdog执行kWatchdogMs 200见 Annotations.h超时后fail()禁用解码器并给出decode() exceeded 200 ms消息。有界结转feed(bytes)Annotations.cpp把新块追加到m_carry调用decode按返回值裁剪已消费部分结转上限kMaxCarry 4096字节超限时连同偏移一起丢弃头部。m_carryOffset记录绝对位置保证注解偏移在流上连续。失败闩锁fail()Annotations.cpp递增errorCount、记录lastError、置failedtrue、禁用并销毁引擎同时qWarning输出脚本需重新 Apply 才能恢复。这样每块都抛异常的解码器只产生一条错误AC5不会弹出对话框风暴。视图门控解码只在至少一个注解视图在屏时运行setViewerActiveAnnotations.cpp关闭面板后控制台流零开销恢复时结转被清空避免把暂停前的旧字节与之后的字节拼接出线上从未出现过的帧。热路径与线程影响按 plan.md 的 Hotpath threading impact 一节管线线程零影响。Console::Handler本就在 GUI 线程按块节奏接收原始字节解码器在同一线程于看门狗下运行模型增长全程有界注解、文本、字节、结转四者皆受限。规格的 Constraints 进一步限定帧路径FrameReader/FrameBuilder零改动解码器消费的是终端本就在收的原始字节流ConnectionManager::onRawDataReceived路径脚本执行沿用既有护栏JsScriptEngine::guardedCall、Lua Safe/Fast 模式 看门狗不引入新引擎类型。面板的四个视图与状态机ConsoleAnnotations.qmlapp/qml/Widgets/Dashboard/ConsoleAnnotations.qml是全部交互入口轨道条track strip每个声明行一条车道覆盖字节窗口跨度按偏移比例绘制显示最长可容纳文本maxTrackSpans 4096。悬浮可看运行段的{start, end, cls, count, color, text, shortText}trackSpans。表格 CSVAnnotationFilter代理按行/类过滤-1 全部按 Start 排序表格列 Start/End/Length/Row/Class/Text。选中某行可让终端滚动到对应字节规格 R6。注意AnnotationFilter::setSourceModel会监听layoutDeclared并在解码器重新声明布局时自动把两个过滤器复位为 -1避免 combo 已重建而代理仍按旧索引过滤导致表格空白的竞态Annotations.cpp。载荷视图选择某个类实时拼接该类的全部字节hex/文本切换支持复制/导出。解码器编辑器内嵌代码编辑、Apply/Enable/Clear 三操作可从内置模板起步。面板顶部还有一个状态交通灯ConsoleAnnotations.qml失败→alarm红、运行中→alarm_ok绿、已编译未启用→alarm_warning黄、无解码器→占位灰状态文案包括 Decoder error: %1、Decoding, N annotations、Paused, N annotations kept 等。字节偏移用toLocaleString(..., f, 0)格式化避免百万级偏移被显示成1.07e07。持久化项目副本 应用副本的双存储解码器代码与启用状态双写ConsoleAnnotations.qml项目副本经Cpp_JSON_ProjectModel.saveWidgetSetting(console, annotationDecoder, ...)随.ssproj旅行应用副本存于SettingscategoryConsoleAnnotations含currentTab、windowBytes、payloadHex等作为 Quick Plot / Console Only 模式项目存储为 no-op的回退。恢复时项目副本优先。这对应规划中解码器随项目分发经widgetSettings(console)无需 schema 改动的决策——同时解释了会话数据库不持久化注解、回放时重跑解码器的决策重跑更便宜且永远与解码器版本一致。规划中的关键决策plan.md 的 Decisions 表记录了规格中开放问题的最终取舍问题决策归属终端Console 工具——控制台下方面板ribbon 开关切换注解持久化否回放时重跑解码器控制台 feed 也会回放Pro 门槛无解码器分发随项目经widgetSettings(console)无 schema 改动Lua 解码器本切片仅 JSLua 跟进模型/API 语言中立VT100 网格覆盖层被按字节比例的轨道条取代终端渲染的是格式化文本而非字节单元逐单元覆盖需要一张不存在的 字节→单元 映射表错误处理禁用解码器 面板消息 qWarningProblem Center 检查器延后其中 VT100 覆盖层被否定的理由值得注意它从架构上解释了为何轨道条按字节窗口比例而非终端文本行布局。任务清单tasks.md显示 T1–T9 全部完成包括 Lua 解码器T7、Problem Center 检查器T8与两个内置示例解码器T9后者实际扩展为七个模板见下节。内置示例解码器与实战写法仓库 app/rcc/scripts/annotations 目录提供了 7 个可直接从编辑器模板加载的 JS 解码器line_framing.js— 应用自身的分隔符帧格式起始分隔符、载荷、校验和、结束分隔符modbus_rtu.js— Modbus RTU 帧mavlink_v2.js— MAVLink v2nmea_0183.js— NMEA 0183fixed_records.js— 固定长度记录cobs.js/slip.js— COBS 与 SLIP 字节填充协议以 modbus_rtu.js 为范例注意 RTU 的分帧依赖线缆空闲间隙而字节流已不再携带该信息因此解码器从功能码推导帧长decoder { rows: [frames, fields], classes: [ {name: address, color: #4e79a7}, {name: function, color: #59a14f}, {name: data, color: #76b7b2}, {name: CRC, color: #f28e2b}, {name: exception, color: #e15759} ], decode: function(bytes, offset, ctx) { const b new Uint8Array(bytes) const COUNTED [0x01, 0x02, 0x03, 0x04, 0x0C, 0x11, 0x14, 0x15, 0x17] let i 0 while (i 4 b.length) { const fn b[i 1] let total 8 // 请求帧恒为 8 字节 if ((fn 0x80) ! 0) total 5 // 异常响应 else if (COUNTED.indexOf(fn) 0) total 3 b[i 2] 2 // 带字节计数的响应 if (i total b.length) return i // 帧未完整结转到下一块 ctx.annotate(offset i, offset i, 0, 0, [addr b[i], String(b[i])]) ctx.annotate(offset i 1, offset i 1, 1, (fn 0x80) ! 0 ? 4 : 1, [(fn 0x80) ! 0 ? exception : fn fn, String(fn 0x7F)]) if (total 4) ctx.annotate(offset i 2, offset i total - 3, 1, 2, [(total - 4) bytes, D]) ctx.annotate(offset i total - 2, offset i total - 1, 0, 3, [CRC, C]) i total } return i } }值得借鉴的写法前缀长度不足时return i已消费部分而非返回 0让未完整帧留在结转缓冲等待下块注释文本按完整描述 → 短标签两级给出decode内部用Uint8Array包装后线性扫描不做逐字节的 QML/引擎往返。测试与验收规格中的验收标准spec.md全部勾选测试实现于 tst_console_annotations.cpp覆盖 plan.md 列出的驻留界、保留窗口裁剪、容量裁剪、过滤 CSV、载荷提取、带结转的 JS 解码器、抛异常禁用、布局校验。验收要点AC1— 应用自带分隔符帧格式的示例解码器在默认 UART 速率下实时标注起始符/载荷/校验和/结束符四类、两行无可见终端卡顿AC2— 1M 条注解、8 个不同文本时驻留表仅 8 个字符串内存由终端保留窗口约束AC3— 表格排序/过滤/导出往返为可在电子表格重开的 CSVAC4— payload 类的载荷视图对已知采集逐字节还原拼接载荷AC5— 每块都抛异常的解码器产生一条发现和禁用状态而非对话框风暴。使用与验证路径小结在运行中的应用内验证打开控制台 → ribbon 切换注解面板 → 从模板选择或粘贴解码器代码 → Apply → Enable随后轨道条、表格与载荷视图随流更新。建议结合仓库以下文件深入阅读规格与决策spec.md、plan.md、tasks.md核心实现Annotations.h、Annotations.cpp界面实现ConsoleAnnotations.qml示例解码器modbus_rtu.js、line_framing.js、mavlink_v2.js测试tst_console_annotations.cpp说明本特性当前切片仅支持 JS 解码器规格明确模型/API 语言中立Lua 作为跟进项已列入任务清单T7。注解层是终端侧特性运行于块节奏chunk cadence从不进入帧管线的逐字节热路径。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表