news 2026/10/3 1:55:27

QGroundControl Fact System 深度解析:从 Fact 到 FactGroup 的自定义构建与元数据驱动 UI

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QGroundControl Fact System 深度解析:从 Fact 到 FactGroup 的自定义构建与元数据驱动 UI
  • 无人机
  • 智能硬件

【免费下载链接】qgroundcontrol

Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)

项目地址:https://gitcode.com/gh_mirrors/qg/qgroundcontrol
点击查看免费下载

导读:本文围绕 QGroundControl(QGC)开发指南中的 Fact System 章节展开,完整讲解 Fact、FactMetaData、FactGroup 与 Fact Controls 四层架构的职责与协作方式,并结合仓库源码(src/FactSystem、src/FirmwarePlugin/FirmwarePlugin.h)深入剖析自定义固件插件如何通过重写factGroups与adjustMetaData扩展用户自定义 Fact、调整已有 Fact 的元数据,以及getFact()的两种寻址方式。读完本文,你将掌握 QGC UI 自动生成的底层机制,并能够编写属于自己的 Fact 扩展。

概述:Fact System 是什么

QGC 的界面以“飞行控制台”著称,其几乎所有界面控件(数值输入框、开关、下拉框、滑块)都不是手写死绑的,而是由Fact System统一驱动、自动生成。正如 docs/en/qgc-dev-guide/fact_system.md 所述:Fact System 提供了一组能力,用于标准化并简化 QGC 用户界面的创建。

这套体系由四个核心构件组成,职责层层递进:

构件职责源码位置
Fact系统中单个值(如某个飞行参数、某个设置项)的载体src/FactSystem/Fact.h
FactMetaData描述该值“是什么、怎么显示、怎么校验”的元数据src/FactSystem/FactMetaData.h
Fact Control连接 Fact 与元数据的 QML UI 控件,供用户修改/显示值src/FactSystem/FactControls
FactGroup将一组 Fact 组织成对象层级,支持用户自定义 Factsrc/FactSystem/FactGroup.h

理解这套分层后你会发现:“数据 + 描述 + 控件模板”三者分离,是 QGC 能够自动生成界面、同时允许不同固件(PX4、ArduPilot 系)注入自己特有参数的根本原因。

Fact:系统中的一个值

核心定位

Fact继承自QObject,在 src/FactSystem/Fact.h 中被声明为QML_ELEMENT,意味着它可以直接暴露给 QML 层使用。每个 Fact 代表系统中的一个值,它至少包含:

  • name:唯一名称(如"MaxAlt");
  • type:FactMetaData::ValueType_t类型(如valueTypeInt32);
  • componentId:关联的 MAVLink 组件 ID(默认-1表示不关联组件);
  • rawValue / cookedValue:原始值(raw)与经过单位换算、翻译后的“熟值”(cooked)。

Raw 与 Cooked 的双域设计

Fact最核心的设计是raw / cooked 双值域:

  • rawValue是未经翻译的原始值(例如飞控下发的degE7、centiDegrees);
  • cookedValue是经过元数据中 translator(翻译器)处理后的用户可读值(如米、英尺、节)。

Fact::rawToCooked()(src/FactSystem/Fact.h)的注释明确指出:在混合使用常量与 Fact 值时,应当使用它而不是QmlUnitsConversion辅助函数,以保证换算始终跟随该 Fact 自身的元数据。Q_PROPERTY 中value与rawValue都提供了读写接口,setCookedValue会触发valueChanged信号,setRawValue触发rawValueChanged,二者是 QML 控件刷新与 MAVLink 回传的分界。

常用 Q_PROPERTY 一览

从 src/FactSystem/Fact.h 可以看到 Fact 对外暴露的属性非常丰富,QML 控件主要依赖这些属性做自动生成与校验:

  • 显示类:label、shortDescription、longDescription、units、valueString、enumOrValueString、decimalPlaces;
  • 校验类:min/max(类型默认范围)、userMin/userMax(用户自定义范围)、validate()、clamp();
  • 枚举/位掩码类:enumStrings、enumValues、enumIndex、bitmaskStrings、bitmaskValues、selectedBitmaskStrings;
  • 行为类:readOnly、writeOnly、hasControl、volatileValue、vehicleRebootRequired、qgcRebootRequired、increment;
  • 状态类:valueEqualsDefault、defaultValue、defaultValueAvailable。

