- 无人机
- 智能硬件
【免费下载链接】qgroundcontrol
Cross-platform ground control station for drones (Android, iOS, Mac OS, Linux, Windows)
导读:本文围绕 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 组织成对象层级,支持用户自定义 Fact | src/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 键 | 含义 | 备注 |
|---|---|---|
name | Fact 名称 | 必填,^[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,完整路径为:
- 继承
FactGroup编写自定义组类,构造时传入 JSON 元数据文件路径(或手工_addFact添加 Fact); - 重写固件插件(继承
FirmwarePlugin)的factGroups(),返回包含该组的新映射;参考ArduSubFirmwarePlugin的写法(src/FirmwarePlugin/APM/ArduSubFirmwarePlugin.cc); - 如需修改现有 Fact 的元数据,重写
adjustMetaData(MAV_TYPE vehicleType, FactMetaData *metaData),按vehicleType分支改写metaData的属性; - 在 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)
相关推荐
Fact Check Report: [Concept Name]
Fact Check Report: Concept Name File: docs/concepts/ slug .mdx Date: YYYY MM DD
教程前端文档Puppet 实战:基于 Hiera 5 与 YAML 后端的层次化数据分层与 Fact 驱动覆盖
Puppet 实战:基于 Hiera 5 与 YAML 后端的层次化数据分层与 Fact 驱动覆盖 导读 本文以 Puppet 仓库中的 examples/hi
运维DevOpsIaCQGroundControl 自定义构建插件体系:FirmwarePlugin / AutoPilotPlugin / QGCCorePlugin 深度定制指南
QGroundControl 自定义构建插件体系:FirmwarePlugin / AutoPilotPlugin / QGCCorePlugin 深度定制指南
无人机智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考