Serial Studio 术语全解:从采集管线到数据集身份模型的权威概念手册
【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio
Serial Studio 的文档、界面与 API 中反复出现大量领域术语——Frame、Dataset、uniqueId、Acquisition pipeline、Operation mode等,理解这些概念是高效使用遥测仪表盘、编写解析脚本与对接 API 的前提。本文以 Glossary.md 为骨架,逐条展开每个术语的定义、作用与底层实现,并给出对应的仓库源码与测试佐证,帮助读者建立从"原始字节"到"仪表盘控件"的完整概念地图。
数据与身份:从 Frame 到 Dataset
Frame(帧)
Frame是设备发来的一"份"完整数据单元,其边界由帧检测设置决定。Serial Studio 将每一帧解析为若干 dataset 值并刷新仪表盘。帧是数据流的最小逻辑单位——一次驱动接收的 chunk 可能包含多帧,而每一帧都会进入解析管线。完整的数据路径见 Data Flow。
从源码结构看,帧由FrameBuilder(FrameBuilder.h)填充为结构化的Frame对象,该对象是每个数据源复用的暂存缓冲,而不是直接发布给下游的对象——填充完成后其值会被拷贝进当前的DataBlock。这一设计对应了热路径文档中"每帧零分配"的性能目标,详见 Data-Hotpath.md。
Frame detection / Delimiters(帧检测 / 分隔符)
帧检测决定 Serial Studio 如何判定一帧在哪里结束、下一帧从哪里开始。支持四种模式:
- End Delimiter Only(仅结束分隔符):遇到结束标记即为一帧;
- Start Delimiter Only(仅起始分隔符):遇到起始标记开始累积;
- Start + End Delimiter(起始 + 结束分隔符):两者之间的内容为一帧;
- No Delimiters(无分隔符):按固定或逻辑方式切分。
对于基于行的 CSV 数据,结束分隔符通常是换行符。底层实现上,IO::FrameReader(FrameReader.h)在专用管线线程上扫描CircularBuffer寻找帧边界:单分隔符模式走 KMP 快速路径,多分隔符模式使用CircularBuffer::findFirstOfPatterns()单遍扫描(栈分配的PatInfo数组最多支持 8 种模式,不触碰堆)。帧提取后还可立即做校验和验证(XOR-8、MOD-256、CRC-8、CRC-16、CRC-16-MODBUS、CRC-16-CCITT、Fletcher-16、CRC-32、Adler-32)。相关测试见 tst_frame_delimiters.cpp 与 tst_frame_reader_modes.cpp。
Frame index(帧索引)
Frame index是字段在解析后帧中的1 基位置:CSV 行25.3,1013.2,42配三个数据集时,第一个数据集index = 1,第二个index = 2,第三个index = 3(索引0保留)。一个 dataset 从且仅从一个 frame index 读取值;多个 dataset 可以共享同一index,以便把同一个原始值以不同样式呈现。
注意index与身份无关——它描述的是"这一列在帧里的位置",由你在 Project Editor 中为每个 dataset 设置(编辑器会按顺序填充默认值,可覆盖)。在生成的数据集注册表 DatasetRegistry.h 中,Index被登记为可编辑属性(kDatasetView_Index),而datasetId、uniqueId、sourceId则是只读的展示字段(PropertyWidget::None),这从属性层面印证了index是"你来定"的配置项。
如果解析出的帧字段数少于某 dataset 的index要求,该 dataset 会保留上一帧的值而不是清空或报错——表现在界面上就是某个控件"冻结"了而其他控件继续更新。这是仪表盘元素停止更新最常见的排查点。
Dataset(数据集)
Dataset是单条命名数据通道,例如一个温度值或加速度计的一个轴。一个 dataset 将帧中的某个位置映射为一个值,并携带标题、单位、量程与所属控件(widget)。Dataset 必须位于Group内。
Dataset 在项目模型中被建模为DataModel::Dataset,其 CRUD 操作集中在 ProjectEntities.h(addDataset、updateDataset、deleteDataset、duplicateDataset、moveDataset等),所有变更入口都通过ProjectUndoScope提交到ProjectModel::setModified,保证撤销/重做契约。
Dataset ID / Unique ID(数据集 ID / 唯一 ID)—— 最易踩坑的术语
Glossary 明确指出:混淆index与datasetId、或混淆datasetId与uniqueId,是项目编辑中最常见的陷阱。一个 dataset 携带两到三个含义完全不同的标识符,详见 Identity-Model.md:
- Frame index:值在帧中的位置(见上文);
- Dataset ID(
datasetId):dataset 在其所属 Group 内的槽位。一个含 3 个 dataset 的组,datasetId = 0, 1, 2;一旦重排、插入或删除,datasetId会被重新分配以匹配新的数组下标。它用于变更型(mutating)API 调用; - Unique ID(
uniqueId):跨整个项目全局唯一、持久化在.ssproj项目文件中的整数标识,创建/复制/导入时从项目级计数器分配一次,重排、重命名都不会改变。它用于读取型操作:实时数据 API、datasetGetRaw(uniqueId)/datasetGetFinal(uniqueId)变换脚本、共享表raw:<uniqueId>/final:<uniqueId>变量。
此外还有可选的alias(别名):在 Project Editor 的 Script Alias 字段中设置的人类可读名称,要求项目内唯一,同样能跨重排存活,是脚本和 API 的第二个读取键——字符串参数一律视为别名,数字参数一律视为uniqueId,两者不会互相强转。Project Editor 树右键菜单提供Seed Aliases from Titles,可批量把空别名填充为去重后的 dataset 标题。
源码层面的证据:
- 变更型 API 的签名确实按
(groupId, datasetId)寻址,例如 ProjectEntities.h 的updateDataset(const int groupId, const int datasetId, ...)、deleteDataset(int groupId, int datasetId, ...); uniqueId由项目级分配器给出,ProjectMerge.h 中可见nextSourceId与nextUniqueId = 1的分配器字段;- 读取型访问按
uniqueId键控,DataTable.h 的getDatasetRaw(int uniqueId)/getDatasetFinal(int uniqueId)与registerDatasetAlias(const QString& alias, int uniqueId)直接印证; - Workspace 的控件引用持久化的是group 的
uniqueId,且 JSON 键名仍叫groupId——WorkspaceKeys.cpp 用 24 位存放 group uniqueId 编码进 64 位 key,因此当你读到远大于组数的"groupId"时,看到的是uniqueId而非位置索引。
记忆口诀(覆盖 95% 场景):
变更用
(groupId, datasetId),读取用uniqueId,定位用index。
生命周期要点:
datasetId随重排漂移,脚本中缓存后务必在每次变更后重新查询;uniqueId不随datasetId变化,唯一例外是复制/粘贴——副本会从计数器领到新uniqueId以便与原数据集区分;groupId随组重排漂移,需要稳定组标识时使用Group.uniqueId(project.group.list返回),project.group.get两种都能解析;index完全由你设置,帧解析器返回位置数组,你在 Project Editor 里为每个 dataset 指定读取哪个槽位。
Group(组)与 Folder(文件夹)
Group是容纳相关 dataset 的容器,并把它们映射到复合控件上,如 GPS 地图、3D 绘图或数据表格(Data Grid)。Group 构成项目树的主要层次。
Folder是 Project Editor 中可命名、可折叠的容器,用于组织组、共享表或工作区。Folder 可无限嵌套、纯属组织用途、在所有版本中都存在;一个只含组的叶子 Folder 还可以折叠成一个聚合的仪表盘工作区。详见 Project Editor 的文件夹组织。
Project file(.ssproj项目文件)
Project file是定义整个仪表盘的 JSON 文档:帧检测、组、数据集、控件、解析脚本与变换(transform)全部编码在内,由 Project Editor 创建和编辑。它以.ssproj为扩展名,是 Project File 操作模式(见下文)的输入。多源项目(Pro)中每个 source 携带自己的解析引擎,数据集按(sourceId, groupId, datasetId)层次寻址。
采集与解析:从字节流到数值
Acquisition pipeline(采集管线)
Acquisition pipeline是从"收到字节"到"仪表盘刷新"的性能关键路径,目标是以 256 kHz+ 的持续数据率运行且每帧零分配。管线各阶段(驱动 → FrameReader → 无锁队列 → PipelineHost 路由 → FrameBuilder 解析/变换 → DataBlock → 仪表盘与导出扇出)全部运行在专用管线线程上,只有驱动入队与 GUI 刷新两个跨线程跳变。详细原理见 Data-Hotpath.md,用户视角的高层数据流见 Data-Flow.md,线程保证见 Threading-and-Timing.md。
Driver(驱动)
Driver是与单一数据源对话的组件:UART、TCP/UDP、蓝牙 LE、MQTT、Modbus、CAN 总线、音频、USB、HID、Process I/O 等。驱动在数据源边界为每帧盖上到达时间戳——这是全管线时间戳的唯一所有者,下游所有组件只传播该时间戳,不在事后调用时钟。驱动通过HAL_Driver::publishReceivedData(...)发布数据,载荷IO::CapturedData携带data(QByteArray,写时复制)、timestamp(steady-clock)、frameStep(帧间隔纳秒)与logicalFramesHint。音频驱动是填充真实frameStep的典型例子,它把块起始时间回拨step * (totalFrames - 1),使每个采样样本对齐真实采集时刻。详见 Data Sources。
Built-In parser(Native,内置解析器)
Built-In parser是"无代码"帧解析器:你用 JSON 描述符描述帧布局,Serial Studio 用参数化的 C++ 模板完成解析,无需 Lua 或 JavaScript。"Built-In" 是面向用户的名称,内部标识符称其为Native。它适合固定/简单布局的协议,复杂协议则可改用脚本解析器。参考 Frame Parser Reference。
Frame parser(帧解析器)
Frame parser是把原始帧变成有序值列表的逻辑,三种形态:
- Built-In(Native):参数化 C++ 模板,见上;
- JavaScript
parse(frame):运行在 Qt 的QJSEngine上; - Lua 等价实现:运行在嵌入式 LuaJIT 2.1 解释器上(Lua 5.1 语法 + 兼容垫片,加载
base、table、string、math、utf8、coroutine库)。
解析器每个数据源一个实例、绝不跨源共享,且在项目加载或连接打开时编译一次,之后被反复调用——编译成本前置,不计入每帧开销。解析器与变换是管线上仅有的两处用户代码运行点,两者都在运行时看门狗下执行,抛错、死循环或返回非有限值都会安全回退(原始值或空帧),不会中断数据流。参考 Frame Parser Scripting,测试见 tst_proto_parser.cpp 与 tst_lua_migration.cpp。
Value transform(数值变换)
Value transform是逐 dataset的脚本(JavaScript 或 Lua),在解析之后、显示之前对值进行校准、滤波或单位换算。每个定义了变换的 dataset 每帧调用一次transform(value),按组序、组内 dataset 序执行;变换可以读取任意 dataset 的原始值和更早顺序dataset 的最终值,还能读写项目共享表(Variables)中的计算变量。变换的 100 ms 看门狗预算按帧整体武装(JavaScript 场景下覆盖该帧全部变换,而非每次调用)。未声明info参数的变换零额外开销——引擎在编译期检查参数个数并跳过 info 构造。Lua 的顶层local与 JavaScript 的顶层var都会被隔离进各 dataset 的闭包,两份相同的 EMA 模板不会互相污染状态。详见 Dataset-Transforms.md。
Operation mode(操作模式)
Operation mode决定连接如何处理到达的数据,三种模式:
- Console Only(仅控制台):不解析,原始字节直接进控制台,适合确认设备在发数据、核对波特率与帧格式;
- Quick Plot(快速绘图):自动按逗号切分 CSV 并逐字段绘图,零配置;
- Project File(项目文件):用
.ssproj项目解析,完整走 FrameBuilder 管线。
Quick Plot 与 Console-Only 模式在管线上有专门分支:Quick Plot 用内置行切分器把逗号当作字段分隔符替代解码+解析两步;Console-Only 则让 FrameBuilder 阶段成为 no-op,字节经DeviceManager::rawDataReceived直达终端。见 Operation-Modes.md。
Console(控制台)与 Setup panel(设置面板)
Console是中央面板,在任何解析之前以 ASCII 或十六进制显示原始入站字节,也是 Console Only 模式的完整视图。它是确认设备发数、检查波特率与帧格式的第一站。
Setup panel是右侧可折叠面板,在此选择操作模式、配置 I/O 接口、设置帧解析选项并启用导出。
界面与交互:Dashboard、Widget 与事件
Dashboard(仪表盘)
Dashboard是实时控件的网格,一旦解析出至少一帧有效数据,它便取代 Console。控件布局来自项目文件(Quick Plot 模式下自动生成)。仪表盘仅在 GUI 线程上运行:它通过 SPSC 环形缓冲在Dashboard::onDisplayTick上直接消费池化的DataBlock,不做拷贝,以 UI 刷新率(默认 60 Hz,可在 1–240 Hz 间配置)限速视觉刷新——但每一帧仍会被解析和导出,限速只作用于画面。
Widget(控件)
Widget是仪表盘上的视觉元素:Plot(绘图)、Gauge(仪表)、Compass(罗盘)、GPS Map(GPS 地图)、Data Grid(数据表格)等。Dataset 和 Group 决定由哪个控件渲染它们;Pro 版额外提供输出控件、3D、Image View、Waterfall 等。完整的控件手册见 Widget-Reference.md。
Workspace(工作区)
Workspace是"哪些控件可见、如何布局"的已保存快照,让一个项目可以为不同任务呈现不同视图。Workspace 的控件引用持久化的是 group 的uniqueId(键名仍叫groupId,见上文命名陷阱),其 64 位 key 编码格式在 WorkspaceKeys.cpp 中定义。Workspace ID 使用独立保留区间(Overview = 1000、All Data = 1001、逐组工作区从 1002 起、用户工作区从 5000 起),因此永不与位置型groupId冲突。相关测试见 tst_workspace_rebind.cpp。
Annotation(注解)
Annotation是控制台流中带标签的字节区间,由注解解码器(annotation decoder)而非帧解析器产生。注解携带行(row)、类别(class)与文本(text),供控制台的 Track、Table 与 Payload 视图消费。见 Console-Annotations.md,测试见 tst_console_annotations.cpp。
Action(动作)
Action是用户自定义命令,绑定到仪表盘按钮或入站触发器。Action 可向设备回发字节、按定时器运行或在条件满足时触发。见 Actions.md。Action 的 CRUD 与 dataset/group 并列实现于 ProjectEntities.h(addAction、deleteAction、duplicateAction、moveAction)。
Macro(宏)
Macro是从 Macros 窗口针对运行中的应用执行的临时 JavaScript/Lua 脚本或单条命令。Macro 可以不经过 API 服务器、在进程内触达所有已注册的 API 命令,并且不绑定到某个项目。见 Macros.md。
Notification(通知)
Notification是仪表盘级事件——Info、Warning 或 Critical——可由帧解析器、dataset 变换、输出控件脚本、C++ 代码或 MCP API 发布。事件归组到自由格式的频道中,显示在 Notification Log 控件里,可选触发原生 OS 桌面通知。通知是Pro 功能。见 Notifications.md,测试见 tst_console_annotations.cpp 与 tst_notification_*。
Control Loop(控制循环)
Control Loop是可选的setup()/loop()脚本,与项目并行运行,用于驱动输出、自动化设备或计算派生值。见 Control-Script.md。
Problem Center(问题中心)
Problem Center是列出会话常驻诊断信息的窗口:项目错误、链路状况、脚本错误与失败扩展,每项都附原因(cause)与补救措施(remedy)。Connection Diagnostics在同一列表中加入机器自检(端口权限、蓝牙状态、主机可达性、音频访问)。见 Problem-Center.md。
扩展体系:Extension、Plugin 与版本
Extension(扩展)
Extension是扩展 Serial Studio 的附加组件:主题(theme)、帧解析器、项目模板或插件(plugin)。扩展从Extension Manager对话框浏览、安装、更新与卸载。见 Extensions.md。
Plugin(插件)
Plugin是通过 TCP/JSON 或 gRPC 连接 Serial Studio API 服务器的外部程序(Python 脚本或原生二进制),用于提供自定义的实时数据处理、可视化或分析。插件从 Extension Manager 安装,并用其 Run 按钮启动。插件 API 见 API-Reference.md 与 SerialStudio-SDK.md。
Pro / Free(专业版 / 免费版)
Serial Studio 提供免费 GPL 版本与商业 Pro 版本。Pro额外包含:输出控件、Modbus、CAN 总线、MDF4、3D、Image View、Waterfall、文件传输协议、Historian(历史记录)以及多源项目、通知等。免费构建会在编译期将 MDF4 导出、Historian 与 MQTT 桥接等 Pro sink 编译掉并在扇出中跳过。完整的特性对照见 Pro-vs-Free.md。
进一步阅读
原文档推荐的延伸路径,均位于 doc/help 目录下:
- Getting-Started.md——安装、连接并读取你的第一帧;
- Identity-Model.md——
index与 ID 之别的深度讲解; - Data-Flow.md——从原始字节到控件的完整路径;
- Data-Hotpath.md——采集管线的源码级性能设计;
- FAQ.md——常见问题。
附:术语速查表
| 术语 | 一句话定义 | 详细章节 |
|---|---|---|
| Frame | 设备发来的完整数据单元 | 帧 |
| Delimiters | 判定帧边界的四种模式 | 帧检测 |
| Frame index | 字段在帧内的 1 基位置 | 帧索引 |
| Dataset | 单条命名数据通道 | 数据集 |
| datasetId | 组内槽位,用于变更 | Dataset ID |
| uniqueId | 全局持久稳定标识,用于读取 | Dataset ID |
| Group | 承载相关 dataset 与复合控件 | 组与文件夹 |
.ssproj | 定义仪表盘的 JSON 项目文件 | 项目文件 |
| Acquisition pipeline | 256 kHz+ 零分配的字节→仪表盘链路 | 采集管线 |
| Operation mode | Console Only / Quick Plot / Project File | 操作模式 |
| Widget / Workspace | 仪表盘控件与布局快照 | 控件 / 工作区 |
| Extension / Plugin | 扩展与外部程序插件 | 扩展体系 |
【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考