其中validate(const QString &cookedValue, bool convertOnly)与clamp(const QString &cookedValue)是 Q_INVOKABLE 方法,供 QML 输入控件在用户输入时做“先转换、再校验、必要时钳制”的三步处理。

FactMetaData:驱动 UI 自动生成与校验的元数据

为什么需要独立元数据对象

src/FactSystem/FactMetaData.h 的类注释说明了设计意图:

元数据与 Fact 本身分离保存,因为系统中可能存在同一 Fact 的多个实例,但每个 Fact 只有唯一的 FactMetaData 实例。

例如“高度”这个 Fact 可能同时出现在多个设置页中,但它们的类型、单位、范围、枚举描述只需一份共享元数据。

值类型体系

FactMetaData::ValueType_t(src/FactSystem/FactMetaData.h)定义了 14 种值类型:

Uint8 / Int8 / Uint16 / Int16 / Uint32 / Int32 / Uint64 / Int64 / Float / Double / String / Bool / ElapsedTimeInSeconds / Custom

注意两个特殊类型:

  • valueTypeElapsedTimeInSeconds:内部以 double 存储,但valueString显示为HH:MM:SS格式;
  • valueTypeCustom:内部以QByteArray存储,用于无法归入基础类型的值。

类型字符串与枚举的互相转换由stringToType()/typeToString()完成(src/FactSystem/FactMetaData.h),这使 JSON 文件中的"type": "Int32"能直接映射到valueTypeInt32。

单位换算(Translator)机制

FactMetaData内置大量翻译器(src/FactSystem/FactMetaData.h),覆盖了地面站场景几乎全部单位换算需求:

  • 角度:_degreesToRadians、_centiDegreesToDegrees、_userGimbalDegreesToMavlinkGimbalDegrees等;
  • 温度:_centiCelsiusToCelsius、_celsiusToFarenheit等;
  • 距离/高度:_metersToFeet、_centimetersToInches等;
  • 面积:平方米 ⇄ 平方公里 / 公顷 / 平方英尺 / 英亩 / 平方英里;
  • 速度:m/s ⇄ mph / km/h / 节;
  • 重量:克 ⇄ 千克 / 盎司 / 磅;
  • 百分比:_percentToNorm/_normToPercent。

此外还有跟随用户 App 设置的单位翻译(_setAppSettingsTranslators(),src/FactSystem/FactMetaData.h):FactMetaData会依据用户在设置中选择的水平距离、垂直距离、面积、速度、温度、重量单位(UnitTypes,src/FactSystem/FactMetaData.h)自动切换 cooked 单位与 translator。

校验、钳制与自定义校验器

FactMetaData提供三条核心校验路径:

  • convertAndValidateRaw()/convertAndValidateCooked()(src/FactSystem/FactMetaData.h):转换类型并对照 min/max 校验,失败时通过errorString返回面向用户的错误信息;
  • clampValue()(src/FactSystem/FactMetaData.h):转换后把越界值钳制到cookedMin/cookedMax;
  • setCustomCookedValidator()(src/FactSystem/FactMetaData.h):安装自定义 cooked 值校验函数,在标准校验器之前被调用,返回空字符串表示校验通过,否则返回向用户解释的错误字符串。

min/max 体系同样区分两套:类型固有范围(_minForType/_maxForType)与元数据声明的范围;maxIsDefaultForType等属性用来判断元数据是否显式覆盖了默认范围(src/FactSystem/FactMetaData.h)。

JSON 元数据字段全集

FactMetaData通过createMapFromJsonFile()/createMapFromJsonArray()/createFromJsonObject()(src/FactSystem/FactMetaData.h)从 JSON 加载元数据。JSON 键名定义在 src/FactSystem/FactMetaData.h,配合官方 schema src/FactSystem/factmetadata.schema.json 可得到完整字段清单:

