Serial Studio 组级 Bar Panel 控件与 severity-first 告警可视化语言:Spec 0052 设计与源码实现全解析
【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio
本规范文档(doc/claude/specs/0052-bar-panel-alarm-visuals/spec.md,状态done,作者 Alex Spataru)定义了 Serial Studio 的一项核心仪表盘能力:新增组级 "Bar Panel" 控件,并为 Bar、Gauge、Meter、Bar Panel 四类仪表控件确立一套"严重级别优先"(severity-first)的告警可视化语言——让颜色只编码告警严重度这一件事。读完本文,你将掌握该规范的完整需求(R1–R10)、验收标准(AC1–AC9)、约束不变式,以及它如何在仓库的 QML 渲染层、C++ 数据模型与共享算法层落地实现,并能在项目编辑器中直接配置出多通道告警条面板。
1. 规范背景:为什么需要一种新的告警可视化语言
1.1 现场项目的 painter 脚本与"一眼可读"的诉求
规范的动机来自一个私有仓库的现场项目:团队用约 900 行用户 JavaScript 编写了一个 painter 脚本(同步面板 synoptic panel),实现多通道仪表盘——每行一个带标签的条形(bar),完整告警带结构以"静音区"(muted zone)形式显示在轨道(track)上,填充条取当前值所在 band 的颜色,仪表指针(needle)同样按 severity 重新着色。
这套方案的即时可读性(instantly legible)正是其价值所在:任何人不用读任何一个数字,扫一眼就能看出哪个通道正常(OK)、警示(cautioned)还是告警(alarmed)——因为颜色精确地只编码一个维度:严重级别。
1.2 内置控件的三个缺陷
规范明确指出 Serial Studio 当时的内置控件无法产出这种效果:
- 没有组级 bar 控件:监控 8 个通道意味着放 8 个独立 Bar 控件,每个都有各自的标题条、数值框、刻度尺——视觉重量大(heavy chrome),缺乏共享的视觉节奏,也没有紧凑的"耙式"(rake)视图。
- band 只是装饰而非信息:内置 Bar 把告警带画成读数边缘的细条,填充永远用数据集颜色;Gauge 和 Meter 行为类似。severity 只在闪烁的数值框中体现——band 结构没有被当作"信息本身"来呈现。
- 默认调色板循环造成信息噪声:默认数据集颜色按索引循环主题调色板,10 通道仪表盘就出现 10 种不相关色相。在仪表类控件上,这种彩虹色不携带任何信息,反而与唯一有意义的颜色轴(severity)相互竞争。规范特别指出:多序列曲线图(plot)是唯一 per-series 色相真正有区分价值的地方。
1.3 核心设计洞察
颜色只编码一个轴(Color encodes one axis)——在仪表类控件上,色相变化必须只表示严重级别;任何装饰性的多色渲染都视为违反本规范的缺陷。
这一约束写进了规范的 "Constraints & Invariants" 章节,也是整篇规范的设计哲学核心。
2. 设计目标与非目标
2.1 九大目标(Goals)
规范定义了以下设计目标:
- 组级 Bar Panel:用户可在组(group)上放置一个组级控件 "Bar Panel",为每个数据集渲染一条带标签的 bar——标签、band 分区轨道、severity 着色填充、实时数值一应俱全,与现场项目 painter 面板效果相当,且零脚本。
- 双向渲染:Bar Panel 既支持横向行(synoptic panel 风格),也支持纵向列(EGT-rake 风格),可在项目编辑器中切换,并提供自动默认值。
- band 始终完整可见:在 Bar、Gauge、Meter 和 Bar Panel 上,已定义的告警带始终以完整形态显示(静音轨道分区 / 环形弧段),活动指示器(填充、指针)取活动 band 的 severity 颜色——OK 色也包含在内,"确认正常"是一个正向信号,而非"没有告警信号"。
- 非 plot 控件单一默认色:非绘图控件默认使用单一强调色(主题派生),不再按索引循环调色板;显式的 per-dataset 颜色覆盖在任意位置继续生效。绘图类控件(Plot、MultiPlot、FFT、Plot3D、Waterfall)保留多色循环。
- 超范围钳制:值落在所有已定义 band 之外时,按最近 band 的 severity 渲染,绝不出现未分类/中性状态(超量程必须读作"危急"而非"平静")。
- 极值保持标记:数据集可选择性开启 extreme-hold 标记:控件显示自上次数据重置以来观测到的最高/最低值在刻度上的位置(对应 painter 的振动峰值保持刻度,无需固件/脚本支持)。
2.2 明确的非目标(Non-Goals)
规范严格划定了边界,防止范围蔓延:
- 不引入新的告警 band 数据模型——数据集已携带
alarmBands(severity、color、label、blink),本规范只改变渲染方式。 - 不改变告警通知行为(central monitor、冷却、LED 闪烁语义)。
- 不改变绘图类控件的渲染与颜色分配。
- 不迁移、不重写现有项目中的 per-dataset 颜色覆盖。
- 不是 painter 的替代品:不支持自由布局、面板内表盘组合、自定义页眉;需要完整同步面板的用户继续使用 painter。
- 不支持外部馈入的保持标记(painter 的表格馈入 pk-hold 仍是 painter 能力);控件选项只跟踪自身值流;不支持衰减/窗口化峰值模式。
- 不改变 LED Panel 渲染(其 band 消费已编码 severity)。
3. 需求规格逐条解读(R1–R10)
3.1 R1–R2:Bar Panel 控件与方向选择
- R1 — Bar Panel 控件:新增组级仪表盘控件,在项目编辑器中可为任意组选择;为组内每个(可见、有数值范围的)数据集渲染一条 bar,含数据集标题、显示数据集完整范围的轨道、以及用数据集单位/小数位设置格式化的实时数值。
- R2 — 方向(Orientation):项目编辑器提供样式选项,取值为 Auto / Horizontal / Vertical。Auto(默认)根据控件宽高比和通道数自动选择方向;显式选择则强制生效。设置持久化保存到项目文件中。
从源码看,方向判定实现在 BarPanel.qml:styleMode为"vertical"时强制纵向、"horizontal"时强制横向;Auto 模式仅在"面板明显比高宽(width > height * 1.2)且每个通道能分到至少 64px 列宽"时才选纵向列,否则走横向行。持久化字段是 Frame.h 中 Group 结构体的QString barPanelStyle("" = auto | horizontal | vertical),并在项目 JSON 序列化中以BarPanelStyle键存取(Frame.cpp)。
3.2 R3–R5:band 可视化与超范围钳制
- R3 — 轨道上的 band:在 Bar Panel、Bar、Gauge、Meter 上,每个已定义告警带都沿刻度全范围绘制为静音区(轨道分段或环形弧段),颜色取自 band 的 severity 颜色(若设置了自定义颜色则用自定义色),与当前值无关、始终可见。
- R4 — severity 着色指示器:同样在这四类控件上,定义了 band 时,数值指示器(bar 填充 / 指针)始终使用活动 band 的 severity 颜色——OK 也不例外。数值文本在 OK 时保持中性色,在 warning 及以上级别取 severity 颜色(与 painter 的文本纪律一致)。
- R5 — 超范围钳制到最近 band:定义了 band 且值落在所有 band 之外时,指示器和文本取最近 band 的 severity。
R3/R4 的 QML 实现可对照 BarPanel.qml 的三个颜色函数:fillColor()在severity >= 0时返回Cpp_ThemeManager.alarmColorForSeverity(severity),否则回落到数据集强调色;valueColor()在severity >= 2(warning 及以上)时用告警色,否则用主题widget_text;bandColor()优先返回 band 自定义色。C++ 侧severity()访问器(BarPanel.h)逐行暴露活动 severity(-1= 无 band 或未知行)。
3.3 R6–R7:单一默认颜色与无 band 降级
- R6 — 单一默认颜色:无显式颜色覆盖时,所有非 plot 控件使用一个共享强调色(主题派生),取代按索引循环的调色板;显式覆盖不受影响。绘图类控件保留调色板循环。
- R7 — 无 band 无噪声:没有告警带的数据集渲染为普通单色控件——默认强调色(或覆盖色)填充,无分区、无 severity 逻辑。
R6 在 C++ 侧体现为rowColor()(BarPanel.cpp):通过UI::SerialStudioHelpers::getDatasetAccentColor(dataset)解析——显式覆盖优先,否则取主题默认强调色,且"按需解析"保证主题切换时 QML 绑定会重新求值。
3.4 R8–R9:主题保真与免费层级
- R8 — 主题保真:所有新渲染颜色派生自活动主题(severity 颜色通过既有主题告警色查找),深浅主题均可读,无需在项目中为各主题写特例。
- R9 — 免费层级:Bar Panel 与全部渲染变更在 GPL 构建中可用(不设付费门槛)。
QML 中所有颜色均取自Cpp_ThemeManager.colors[...]与alarmColorForSeverity(...),没有硬编码十六进制(规范约束明确禁止 QML 中出现超出派生混合的硬编码 hex),这正是 R8 的落地方式。
3.5 R10:极值保持标记(Extreme-Hold Markers)
数据集级选项(默认关闭),使 Bar、Gauge、Meter 和 Bar Panel 显示两个保持标记:一个在自上次数据重置(连接启动、仪表盘数据重置或回放重启)以来观测到的最大值处,一个在最小值处。标记无限期保持、不衰减,数据重置时清除。该选项持久化到项目文件。
数据模型层由 Frame.h 中Dataset::extremeHold布尔字段承载;渲染层在 BarPanel.qml(横向)与 BarPanel.qml(纵向)中通过minSeenFrac/maxSeenFrac/hasExtremes三个访问器画 2px 宽、横穿轨道的标记线,仅在hasExtremes为真时可见。极值数据本身的跟踪见下文 4.5 节。
4. 源码级实现剖析
4.1 数据模型层:alarmBands / extremeHold / barPanelStyle
severity 语义在 Frame.h 中以枚举定义:
enum class AlarmSeverity : quint8 { Info = 0, ///< Informational; never raises a notification Ok = 1, ///< Healthy / operating range; never raises a notification Warning = 2, ///< Out-of-normal; raises a Warning notification on entry Critical = 3 ///< Out-of-safe; raises a Critical notification on entry };每个告警带由 Frame.h 的AlarmBand结构体描述:min/max(含边界)、severity、blink(活动时闪烁指示器)、color(可选 hex 覆盖,空 = 用 severity 默认色)、label(可选,用于通知)。数据集携带std::vector<AlarmBand> alarmBands(Frame.h)。项目 JSON 解析兼容 canonical 与 v3.3 遗留两种字段形式(Frame.cpp),保证旧项目无缝加载。
4.2 共享告警带查找:Widgets::Bands
这是本规范的算法核心——一个纯头文件、被 Bar 与 BarPanel 共享的告警带查找工具 WidgetBands.h(命名空间Widgets::Bands),文档注释明确写着 "Header-only alarm-band lookup shared by the instrument widgets (spec 0052)"。它提供四个模板函数:
indexFor(bands, value, hint)(WidgetBands.h):返回包含 value 的 band 下标,hint为上次命中的 band,作为重复查找快速路径先测一次([[likely]]分支),miss 再线性扫描;边界含入。nearestIndex(bands, value)(WidgetBands.h):返回距离 value 最近的 band;距离定义为到 band 区间的最大越界距离max({0, min-value, value-max})。activeIndex(bands, value, hint)(WidgetBands.h):先 containment 命中,miss 且存在 band 时钳制到最近 band——这就是 R5 "超范围钳制"的算法实现。reportedSeverity(bands, activeIndex, hasData)(WidgetBands.h):仅在已有数据样本时才报告 severity,避免首字节前的占位值 0.0 在 band 高于零的项目上"永远告警"(注释引用 spec 0075 N3)。
Bar 控件在每帧数据更新时调用Bands::activeIndex(m_bands, value, m_lastBandHint)(Bar.cpp),并通过Bands::reportedSeverity派生告警状态(Bar.cpp);BarPanel 则在行刷新时同样经bandIndexFor+nearestBandIndex走同一套逻辑(见下节)。
4.3 BarPanel C++ 后端:行快照、severity 钳制与 revision 驱动
BarPanel.h 以QQuickItem暴露给 QML,属性包括count、styleMode、titles、units、ranged、bands、widgets以及单调递增的revision变更计数器;行级标量通过Q_INVOKABLE访问器(frac、severity、isNumeric、valueText、rowColor、hasExtremes、minSeenFrac、maxSeenFrac)暴露。内部维护每行结构Row(BarPanel.h):ranged(范围是否非退化)、extremeHold、hint(band 查找快速路径)、uniqueId、decimals、min/max 与std::vector<RowBand>。
两个关键流程:
buildRows()(BarPanel.cpp):从所属组数据集构建静态行快照。注意几个工程细节:ranged要求max > min且范围有限(std::isfinite);每个 band 先钳制到控件范围(qBound(row.min, ...))再归一化为fracMin/fracMax;band 表以QVariantMap形式(含customColor)一次性推给 QML。小数位数由rowDecimals()(BarPanel.cpp)决定:显式decimalPoints优先(封顶 6 位),否则按范围跨度自适应——跨度 ≥100 取 0 位、≥10 取 1 位、否则 2 位。
refreshRow()(BarPanel.cpp):每 tick 由updateData()调用(仅当控件isEnabled() && isVisible()时,隐藏面板整体跳过、itemChange()在下次显示时刷新)。数值样本才参与 band 查找:先bandIndexFor命中,未命中且存在 band 时nearestBandIndex钳制,随后把活动 band 的 severity 写入m_severities——这正是 R5/R4 的每帧落地。非数值但有数据的行取severity = band 空 ? -1 : 2(warning)。最后比较全行状态是否变化,变化才++m_revision并发出updated()。
resetData()(BarPanel.cpp):数据重置(连接/回放重启)时清空数值、极值与 severity,数值文本回落到"--"——对应 R10 的"标记在数据重置时清除"。
4.4 QML 渲染:横向 rake 与纵向 EGT 两种形态
BarPanel.qml 是一个约 600 行的自包含控件,结构清晰:
- 横向行模式(BarPanel.qml):
Flickable+ColumnLayout+Repeater,每行由标签(labelWidth固定、超长 elide + ToolTip)、凹槽轨道、数值框和弹窗按钮组成。 - 纵向列模式(BarPanel.qml):
RowLayout均分列宽(Layout.minimumWidth: 64、preferredWidth: 72),每列数值在上、轨道居中、标签在下。
渲染层几个值得注意的视觉工程细节:
- band 静音区:横向每条 band 是一个
opacity: 0.32的色块,x/width由fracMin/fracMax归一化坐标换算(BarPanel.qml);纵向则是(1 - fracMax)起算的垂直色块(BarPanel.qml)。透明度 0.32 正是"muted zone"——band 结构常驻可见但不过度抢眼。 - 凹槽质感:轨道上覆盖"上边缘衰减 + 下边缘高光"的 recess 渐变(横向 L233-L245,纵向 L492-L505),填充条上叠加圆柱光泽,使轨道读作"凹陷的井"而非色带。
- 填充动画:
frac变化带SpringAnimation { spring: 4.5; damping: 0.4 }弹簧动画,数值跳动平滑。 - revision 驱动的绑定收敛:每行绑定以
(root.rev, ...)作为唯一 notify 依赖,行状态经模型标量访问器读取,一次 tick 只转换少量标量而非整个列表——这是规范 "Display-only change、无新增每帧分配" 约束在 QML 侧的直接体现。 - 弹窗按钮:
DatasetWidgetButtons显示该数据集在仪表盘中其他控件(plot、gauge 等)的弹窗入口,数据由datasetWidgetLinks()(BarPanel.cpp 中调用)提供。
4.5 极值跟踪:DashboardIngest 的 datasetExtremes
R10 的极值不是由 BarPanel 自行跟踪,而是由仪表盘数据摄取层集中维护:DashboardIngest持有m_datasetExtremes映射(DashboardIngest.cpp),在块状数据摄入时按数据集uniqueId更新(DashboardIngest.cpp):
slot.min = slot.valid ? qMin(slot.min, value) : value; slot.max = slot.valid ? qMax(slot.max, value) : value; slot.valid = true;非有限值(NaN/Inf)被跳过。BarPanel 通过m_dashboard.datasetExtremes(row.uniqueId)读取并归一化为minSeenFrac/maxSeenFrac(BarPanel.cpp)。这种"摄取层集中跟踪、控件层按需读取"的架构,保证同一数据集在 Bar、Gauge、Meter、Bar Panel 上显示一致的极值标记。
5. 测试与验收闭环
5.1 单元测试:油压阶梯用例
app/tests/tst_bar_bands.cpp 直接以 "spec 0052" 为注释主题,用一组油压阶梯band 覆盖共享查找的全部语义(tst_bar_bands.cpp):
return { { 0, 25, 3}, // critical {25, 55, 2}, // warning {55, 75, 1}, // ok {75, 80, 2}, // warning {80, 150, 3}, // critical };七个测试槽覆盖:containment 命中(indexFor各段返回正确下标)、hint 快速路径不改变答案、边界含入、gap 值钳制到最近 band、超范围钳制到最外层 band、空列表保持未分类、activeIndex优先 containment 而非距离。这套测试是 R5 与"超量程必须读作危急"语义的可执行证明。
5.2 九项验收标准(AC1–AC9)
规范在 "Acceptance Criteria" 中给出完整验收清单(状态注记于 2026-08-12:实现已代码完成、静态门禁全部通过,验收需重建二进制后由维护者执行 ctest/pytest 与可视化/基准检查):
- AC1:项目编辑器中组可分配 Bar Panel 控件;仪表盘每个数据集显示一条带实时值的标签 bar(
dashboard.getDataAPI 可见该组控件)。 - AC2:Auto/Horizontal/Vertical 样式切换即时重渲染,且项目保存/重载后保留(pytest 集成测试含项目 JSON 往返)。
- AC3:数据集定义 OK/warning/critical band 后,四类控件始终显示全部 band 为静音区,填充/指针颜色随值跨越 band 边界按 ok→warning→critical 过渡。
- AC4:馈入最外层 band 之外的值,指示器渲染为最近 band 的 severity 色而非中性色。
- AC5:全新项目多数据集无颜色覆盖时,非 plot 控件渲染为单一强调色,而 MultiPlot 仍循环色相;显式覆盖的数据集显示覆盖色。
- AC6:现有项目加载后数据集数据不变、无项目文件 schema 错误、存储的颜色覆盖照旧渲染(pytest 项目往返套件保持绿色)。
- AC7:
--benchmark-hotpath门禁保持绿色——渲染变更在 GUI 侧,不向帧管线增加任何负担。 - AC8:两种方向在小尺寸控件下仍可读(标签先省略后丢弃、不重叠;维护者在浅/深主题下做视觉检查)。
- AC9:开启 extreme-hold 后,先升后降驱动值,max 标记停在峰值、min 标记停在谷值;数据重置清除两者;选项关闭时不渲染任何标记(项目 JSON 往返覆盖持久化)。
5.3 性能与兼容性门槛
AC7 反映规范的核心性能约束:所有 band 几何可在(重新)配置时预计算,每 tick 的工作量受可见通道数约束;帧管线无新增信号、无新增分配。BarPanel 的 revision 机制(一次 tick 仅当行状态变化才发信号)与 hidden 面板跳过更新(BarPanel.cpp)正是为此设计的。
6. 约束与不变式
规范在 "Constraints & Invariants" 中冻结了如下边界:
- 颜色编码一个轴:仪表控件上色相变化必须只表示 severity;任何装饰性多色渲染都是缺陷。
- 仅显示层变更:帧管线零新增(见 5.3)。
- 数据模型与既有 project-key 语义冻结:新增持久化状态仅限 Bar Panel 的 widget/style 设置与数据集级 extreme-hold 选项(增量式,缺失 = 关闭,旧项目加载不变)。
- 告警通知行为不变:central monitor 语义不变,无 per-widget 通知投递。
- ProjectFile 模式必须工作;QuickPlot/DeviceDefined 模式不得回归(它们本就不以同样方式配置 band/组)。
- 深浅主题均可读:QML 中除派生混合外无硬编码 hex;无新增依赖。
规范的 "Open Questions" 章节标注为None——方向 UX(编辑器选项、Auto 默认)、OK 态着色(始终 severity 色)、层级(免费)、命名(Bar Panel)与 extreme-hold 语义(max+min 标记、保持到数据重置、不衰减)均于 2026-08-12 与维护者敲定。
7. 在项目中实际使用 Bar Panel
7.1 项目编辑器配置
在项目编辑器中将某组的控件类型设为 Bar Panel(相关编辑器命令与注册见 app/rcc/commands/projecteditor.json 与 app/qml/Commands/ProjectEditorMenuBindings.qml,类型枚举SerialStudio::DashboardBarPanel由 BarPanel.cpp 校验)。随后在样式选项中选 Auto / Horizontal / Vertical——Auto 按控件宽高比与通道数自动决定:明显宽于高且每通道 ≥64px 时用纵向列,否则横向行(BarPanel.qml)。
7.2 数据集告警带定义
为组内每个数据集定义告警带(OK / warning / critical),对应AlarmBand的四个字段:范围min–max、severity(Info=0 / Ok=1 / Warning=2 / Critical=3)、可选自定义颜色、可选 blink 与 label。控件范围内与带无关的数值范围由数据集的wgtMin/wgtMax决定(BarPanel.cpp)。
7.3 颜色覆盖规则
- 数据集未设显式颜色且无 band → 非 plot 控件用主题单一强调色(R6)。
- 数据集设了显式颜色 → 覆盖始终生效(R6,
rowColor()经getDatasetAccentColor(dataset)解析,BarPanel.cpp)。 - 数据集定义了 band → 填充色取活动 band 的 severity 色(R4,
fillColor()优先alarmColorForSeverity);数值文本在 warning(severity ≥ 2)及以上取 severity 色、OK 保持中性(R4,valueColor())。 - 值越出全部 band → 按最近 band 渲染(R5,
nearestBandIndex)。 - 无任何 band → 普通单色控件(R7)。
7.4 开启极值保持标记
在数据集级选项开启 extreme-hold(默认关闭,Dataset::extremeHold,Frame.h)后,Bar、Gauge、Meter、Bar Panel 会在轨道上显示最高/最低观测值的保持标记线;数据重置(连接启动、仪表盘数据重置、回放重启)时清除(R10)。旧项目未设置该字段时按关闭处理,加载不受影响。
8. 进一步阅读
- 规范原文:doc/claude/specs/0052-bar-panel-alarm-visuals/spec.md
- 共享 band 查找算法(纯头文件):core/Ui/UI/WidgetBands.h
- BarPanel C++ 后端:core/Ui/UI/Widgets/BarPanel.h、core/Ui/UI/Widgets/BarPanel.cpp
- BarPanel QML 渲染:app/qml/Widgets/Dashboard/BarPanel.qml
- 数据模型(AlarmSeverity / AlarmBand / Dataset / Group):core/Core/DataModel/Frame.h
- 极值跟踪:core/Ui/UI/Dashboard/DashboardIngest.cpp
- 单元测试:app/tests/tst_bar_bands.cpp
- 同类视觉语言在 Bar 与 LED Panel 的应用:core/Ui/UI/Widgets/Bar.cpp、core/Ui/UI/Widgets/LEDPanel.cpp
【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考