Serial Studio Widget 扩展开发完全指南:包结构、清单参考与 QML 数据模型 📅 发布时间:2026/9/18 18:05:14 👁 浏览次数: Serial Studio Widget 扩展开发完全指南包结构、清单参考与 QML 数据模型【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本篇指南以 Serial Studio 官方文档 Widget-Extension-Development.md 为核心结合仓库内真实样例examples/widget-extension、内置扩展app/rcc/extensions/widget与源码实现系统讲解如何为 Serial Studio 编写不重新编译应用即可运行的仪表盘组件扩展widget extension。读完本文你将掌握 widget 扩展包的结构与info.json清单全部字段、四个必备 QML 属性与ExtensionDataModel数据模型、声明式配置表单的用法以及安装、测试、故障排查与分发的完整流程。什么是 Widget 扩展Widget 扩展是一种类型为widget的扩展包一份元数据info.json、一个 QML 文件外加一组声明的设置项。安装后它会出现在项目编辑器Project Editor中它声明所属实体类型entity kind的 Widget 列表中并在仪表盘上像内置组件一样工作——拥有同样的画布、标题栏、工作区workspaces、冻结模式freeze mode与弹出窗口pop-out windows能力不需要重新构建 Serial Studio。Serial Studio 自带的两个组件Compass与Data Grid正是以这种方式构建的。它们随应用一起打包bundled而非由用户安装但使用的就是本文描述的同一套包格式。其清单与 QML 源码分别位于 Compass.info.json、Compass.qml 与 app/rcc/extensions/widget/datagrid 目录下是学习真实生产级扩展的绝佳范本。需要特别说明的是Widget 扩展不是 Pro 专属功能它在 GPL 构建版与商业构建版中都能加载。同时一个扩展包永远无法成为、替换或解锁某个 Pro 组件——内置组件的标识符是被保留的任何声称占用内置标识符的包都会被拒绝详见下文「保留标识符」。信任模型Trust ModelWidget 扩展以与 Serial Studio 本身相同的权限运行在应用内部。它的 QML 与应用程序共享同一个 QML 引擎因此能够访问应用程序可访问的同一文件系统、同一网络与同一运行时。整个设计没有任何沙箱、能力边界或权限列表任何声称扩展被隔离的文档都是错误的。这一点在 widget-manifest.json 模式文件 的文档注释中也被明确重申「Extension widgets run with the same privileges as the application itself; nothing here isolates them.」Serial Studio 采用的替代方案是询问。在你安装的包首次运行之前会弹出一个对话框说明该包是什么、由谁发布、它在磁盘上的位置以及它将以应用程序的权限运行。拒绝运行会让包保持已安装但惰性inert状态该决定按包版本记录因此更新后需要再次询问。随应用捆绑的包则豁免此流程。从源码可以印证这套「默认拒绝default-deny」的同意门控consent gate机制实现在 WidgetExtensions.cpp 中consentRequired(id)随应用捆绑的包豁免用户安装的包一律需要同意package.isValid() !package.bundledconsentGranted(id)同意记录按「id 版本」存储因此更新包后旧同意自动失效、需要重新询问canInstantiate(id)widget 允许执行的唯一判定——必须已注册且要么随应用捆绑、要么已对当前版本显式同意「default-deny 才是关键」源码注释原文。安装 Widget 包的方式应与安装任何程序一致从你信任的来源安装。包结构Package Structure一个最小可用的 widget 扩展包只需两个文件com.example.level-bar/ info.json LevelBar.qml源码树中 examples/widget-extension 目录提供了一份带注释、可直接复制的完整样例包含info.json、LevelBar.qml与README.md是开始开发的最佳起点。安装后的包位于**工作区文件夹workspace folder**中、应用包之外因此升级或移动 Serial Studio 不会影响已安装的扩展~/Documents/Serial Studio/Extensions/widget/com.example.level-bar/注意安装路径中的widget目录段来自包类型本地包类型决定安装位置而不是清单中的type字段单独决定这也是 catalog.json 模式 中特别强调的规则。info.json 清单参考完整的清单示例与源码树样例 info.json 一致{ id: com.example.level-bar, type: widget, title: Level Bar, description: A horizontal level bar for a single numeric dataset., author: Your Name, version: 1.0.0, license: MIT, category: Instruments, files: [info.json, LevelBar.qml], widget: { apiVersion: 1.0, hostCompat: 1.0 2.0, scope: dataset, qml: LevelBar.qml, icon: widgets/bar, readsStringValues: false, accepts: { datasets: { min: 1, max: 1 }, value: numeric }, defaultSize: { width: 360, height: 120 }, config: [], dependencies: { required: [], optional: [] }, experimental: false } }widget块之外的键是 Extensions.md 中描述的标准扩展元数据widget块是本类型专有的。各字段语义如下Key必填说明apiVersion否包编写时依据的清单格式版本。若包声明的主版本号比宿主更新则该包不会被加载。模式要求形如^[0-9](\.[0-9])?$。hostCompat否包支持的宿主 widget-API 范围以空格分隔的比较符表示1.0 2.0。省略或*表示任意版本。scope是dataset或group决定该包出现在哪个 Widget 列表中。qml是入口文件相对于包文件夹的路径不得指向包文件夹之外模式^(?!.*\.\.)[^/\\][^\\]*$直接拒绝..相对路径。icon否内置图标标识符如widgets/bar或包内文件如icon.svg。accepts否datasets.min/datasets.max约束数据集数量模式约束为 0–4096 的整数value取值numeric、string或any。不匹配的实体不会被提供该组件。readsStringValues否当组件渲染文本值而非数字时设为true。Serial Studio 只向主动请求字符串值的组件推送字符串值。defaultSize否弹出窗口的宽高像素模式约束为 48–8192。config否声明的设置项详见「配置设置」一节。dependencies否本包依赖的其他扩展包。必需依赖缺失会使组件停止加载并上报可选依赖缺失仅上报。每个依赖条目为{ id: ..., version: 范围 }version复用hostCompat的比较符语法。experimental否标记该包为进行中work in progress。关于清单验证的边界条件模式文件还给出了一些值得注意的细节id模式为^[A-Za-z0-9][A-Za-z0-9._-]*$1–128 字符由于id也是安装目录的一个路径段该模式顺带排除了分隔符与父路径引用config数组最多 128 项每项id须匹配^[A-Za-z_][A-Za-z0-9_]*$1–64 字符dependencies中required/optional各自最多 32 项widget块设置了additionalProperties: false未知键会被拒绝。id是项目 group/dataset 的widget字段中存储的值因此跨版本必须保持稳定。请使用反向域名标识符reverse-domain identifier内置组件的标识符bar、gauge、compass、datagrid、plot3d等是保留的会被拒绝。完整的保留列表可在模式文件的reservedId定义中看到包含map、gps、gyro、multiplot、accelerometer、image、painter、webview、barpanel、terminal、clock、stopwatch、notification-log、led-panel、meter等 20 个字符串。唯一的例外是随应用捆绑的包通过replaces键声明替代某个内置标识符宿主会拒绝任何从磁盘加载的包使用replaces。清单解析器的行为有专门的测试覆盖见 tst_widget_manifest.cpp包括版本范围语法versionInRange_data中的各种 KAT 用例、「无法解析的版本按失败关闭fails closed而不是当作任意版本」以及保留标识符拒绝错误码widget-id-reserved与 API 主版本不匹配拒绝错误码widget-api-version。Widget QML 文件入口文件必须声明四个必备属性Serial Studio 在创建组件时会将它们全部注入import QtQuick import QtQuick.Controls import SerialStudio Item { id: root required property color color required property string widgetId required property Item windowRoot required property ExtensionDataModel model Label { anchors.centerIn: parent color: root.color text: model.title : model.text } }属性承载内容model实时数据见下一节。color该组件在仪表盘上的强调色accent colour。windowRoot组件所在的窗口供对话框与弹出层使用。widgetId组件的持久化键persistence key。这四个属性的注入发生在 WidgetDelegate.qml 的buildWidget()中对扩展组件调用dashboardWidget.createExtensionItem(loader, { model: …, windowRoot: …, color: …, widgetId: … })对内置组件则用Qt.createComponentcreateObject传入同一组参数——这正是扩展组件与内置组件在画布上「平起平坐」的机制。导入面Import surface。一个包可以导入QtQuick、QtQuick.Controls与SerialStudio。Serial Studio 自身的 QML 组件被编译进应用程序无法从包文件夹导入你需要的一切其他内容都必须随包携带。扩展没有组件库component library。注意一个细节虽然文档给出的最小示例只用了三个模块但内置扩展如 Compass还会额外导入QtQuick.Shapes与QtQuick.Effects等标准 Qt Quick 模块——只要属于 Qt 标准模块就可以自由使用限制的只是应用内部编译的组件。Compass 中还大量使用了Cpp_ThemeManager.colors[...]与Cpp_Misc_CommonFonts等 SerialStudio 命名空间下暴露给 QML 的 C 单例这说明SerialStudio导入面下可用的宿主服务相当丰富从源码看包括主题颜色、字体、图形后端能力开关Cpp_Misc_GraphicsBackend.effectsEnabled、仪表盘格式化Cpp_UI_Dashboard.formatValue等。ExtensionDataModel 数据模型参考模型会重新发布仪表盘已经解析好的数据并跟随仪表盘自身的更新节拍update tick。它没有逐帧开销且包永远不会触碰任何应用对象来读取数据。属性类型描述titlestring数据集或组的显示标题包含用户的重命名。valuedouble数据集的数值组取第一个数据集。textstring与仪表盘格式化方式相同的值文本包含单位。stringValuestring原始文本值。unitsstring声明的单位。isNumericbool当前值是否解析为数字。minValue,maxValuedouble声明的显示范围。decimalPoints,displayFormatint, string声明的格式化参数。alarmsDefined,alarmTriggered,alarmSeveritybool, bool, int报警带alarm-band状态。datasetCountint组件背后的数据集数量。datasetsmodel组作用域group-scope组件的逐数据集行角色见下表。groupId,sourceId,uniqueIdint组件所渲染实体的身份标识。groupScopebool是否为组作用域包。extensionIdstring包自身的标识符。configmap声明设置的当前值。pausedbool可写冻结模型发布的值。datasets模型为每个数据集暴露一行角色为title、text、value、numericValue、isNumeric、units、minValue、maxValue、decimalPoints、displayFormat、uniqueId、index、alarmsDefined、alarmSeverity以及widgets显示同一数据集的其他仪表盘组件为{ windowId, icon, title }映射的列表。模型在值变化时发出updated()信号QML 属性绑定会自动捕获该信号。从源码ExtensionData.h可以看到除了paused、config、alarmsDefined等少数属性外绝大多数属性都以updated为 NOTIFY 信号groupId、sourceId、uniqueId、groupScope、extensionId与datasets则是CONSTANT——实体身份在创建时即固定。几个源码层面的实现要点可以帮助你写出更高效的扩展 QML逐行增量更新ExtensionRowsModelExtensionData.h 中定义是一个QAbstractListModel子类通过updateRow()就地更新行并触发dataChanged()因此一个渲染五十个数据集的组组件不会每个节拍都重建委托。updateData()ExtensionData.cpp只在「行数变化」或「实际有值移动」时触发updated()其余时候静默。paused的语义置true时挂起逐节拍刷新恢复时立即拉取一次新鲜快照setPaused中if (!m_paused) updateData();。config合并逻辑reloadConfig()先从包描述符取出每个声明设置的默认值再用项目文件为该组件存储的设置覆盖——这正是「声明默认值 项目级覆盖」两级配置的来源。配置设置Configuration Settings在清单中声明设置Serial Studio 会自动渲染设置表单——无需编写任何 UIconfig: [ { id: barColor, type: choice, label: Bar colour, default: green, options: [green, amber, red] }, { id: showValue, type: bool, label: Show numeric value, default: true }, { id: smoothing, type: double, label: Smoothing, default: 0.25, min: 0, max: 1 } ]支持的类型为bool、int、double、string与choice。读取用model.config[barColor]写入用model.setConfigValue(barColor, amber)。从模式文件可得到每项的完整约束id必填^[A-Za-z_][A-Za-z0-9_]*$1–64 字符type必填枚举[bool, int, double, string, choice]label最长 128 字符description最长 512 字符default任意值min/max数值上下界optionschoice类型的候选值数组最多 128 项。从源码看setConfigValueExtensionData.cpp的实际行为是通过projectModel.saveWidgetSetting(widgetId(), key, value)将值写入项目文件中该组件按widgetId对应的设置区然后reloadConfig()立即重新合并。因此配置值按项目存储、随项目文件一起保存——换一台机器打开同一项目设置依然在。用户通过组件的标题栏菜单caption menu中的Widget Settings…进入表单当包未声明任何设置时该入口自动隐藏。在 WidgetDelegate.qml 中可以看到设置表单通过extensionSettingsLoader.openDialog(root.extensionId, …)打开而 Compass 还演示了一种更进阶的用法把「当前页码」这类内部状态也通过model.setConfigValue(page, …)持久化到项目设置中重启后恢复见 Compass.qml 的Component.onCompleted与Connections段。安装与测试将包文件夹复制到~/Documents/Serial Studio/Extensions/widget/。重启 Serial Studio。目录在启动时读取扩展管理器安装、更新或移除包时也会重新读取对应源码中catalogChanged信号触发 WidgetDelegate.qml 的onCatalogChanged就地重建组件槽位。打开项目选中一个数据集或组从 Widget 列表中选择该组件。在同意对话框出现时允许该包运行。迭代 QML 时重启应用以拾取编辑。需要快速验证清单语法时可以直接对照 widget-manifest.json 模式 检查该模式同时被测试套件 tst_widget_manifest.cpp 引用仓库 CI 会用它校验。一个有用的迭代技巧内置的 Compass 与 Data Grid 就是「随包捆绑的扩展」其 QML 位于 app/rcc/extensions/widget与用户安装的包使用完全相同的注入契约model/color/windowRoot/widgetId。遇到「扩展为什么拿不到数据」之类的疑惑时对比阅读这些内置实现通常能最快定位问题。包加载失败时的表现无法渲染的包永远不会静默消失。组件槽位会显示一个指名原因的占位符Problem Center 会列出对应条目上报内容原因Manifest 不可用清单不是合法 JSON或缺少id、title、type: widget或widget块。保留标识符包id是内置组件标识符。与当前版本不兼容hostCompat或apiVersion排除了当前构建。没有可用的 QML 文件声明的入口文件缺失或指向包外。依赖缺失必需的依赖未安装或其版本超出范围。Widget 扩展加载失败QML 编译失败或在创建时报错消息会携带 QML 错误信息。未安装项目引用了本机未安装的包。等待你的许可包已安装但尚未被允许运行。占位符机制与「失败绝不静默」的保证可以在 WidgetDelegate.qml 的showPlaceholder(reason)中找到实现它加载内置的ExtensionPlaceholder.qml组件把原因、标题与扩展 id 传入并铺满槽位QML 编译错误Component.Error时则直接展示component.errorString()。分发DistributionWidget 包的托管与安装方式与其他扩展类型完全一致在仓库的manifest.json中添加widget/id/info.json条目然后让扩展管理器指向该仓库。仓库布局、托管方式与按平台分发的文件细节见 Extensions.md。对仓库侧的文件格式catalog.json 模式 给出了关键的完整性要求每个可安装文件都必须携带 SHA-256 摘要与字节大小schemaVersion: 2不带摘要的 v1 目录会被安装器拒绝。安装器先把文件下载到暂存目录逐文件核对摘要全部通过后才替换已安装版本id同时是安装目录的一个路径段因此模式严格排除了..等父路径引用。需要明确的是Serial Studio 在扩展下载时不验证签名也不验证校验和安装器自身的摘要校验针对的是仓库分发流程而非对包作者的签名信任。由于 Widget 包是在应用内部运行的代码请从你的用户已经信任的地方发布并明确告知他们该包的行为。小结从零到可分发包的三步路线复制并改造样例以 examples/widget-extension 为起点改id反向域名、避开保留标识符与title按需填写widget块。实现 QML 契约声明四个required propertycolor、widgetId、windowRoot、model只用QtQuick/QtQuick.Controls/SerialStudio导入面需要用户可调项时在widget.config里声明并用model.config/model.setConfigValue读写。安装验证后分发复制到~/Documents/Serial Studio/Extensions/widget/id/重启选数据集/组并允许运行确认占位符机制在你出错时能给出准确原因然后把widget/id/info.json挂入仓库manifest.json分发。整个过程中记住信任模型的底线你的 QML 以应用自身的权限运行没有沙箱——写代码时把它当作应用本体的一部分来对待只从可信来源分发。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考