JSON 键含义备注
nameFact 名称必填,^[a-zA-Z_][a-zA-Z0-9_]*$
type值类型必填,14 种类型之一(小写)
units显示单位如m、m/s、deg、%、degE7
min/max合法值范围(含端点)可选
default/mobileDefault默认值mobileDefault为移动端单独指定
decimalPlaces显示小数位0–15,默认kDefaultDecimalPlaces = 3
enumStrings/enumValues枚举显示串与数值新格式为values数组
bitmaskStrings/bitmaskValues位掩码显示串与数值新格式为bitmask数组
shortDesc/longDesc短/长描述分别用于 UI 与帮助提示
category/group分组信息默认"Other"/"Misc"
volatile高频变化标志影响 UI 刷新策略
control是否有对应 UI 控件hasControl
readOnly/writeOnly读写权限
increment步进值用于旋钮/带刻度滑块
rebootRequired/qgcRebootRequired是否需重启飞控/QGC
comment/keywords注释与检索关键词

同时 schema 规定 JSON 文件顶层支持version字段与QGC.MetaData.Facts数组、QGC.MetaData.Defines预定义常量映射(src/FactSystem/FactMetaData.h),后者可在元数据文件中复用公共定义。

FactGroup:Fact 的组织容器

FactGroup(src/FactSystem/FactGroup.h)把一组 Fact 组织成对象层级,是“名 → Fact”映射(_nameToFactMap)与“名 → 子 FactGroup”映射(_nameToFactGroupMap)的持有者。其关键能力:

  • _addFact()/_addFactGroup():把 Fact 或子组挂接到当前组,可指定名称,默认使用 Fact 的name()/ objectName;
  • factNames()/factGroupNames():枚举组内 Fact 与子组名;
  • getFact(name)/getFactGroup(name):按名查找,找不到会输出qWarning(视为内部错误);
  • _updateAllValues():按updateRateMsecs周期刷新值,0 表示立即更新,实现高频遥测场景下的节流(对应Fact::setSendValueChangedSignals的延迟信号机制,src/FactSystem/Fact.h);
  • handleMessage():允许 FactGroup 解析收到的 MAVLink 消息并填充值(src/FactSystem/FactGroup.h);
  • telemetryAvailable:标志该组值是否已收到遥测。

构造函数FactGroup(int updateRateMsecs, const QString &metaDataFile, QObject *parent = nullptr, bool ignoreCamelCase = false)(src/FactSystem/FactGroup.h)允许直接传入 JSON 元数据文件,由_loadFromJsonArray()自动批量创建组内 Fact 及其元数据。

Fact Controls:QML 控件层

Fact Control是连接 Fact 与其元数据的 QML 控件。QGC 在 src/FactSystem/FactControls 下提供了覆盖各种交互形态的控件族:

控件用途
FactTextField/FactTextFieldGrid/FactTextFieldRow单行文本/数值输入,网格/行布局
FactTextFieldSlider/FactTextFieldSlider2/FactValueSlider文本输入 + 滑块组合
FactCheckBox/FactCheckBoxSlider布尔开关、开关 + 滑块
FactComboBox枚举下拉框(依赖enumStrings/enumValues)
FactBitmask/FactBitMaskCheckBoxSlider位掩码多选
FactLabel/LabelledFactLabel只读显示
LabelledFactTextField/LabelledFactComboBox/LabelledFactIncrementer/LabelledFactBrowse带标题的封装控件
AltitudeFactTextField高度专用输入(自动应用垂直距离单位)

这些控件统一通过Fact的 Q_PROPERTY 读写值,并通过FactMetaData完成校验与单位显示,因此一套控件即可复用于任意 Fact。配套的FactPanelController(src/FactSystem/FactControls/FactPanelController.h)为面板提供 Fact 查找与绑定能力。

自定义构建:扩展用户自定义 Fact

原文档给出了事实系统的扩展入口,全部集中在FirmwarePlugin 层。以下结合源码逐一展开。

重写factGroups():注入固件专属 FactGroup

src/FirmwarePlugin/FirmwarePlugin.h 中声明了虚函数:

/// Returns a pointer to a dictionary of firmware-specific FactGroups virtual QMap<QString, FactGroup*> *factGroups() { return nullptr; }

默认实现返回nullptr。自定义固件插件(如 APM 系列)通过重写该方法返回“名称 → FactGroup”映射,QGC 据此识别新增的 Fact 组。仓库中以ArduSubFirmwarePlugin为真实范例:它在 src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.cc 实现了factGroups(),并在头文件 src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.h 中以override声明。

添加自定义 FactGroup 的方式是继承FactGroup类,并在构造函数中通过FactGroup(updateRateMsecs, metaDataFile, ...)传入包含必要信息的 JSON 文件,由 FactGroup 自动完成 Fact 与元数据的装载。

重写adjustMetaData():调整已有 Fact 的元数据

src/FirmwarePlugin/FirmwarePlugin.h:

/// Allows the Firmware plugin to override the facts meta data. /// @param vehicleType - Type of current vehicle /// @param metaData - MetaData for fact virtual void adjustMetaData(MAV_TYPE /*vehicleType*/, FactMetaData* /*metaData*/) {}

该钩子在车辆事实创建时被调用,用于按飞控类型改写既有 Fact 的元数据(如调整范围、枚举、单位、默认值)。ArduSubFirmwarePlugin::adjustMetaData(src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.cc)即是重写示例。

调用链路可以在 src/Vehicle/Vehicle.cc 中找到证据:

_firmwarePlugin->adjustMetaData(vehicleType, getFact(factName)->metaData());

即:Vehicle在初始化事实时取得其FactMetaData指针,交给固件插件进行定制化修改。值得注意的是SettingsManager也执行类似流程(Fact构造时调用SettingsManager::adjustSettingMetaData,见 src/FactSystem/Fact.h),因此自定义构建对设置类 Fact 也有调整入口。

getFact()的两种寻址方式

原文档强调:与车辆关联的 Fact(包括固件插件factGroups()返回组内的 Fact)可通过两种路径访问:

  • getFact("factName"):直接访问组内顶层 Fact;
  • getFact("factGroupName.factName"):用点号限定组名后访问。

Vehicle中确实实现了这种分级访问。在 src/Vehicle/Vehicle.cc 中Vehicle先获取_firmwarePlugin->factGroups()返回的固件专属组;在 src/Vehicle/Vehicle.cc 中遍历factGroups()并挂接各组的 Fact;在 src/Vehicle/Vehicle.cc 中可以看到同时使用getFact(factName)与getFactGroup(groupName)->getFact(factName)两种方式汇总全部 Fact 值——这正是“组名.factName”寻址在实现层的等价形式。

实践步骤小结

要在自定义固件插件中加入用户自定义 Fact,完整路径为:

  1. 继承FactGroup编写自定义组类,构造时传入 JSON 元数据文件路径(或手工_addFact添加 Fact);
  2. 重写固件插件(继承FirmwarePlugin)的factGroups(),返回包含该组的新映射;参考ArduSubFirmwarePlugin的写法(src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.cc);
  3. 如需修改现有 Fact 的元数据,重写adjustMetaData(MAV_TYPE vehicleType, FactMetaData *metaData),按vehicleType分支改写metaData的属性;
  4. 在 QML 中通过getFact("factName")或getFact("groupName.factName")获取 Fact,再配合 src/FactSystem/FactControls 中的任一控件完成展示与编辑。

总结

QGC 的 Fact System 用“一个值(Fact)+ 一份描述(FactMetaData)+ 一组控件模板(Fact Controls)+ 一个容器(FactGroup)”四层结构,实现了界面生成的标准化与自动化:元数据驱动 UI 生成与输入校验,translator 处理单位换算,factGroups()与adjustMetaData()为不同固件/自定义构建打开了两条扩展通道。对开发者而言,掌握这套机制就等于掌握了向 QGC 注入任何自定义参数、并为它自动生成专业 UI 的完整方法论。进一步的源码细节可继续研读 src/FactSystem/Fact.cc、src/FactSystem/FactGroup.cc 与 src/FirmwarePlugin/FirmwarePlugin.cc。

  • 无人机
  • 智能硬件

【免费下载链接】qgroundcontrol

Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)

项目地址:https://gitcode.com/gh_mirrors/qg/qgroundcontrol
点击查看免费下载
上一篇:Kubescape架构图详解:理解Kubernetes安全平台组件
下一篇:物联网设备代码库的可视化文档终极指南:如何用DeepWiki-Open快速生成智能文档

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!