QGroundControl 4.0 工程架构与代码目录技术文档
前言
QGroundControl(以下简称 QGC)是由 QGroundControl 开发团队维护的开源无人机地面站(Ground Control Station, GCS)软件。它基于 Qt 5.11+ / C++14 / QML 构建,通过 MAVLink 协议与 PX4、ArduPilot 等飞控栈通信,提供飞行监控、任务规划、参数配置、固件升级、视频流接收等完整 GCS 能力。
本文从工程实践角度,按 分层架构 → 目录结构 → 类体系与信号槽 → 多线程调度 四个维度,对 QGC 4.0 代码库进行系统性解析。文中代码引用均来自当前工程源码。
一、分层架构设计
QGC 并非严格意义上的四层 MVC,而是采用 "工具箱(Toolbox)+ 插件(Plugin)+ 数据绑定(Fact)" 的混合架构。若从职责划分角度,可映射为以下四层:
┌─────────────────────────────────────────────────────────────┐
│ UI 表现层 (Presentation) │
│ QML 视图 + C++ Controller + QGroundControlQmlGlobal │
├─────────────────────────────────────────────────────────────┤
│ 业务逻辑层 (Business) │
│ Vehicle / MissionManager / FirmwarePlugin / AutoPilotPlugin│
├─────────────────────────────────────────────────────────────┤
│ 通信协议层 (Communication) │
│ LinkManager / LinkInterface / MAVLinkProtocol │
├─────────────────────────────────────────────────────────────┤
│ 数据持久层 (Data) │
│ FactSystem / SettingsManager / ParameterManager / 缓存文件 │
└─────────────────────────────────────────────────────────────┘
↑ libs/mavlink、Eigen、ShapeLib 等第三方库 ↑
1.1 UI 表现层
UI 层以 QML 声明式界面 为主,C++ 控制器为辅,通过 Qt 属性绑定实现数据驱动渲染。
核心入口:
src/ui/MainRootWindow.qml:应用顶层ApplicationWindow,定义全局属性(如activeVehicle),承载 Fly View / Plan View / Setup View 等视图切换。src/FlightDisplay/:飞行显示界面(HUD、仪表盘、视频叠加)。src/FlightMap/+src/PlanView/:地图与任务规划界面。src/AutoPilotPlugins/:各飞控栈的设置向导 QML 页面。src/QmlControls/:可复用 QML 控件库(按钮、对话框、参数编辑器等)。
C++ 与 QML 的桥梁:
QGroundControlQmlGlobal 是 QML 侧访问 C++ 后端的全局单例入口,将 LinkManager、MultiVehicleManager、SettingsManager 等核心服务以 Q_PROPERTY 形式暴露给 QML:
69:77:src/QmlControls/QGroundControlQmlGlobal.h
Q_PROPERTY(LinkManager* linkManager READ linkManager CONSTANT)
Q_PROPERTY(MultiVehicleManager* multiVehicleManager READ multiVehicleManager CONSTANT)
Q_PROPERTY(QGCMapEngineManager* mapEngineManager READ mapEngineManager CONSTANT)
Q_PROPERTY(QGCPositionManager* qgcPositionManger READ qgcPositionManger CONSTANT)
Q_PROPERTY(MissionCommandTree* missionCommandTree READ missionCommandTree CONSTANT)
Q_PROPERTY(VideoManager* videoManager READ videoManager CONSTANT)
Q_PROPERTY(MAVLinkLogManager* mavlinkLogManager READ mavlinkLogManager CONSTANT)
Q_PROPERTY(QGCCorePlugin* corePlugin READ corePlugin CONSTANT)
Q_PROPERTY(SettingsManager* settingsManager READ settingsManager CONSTANT)
QML 中通过 QGroundControl.multiVehicleManager.activeVehicle 即可绑定当前活动飞行器的状态,实现 UI 与业务数据的自动同步。
插件化 UI 扩展:
QGCCorePlugin(src/api/)允许自定义构建替换默认行为,包括设置页列表、分析页、工具栏 URL、品牌资源等,是 OEM 定制 QGC 的核心扩展点。
1.2 通信协议层
通信层负责物理链路的建立、字节流收发、MAVLink 帧解析与分发,是 GCS 与飞行器之间的唯一数据通道。
三层结构:
| 组件 | 职责 |
|---|---|
LinkManager |
链路生命周期管理:创建/连接/断开、自动连接、MAVLink 通道分配 |
LinkInterface |
抽象链路接口:串口/UDP/TCP/蓝牙/HIL 仿真的统一基类 |
MAVLinkProtocol |
MAVLink 帧解析、序列号丢包统计、遥测日志、消息广播 |
数据流(接收方向):
mermaid
sequenceDiagram
participant HW as 物理设备/网络
participant Link as LinkInterface<br/>(独立线程)
participant LM as LinkManager
participant MP as MAVLinkProtocol
participant MVM as MultiVehicleManager
participant V as Vehicle
HW->>Link: 原始字节流
Link->>MP: bytesReceived(link, data)
MP->>MP: mavlink_parse_char()
MP->>MVM: vehicleHeartbeatInfo (首次心跳)
MVM->>V: 创建 Vehicle 实例
MP->>V: messageReceived(link, msg)
V->>V: _mavlinkMessageReceived()
关键连接在 LinkManager::_addLink() 中建立:
200:202:src/comm/LinkManager.cc
connect(link, &LinkInterface::communicationError, _app, &QGCApplication::criticalMessageBoxOnMainThread);
connect(link, &LinkInterface::bytesReceived, _mavlinkProtocol, &MAVLinkProtocol::receiveBytes);
connect(link, &LinkInterface::bytesSent, _mavlinkProtocol, &MAVLinkProtocol::logSentBytes);
MAVLinkProtocol::receiveBytes() 逐字节调用 mavlink_parse_char(),完整帧解析后通过 messageReceived 信号广播:
352:355:src/comm/MAVLinkProtocol.cc
// The packet is emitted as a whole, as it is only 255 - 261 bytes short
// kind of inefficient, but no issue for a groundstation pc.
// It buys as reentrancy for the whole code over all threads
emit messageReceived(link, _message);
发送方向 采用线程安全写法:外部调用 writeBytesSafe() → 发射 _invokeWriteBytes 信号 → 在链路线程内执行 _writeBytes(),避免跨线程直接操作串口/套接字。
1.3 业务逻辑层
业务层封装无人机相关的领域逻辑,与具体 UI 和通信细节解耦。
核心实体:Vehicle
Vehicle(src/Vehicle/Vehicle.h/.cc)是整个业务层的中心对象,代表一架已连接的 MAVLink 飞行器。每个 Vehicle 实例聚合:
ParameterManager:飞控参数读写MissionManager/GeoFenceManager/RallyPointManager:任务/围栏/集结点FirmwarePlugin/AutoPilotPlugin:固件特定行为与设置 UI- 多个
FactGroup子组:GPS、电池、振动、风速等遥测数据
多机管理:MultiVehicleManager
监听 MAVLinkProtocol::vehicleHeartbeatInfo,在收到 Autopilot 组件(MAV_COMP_ID_AUTOPILOT1)的心跳时创建 Vehicle:
66:66:src/Vehicle/MultiVehicleManager.cc
connect(_mavlinkProtocol, &MAVLinkProtocol::vehicleHeartbeatInfo, this, &MultiVehicleManager::_vehicleHeartbeatInfo);
固件插件体系:
| 类 | 作用 |
|---|---|
FirmwarePlugin |
抽象基类:飞行模式字符串、MAVLink 命令映射、任务命令树 |
FirmwarePluginManager |
工厂:按 MAV_AUTOPILOT + MAV_TYPE 返回对应插件 |
PX4FirmwarePlugin / APMFirmwarePlugin |
PX4 / ArduPilot 具体实现 |
AutoPilotPlugin |
设置向导 UI 组件列表(传感器、遥控器、安全等) |
这种设计使 QGC 主体代码保持 MAVLink 通用,固件差异全部收敛到 Plugin 目录。
任务规划业务链:
PlanView.qml (UI)
↓ 绑定
PlanMasterController (协调 Mission/GeoFence/Rally)
↓
MissionController (视觉任务项编辑、路径计算)
↓ sendToVehicle()
PlanManager (MAVLink Mission Protocol v2 状态机)
↓ MAVLink MISSION_* 消息
Vehicle → 飞控
PlanManager 实现了 MAVLink v2 任务协议的状态机,含超时重试、ACK 等待、进度百分比等:
74:77:src/MissionManager/PlanManager.h
static const int _ackTimeoutMilliseconds = 1500;
// When actively retrying to request mission items, use a shorter timeout instead.
static const int _retryTimeoutMilliseconds = 250;
static const int _maxRetryCount = 5;
1.4 数据持久层
数据层统一管理参数、设置、缓存三类数据。
FactSystem --- 统一数据模型
Fact 是 QGC 中最基础的数据单元,表示一个带元数据(类型、单位、枚举、范围)的可绑定值:
27:28:src/FactSystem/Fact.h
/// @brief A Fact is used to hold a single value within the system.
class Fact : public QObject
FactGroup 将多个 Fact 组织成树形结构,支持批量更新速率限制(避免高频遥测导致 UI 卡顿):
23:24:src/FactSystem/FactGroup.h
/// Used to group Facts together into an object hierarachy.
class FactGroup : public QObject
ParameterManager --- 飞控参数同步
每个 Vehicle 持有一个 ParameterManager,负责:
- 向飞控请求全量参数列表(
PARAM_REQUEST_LIST) - 本地磁盘缓存(
parameterCacheDir()) - 参数写入队列与
pendingWrites状态跟踪
SettingsManager --- 应用配置
SettingsManager 作为 QGCToolbox 中第一个创建的工具,管理所有持久化应用设置:
55:56:src/QGCToolbox.cc
// SettingsManager must be first so settings are available to any subsequent tools
_settingsManager = new SettingsManager (app, this);
下设 AppSettings、AutoConnectSettings、VideoSettings、FlightMapSettings 等子模块,均基于 QSettings 持久化。
离线/在线地理数据:
Terrain/:地形高度查询(在线 API + 离线瓦片)QtLocationPlugin/:地图瓦片引擎与离线缓存Geo/:坐标系转换(UTM、MGRS 等)
二、核心源码文件夹作用详解
2.1 工程根目录
| 目录/文件 | 功能 |
|---|---|
src/ |
全部核心 C++/QML 源码 |
libs/ |
第三方依赖(MAVLink、Eigen、ShapeLib 等) |
resources/ |
静态资源:SVG 图标、FlightGear HIL 飞机模型 |
cmake/ |
CMake 构建模块(Git 版本、Qt 配置、Android 工具链) |
deploy/ |
打包脚本(AppImage、NSIS 安装包、PPA) |
tools/ |
构建辅助(版本号更新、Qt 打包、ccache) |
android/ / ios/ |
移动端平台配置 |
qgroundcontrol.pro |
qmake 主工程文件 |
CMakeLists.txt |
CMake 构建入口 |
QGCExternalLibs.pri |
外部库路径与 MAVLink 方言配置 |
2.2 src/ 模块一览
应用框架
| 目录 | 说明 |
|---|---|
main.cc |
程序入口:初始化 QApplication、注册 QML 类型、启动 UI |
QGCApplication.* |
应用单例:语言、主题、消息框、主窗口引用 |
QGCToolbox.* |
服务注册中心:统一创建和管理所有顶层工具 |
api/ |
插件 API:QGCCorePlugin、QGCOptions、QGCSettings |
Settings/ |
应用设置分组管理 |
QmlControls/ |
QML 控件库与 C++ 控制器 |
通信子系统 (comm/)
| 文件/类 | 说明 |
|---|---|
LinkManager |
链路 CRUD、自动连接、MAVLink 通道池 |
LinkInterface |
链路抽象基类(继承 QThread) |
SerialLink / UDPLink / TCPLink |
具体链路实现 |
MAVLinkProtocol |
MAVLink 解析与日志 |
MockLink |
单元测试用仿真链路 |
LogReplayLink |
遥测日志回放 |
QGCJSBSimLink / QGCXPlaneLink |
HIL 仿真链路 |
车辆与数据 (Vehicle/, FactSystem/, uas/)
| 目录 | 说明 |
|---|---|
Vehicle/ |
Vehicle 实体、MultiVehicleManager、MAVLink 日志管理 |
FactSystem/ |
Fact、FactGroup、ParameterManager、FactMetaData |
uas/ |
遗留 UAS 接口、UASMessageHandler(STATUSTEXT 消息)、FileManager(MAVLink FTP) |
固件插件 (FirmwarePlugin/, AutoPilotPlugins/)
| 子目录 | 说明 |
|---|---|
FirmwarePlugin/PX4/ |
PX4 飞行模式、参数元数据 XML、MAVLink 命令 JSON |
FirmwarePlugin/APM/ |
ArduPilot 各机型插件(Copter/Plane/Rover/Sub) |
AutoPilotPlugins/PX4/ |
PX4 设置页(机架、传感器、安全、调参) |
AutoPilotPlugins/APM/ |
ArduPilot 设置页(电机、罗盘校准等) |
AutoPilotPlugins/Common/ |
通用组件(遥控器、电机测试) |
AutoPilotPlugins/Generic/ |
通用 MAVLink 飞控插件 |
任务规划 (MissionManager/, PlanView/)
| 类/目录 | 说明 |
|---|---|
MissionItem |
单个 MAVLink 任务项 |
VisualMissionItem / SimpleMissionItem / ComplexMissionItem |
可视化任务项抽象 |
SurveyComplexItem / CorridorScanComplexItem / StructureScanComplexItem |
复杂任务模式 |
MissionController |
Plan View 任务编辑控制器 |
PlanMasterController |
任务+围栏+集结点总控 |
PlanManager |
MAVLink Mission Protocol 状态机 |
PlanView/ |
任务规划 QML 界面 |
地图与显示
| 目录 | 说明 |
|---|---|
FlightMap/ |
地图控件、MapItems(车辆/航点/围栏可视化) |
FlightDisplay/ |
Fly View HUD 与仪表盘 |
QtLocationPlugin/ |
自定义 Qt Location 插件(多地图源) |
Geo/ |
坐标转换数学库 |
Terrain/ |
地形高度查询接口 |
PositionManager/ |
地面站自身 GPS 位置 |
外设与扩展
| 目录 | 说明 |
|---|---|
GPS/ |
外接 RTK GPS(GPSProvider 独立线程 + Drivers/ 驱动) |
Joystick/ |
游戏手柄输入(SDL / Android) |
Camera/ |
MAVLink 相机控制 |
VideoStreaming/ |
GStreamer 视频接收 |
Audio/ |
语音告警播报 |
ADSB/ |
ADS-B 交通感知 |
FollowMe/ |
"跟随我"模式 |
AirspaceManagement/ / Airmap/ |
空域管理 |
AnalyzeView/ |
日志分析、MAVLink 控制台、GeoTag |
VehicleSetup/ |
固件升级、摇杆校准 |
ViewWidgets/ |
可扩展自定义视图 |
UI 层 (ui/)
| 子目录 | 说明 |
|---|---|
MainRootWindow.qml |
主窗口 |
toolbar/ |
顶部工具栏(连接、GPS、电池、模式指示器) |
preferences/ |
设置对话框(链路、MAVLink、串口等) |
测试
| 目录 | 说明 |
|---|---|
qgcunittest/ |
单元测试框架与测试用例 |
SingletonTest/ |
本工程自定义的单例测试模块 |
2.3 libs/ 第三方库
| 库 | 用途 |
|---|---|
mavlink/ |
MAVLink v2.0 协议头文件(默认 ardupilotmega 方言) |
eigen/ |
线性代数(坐标变换、姿态计算) |
shapelib/ |
ESRI Shapefile 读写(地理数据导入) |
qtandroidserialport/ |
Android 串口支持 |
gst-plugins-good/ |
GStreamer 视频插件 |
OpenSSL/ |
加密(配对、AirMap) |
sdl2/ |
摇杆输入(桌面端) |
三、类结构与信号槽通信机制
3.1 应用启动与工具箱初始化
程序从 main.cc 启动,创建 QGCApplication 单例,后者持有 QGCToolbox:
main.cc
QGCApplication
QGCToolbox 两阶段初始化
Phase 1: new 所有 QGCTool
Phase 2: setToolbox 注入依赖
QQmlApplicationEngine
加载 MainRootWindow.qml
QGroundControlQmlGlobal 单例
两阶段构造模式是 QGC 依赖注入的核心约定:
129:140:src/QGCToolbox.h
class QGCTool : public QObject {
Q_OBJECT
public:
// All tools must be parented to the QGCToolbox and go through a two phase creation. In the constructor the toolbox
// should only be passed to QGCTool constructor for correct parenting. It should not be referenced or set in the
// protected member. Then in the second phase of setToolbox calls is where you can reference the toolbox.
QGCTool(QGCApplication* app, QGCToolbox* toolbox);
virtual void setToolbox(QGCToolbox* toolbox);
protected:
QGCApplication* _app;
QGCToolbox* _toolbox;
};
Phase 1 仅做 new,Phase 2 的 setToolbox() 中才通过 _toolbox->xxx() 获取其他工具引用并建立信号连接。这避免了工具间循环依赖和未初始化访问。
工具创建顺序(节选):
SettingsManager(必须最先,供后续工具读取配置)QGCCorePlugin(自定义/OEM 插件)LinkManager→MAVLinkProtocol→MultiVehicleManager(通信链)FactSystem、FirmwarePluginManager、MissionCommandTreeVideoManager、GPSManager、JoystickManager等
3.2 核心类继承关系
QObject
├── QGCTool (工具基类)
│ ├── LinkManager
│ ├── MAVLinkProtocol
│ ├── MultiVehicleManager
│ ├── SettingsManager
│ ├── FirmwarePluginManager
│ ├── GPSManager
│ ├── VideoManager
│ └── QGroundControlQmlGlobal
│
├── QThread
│ └── LinkInterface (链路基类)
│ ├── SerialLink
│ ├── UDPLink
│ ├── TCPLink
│ └── MockLink
│
├── Vehicle (核心业务实体)
│ ├── 持有 ParameterManager
│ ├── 持有 MissionManager / GeoFenceManager / RallyPointManager
│ └── 持有多个 FactGroup 子对象
│
├── FactGroup
│ └── Fact (单个数据点)
│
├── PlanManager (任务协议状态机)
├── MissionController (Plan View 控制器)
└── AutoPilotPlugin (设置向导抽象)
├── PX4AutoPilotPlugin
└── APM 各机型 Plugin
3.3 信号槽通信模式
QGC 中信号槽承担 跨模块解耦 和 跨线程安全调度 两大职责。主要模式如下:
模式一:广播式消息分发(MAVLink 核心链路)
LinkInterface::bytesReceived
→ MAVLinkProtocol::receiveBytes (DirectConnection, 同线程或链路线程)
→ emit messageReceived(link, msg)
→ Vehicle::_mavlinkMessageReceived (每个 Vehicle 独立连接)
→ LinkManager::_mavlinkMessageReceived
→ PlanManager::_mavlinkMessageReceived
Vehicle 在构造时订阅全局 MAVLink 消息:
228:228:src/Vehicle/Vehicle.cc
connect(_mavlink, &MAVLinkProtocol::messageReceived, this, &Vehicle::_mavlinkMessageReceived);
Vehicle 内部按 msg.sysid 过滤,只处理属于自己的消息。这种 单点解析、多点订阅 的模式避免了在 Link 层做路由逻辑。
模式二:属性绑定(QML ↔ C++)
C++ 侧通过 Q_PROPERTY + NOTIFY 信号驱动 QML 自动刷新:
41:46:src/Vehicle/MultiVehicleManager.h
Q_PROPERTY(bool activeVehicleAvailable READ activeVehicleAvailable NOTIFY activeVehicleAvailableChanged)
Q_PROPERTY(bool parameterReadyVehicleAvailable READ parameterReadyVehicleAvailable NOTIFY parameterReadyVehicleAvailableChanged)
/// The current, active vehicle
Q_PROPERTY(Vehicle* activeVehicle READ activeVehicle WRITE setActiveVehicle NOTIFY activeVehicleChanged)
/// The list of all connected vehicles
Q_PROPERTY(QmlObjectListModel* vehicles READ vehicles CONSTANT)
QML 侧:
qml
property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
property bool communicationLost: activeVehicle ? activeVehicle.connectionLost : false
当 Vehicle 的 connectionLost 变化时,工具栏、地图、HUD 等所有绑定该属性的 QML 组件自动更新,无需手动刷新。
模式三:Fact 值变更链(遥测 → UI)
MAVLink MSG (如 GLOBAL_POSITION_INT)
→ Vehicle::_mavlinkMessageReceived
→ _gpsFactGroup._handleGpsRawInt(msg)
→ Fact::_containerSetRawValue(value)
→ emit valueChanged (若启用)
→ QML Text 绑定 fact.value 自动刷新
FactGroup 支持 _updateRateMSecs 批量更新,将高频遥测(如 50Hz 姿态)节流到 UI 可接受频率(如 10Hz),避免 QML 重绘风暴。
模式四:跨线程 QueuedConnection
Vehicle 发送 MAVLink 消息时使用队列连接确保线程安全:
233:233:src/Vehicle/Vehicle.cc
connect(this, &Vehicle::_sendMessageOnLinkOnThread, this, &Vehicle::_sendMessageOnLink, Qt::QueuedConnection);
链路断开等需要异步处理的场景:
212:212:src/comm/LinkManager.cc
connect(link, &LinkInterface::connectionRemoved, this, &LinkManager::_linkConnectionRemoved, Qt::QueuedConnection);
UI 弹窗从工作线程触发时使用主线程槽:
98:106:src/QGCApplication.h
public slots:
/// You can connect to this slot to show an information message box from a different thread.
void informationMessageBoxOnMainThread(const QString& title, const QString& msg);
/// You can connect to this slot to show a warning message box from a different thread.
void warningMessageBoxOnMainThread(const QString& title, const QString& msg);
/// You can connect to this slot to show a critical message box from a different thread.
void criticalMessageBoxOnMainThread(const QString& title, const QString& msg);
模式五:状态机 + 定时器(任务上传/下载)
PlanManager 使用 QTimer 实现 ACK 超时重试状态机:
91:93:src/MissionManager/PlanManager.h
private slots:
void _mavlinkMessageReceived(const mavlink_message_t& message);
void _ackTimeout(void);
流程:发送 MISSION_COUNT → 启动 1500ms 定时器 → 收到 MISSION_REQUEST 则推进状态 / 超时则重试(最多 5 次)→ 完成后 emit sendComplete(error)。
模式六:插件工厂
FirmwarePluginManager::firmwarePluginForAutopilot() 根据心跳中的 MAV_AUTOPILOT 和 MAV_TYPE 返回单例插件实例,Vehicle 和 MissionCommandTree 通过插件获取固件特定的命令名称、参数元数据、飞行模式列表等。
3.4 QML 类型注册
QGCApplication 构造函数中大量调用 qmlRegisterType / qmlRegisterUncreatableType,将 C++ 控制器注册到 QML 引擎。例如:
QGroundControl.Vehicle--- Vehicle 类型(不可直接创建,由 MultiVehicleManager 管理)QGroundControl.FactSystem--- Fact 相关类型QGroundControl.FlightMap--- 地图组件
QGroundControlQmlGlobal 通过 qmlRegisterSingletonType 注册为 QML 全局单例 QGroundControl,是所有 QML 页面的后端入口。
四、多线程任务调度设计
QGC 的线程模型遵循 "IO 密集任务独立线程,业务逻辑主线程" 原则,利用 Qt 事件循环和信号槽实现线程间协作。
4.1 线程全景
┌──────────────────────────────────────────────────────────────┐
│ 主线程 (GUI Thread) │
│ QGCApplication 事件循环 │
│ ├── QGCToolbox 所有 QGCTool (LinkManager, MAVLinkProtocol...) │
│ ├── Vehicle / ParameterManager / MissionController │
│ ├── QML 渲染引擎 │
│ └── QTimer 驱动的定时任务 (心跳、超时、自动连接) │
├──────────────────────────────────────────────────────────────┤
│ Link 线程 (每条链路一个 QThread) │
│ LinkInterface 子类 (SerialLink / UDPLink / TCPLink) │
│ ├── run() 或 moveToThread(this) 事件循环 │
│ ├── 阻塞/非阻塞 IO 读写 │
│ └── emit bytesReceived → 跨线程到 MAVLinkProtocol │
├──────────────────────────────────────────────────────────────┤
│ 专用工作线程 │
│ ├── GPSProvider (QThread) --- GPS/RTK 串口读取 │
│ ├── PX4FirmwareUpgradeThreadWorker --- 固件刷写 │
│ ├── VideoReceiver (GStreamer pipeline) --- 视频解码 │
│ ├── LogReplayLink --- 日志回放定时读取 │
│ ├── HIL Links (JSBSim/XPlane) --- 仿真通信 │
│ └── QThreadPool (QRunnable) --- 视频初始化等一次性任务 │
└──────────────────────────────────────────────────────────────┘
4.2 链路线程模型
LinkInterface 继承 QThread,是所有物理链路的基类:
31:32:src/comm/LinkInterface.h
class LinkInterface : public QThread
{
Q_OBJECT
UDP 链路在构造时将自己移到独立线程:
cpp
// UDPLink 构造函数中
moveToThread(this);
线程安全写操作通过信号中转:
152:155:src/comm/LinkInterface.h
void writeBytesSafe(const char *bytes, int length)
{
emit _invokeWriteBytes(QByteArray(bytes, length));
}
68:68:src/comm/LinkInterface.cc
QObject::connect(this, &LinkInterface::_invokeWriteBytes, this, &LinkInterface::_writeBytes);
任何线程调用 writeBytesSafe() 都是安全的:信号会被 Qt 事件系统投递到 Link 所在线程执行 _writeBytes()。
线程安全读操作 :Link 线程收到数据后直接 emit bytesReceived(this, data)。由于 MAVLinkProtocol::receiveBytes 连接为默认 AutoConnection,当发送方和接收方在不同线程时,Qt 自动使用 QueuedConnection,将字节数组拷贝到主线程队列中异步处理。
4.3 MAVLink 解析线程归属
MAVLinkProtocol 作为 QGCTool 运行在主线程。虽然 receiveBytes() 可能从 Link 线程被调用(QueuedConnection),但 mavlink_parse_char() 和 emit messageReceived() 最终在主线程执行。源码注释明确说明了设计意图:
"It buys us reentrancy for the whole code over all threads"
即:所有 MAVLink 消息的处理(Vehicle 状态更新、参数同步、任务协议)都在主线程完成,避免复杂的锁机制。代价是高频链路下主线程负载增加,但 GCS 场景下(通常 <100Hz 有效消息)完全可接受。
4.4 GPS/RTK 独立线程
GPSProvider 继承 QThread,在 run() 中阻塞读取 GPS 串口:
28:29:src/GPS/GPSProvider.h
class GPSProvider : public QThread
{
Q_OBJECT
57:61:src/GPS/GPSProvider.h
signals:
void positionUpdate(GPSPositionMessage message);
void satelliteInfoUpdate(GPSSatelliteMessage message);
void RTCMDataUpdate(QByteArray message);
void surveyInStatus(float duration, float accuracyMM, double latitude, double longitude, float altitude, bool valid, bool active);
GPSManager 在主线程接收这些信号,更新 RTK FactGroup,并通过 RTCMMavlink 将 RTCM 差分数据转发给飞行器。
停止 GPS 线程使用 std::atomic_bool _requestGpsStop 协作式退出,避免强制 terminate()。
4.5 固件升级工作线程
PX4 固件升级涉及串口 Bootloader 通信,耗时且可能阻塞,因此采用 Worker-Object + QThread 模式:
35:35:src/VehicleSetup/PX4FirmwareUpgradeThread.h
class PX4FirmwareUpgradeThreadWorker : public QObject
{
Q_OBJECT
Worker 对象被 moveToThread() 到专用 QThread,通过信号报告进度:
43:54:src/VehicleSetup/PX4FirmwareUpgradeThread.h
signals:
void updateProgress(int curr, int total);
void foundBoard(bool firstAttempt, const QGCSerialPortInfo& portInfo, int type, QString boardName);
void noBoardFound(void);
void boardGone(void);
void foundBootloader(int bootloaderVersion, int boardID, int flashSize);
void bootloaderSyncFailed(void);
void error(const QString& errorString);
void status(const QString& statusText);
void eraseStarted(void);
void eraseComplete(void);
void flashComplete(void);
UI 层的 FirmwareUpgradeController 在主线程连接这些信号更新 QML 进度条。
4.6 视频流线程
VideoManager 管理 VideoReceiver 实例。GStreamer pipeline 在 GStreamer 内部线程运行,帧回调通过 Qt 信号传递到主线程渲染。视频初始化被投递到线程池避免阻塞启动:
134:147:src/QGCApplication.cc
class FinishVideoInitialization : public QRunnable
{
public:
FinishVideoInitialization(VideoManager* manager)
: _manager(manager)
{}
void run () {
_manager->_initVideo();
}
private:
VideoManager* _manager;
};
4.7 定时器驱动的周期性任务
QGC 大量使用 QTimer 实现轻量级调度,无需额外线程:
| 定时器 | 所属类 | 周期 | 用途 |
|---|---|---|---|
| GCS Heartbeat | MultiVehicleManager |
~1s | 向所有链路广播 GCS 心跳 |
| AutoConnect | LinkManager |
~1s | 扫描串口/USB 自动连接 |
| Link Active Check | LinkManager |
可变 | 检测 USB Direct 链路活动 |
| MAVLink Message Timer | LinkInterface |
3s | 检测 Vehicle 通信丢失 |
| PlanManager ACK Timeout | PlanManager |
250-1500ms | 任务协议超时重试 |
| MAV Command ACK | Vehicle |
可变 | 命令重发 |
| PreArm Error | Vehicle |
单次 | PreArm 错误自动清除 |
| FactGroup Update | FactGroup |
可配置 | 遥测 UI 刷新节流 |
| Log Replay | LogReplayLink |
按日志时间戳 | 遥测回放 |
MultiVehicleManager 的 GCS 心跳定时器示例:
47:52:src/Vehicle/MultiVehicleManager.cc
_gcsHeartbeatTimer.setInterval(_gcsHeartbeatRateMSecs);
_gcsHeartbeatTimer.setSingleShot(false);
connect(&_gcsHeartbeatTimer, &QTimer::timeout, this, &MultiVehicleManager::_sendGCSHeartbeat);
if (_gcsHeartbeatEnabled) {
_gcsHeartbeatTimer.start();
}
4.8 MockLink 仿真调度
单元测试用的 MockLink 使用多频率定时器模拟飞控行为:
cpp
QTimer timer1HzTasks; // 1Hz 任务
QTimer timer10HzTasks; // 10Hz 任务
QTimer timer500HzTasks; // 500Hz 任务
这展示了 QGC 如何用 Qt 定时器模拟不同频率的 MAVLink 消息流,而无需真实硬件。
4.9 线程安全设计原则总结
- IO 隔离:所有串口/网络 IO 在 Link 独立线程,不阻塞 GUI。
- 消息归主:MAVLink 解析和业务处理在主线程,简化同步。
- 信号中转 :跨线程通信用
emit signal+QueuedConnection,禁止跨线程直接访问 QObject。 - 写操作保护 :
writeBytesSafe()+_invokeWriteBytes模式确保链路写线程安全。 - 互斥锁辅助 :Link 的数据速率统计使用
QMutexLocker(_dataRateMutex),UDP 目标列表使用_sessionTargetsMutex。 - 原子变量 :GPS 线程停止标志使用
std::atomic_bool。 - 主线程 UI :所有 QML 弹窗、消息框通过
xxxOnMainThread槽函数触发。
五、典型业务流程串联
5.1 从启动到连接飞行器
1. main() → QGCApplication 构造
2. new QGCToolbox → 创建所有 QGCTool
3. setChildToolboxes() → 注入依赖、建立信号连接
4. QQmlApplicationEngine 加载 MainRootWindow.qml
5. LinkManager 自动连接 / 用户手动添加链路
6. LinkInterface 线程建立连接 → emit connected
7. Link 收到 Heartbeat → bytesReceived → MAVLinkProtocol 解析
8. emit vehicleHeartbeatInfo → MultiVehicleManager 创建 Vehicle
9. Vehicle 构造:连接 messageReceived、创建 ParameterManager/UAS/Plugins
10. ParameterManager 请求参数 → parametersReady → UI 可用
11. QML activeVehicle 绑定生效 → Fly View 显示遥测
5.2 任务上传到飞行器
1. 用户在 PlanView 编辑航点 → MissionController 维护 VisualMissionItem 列表
2. 点击 Upload → PlanMasterController.sendToVehicle()
3. MissionController 生成 MissionItem 列表
4. PlanManager.writeMissionItems() 启动 MAVLink Mission Protocol 状态机
5. 逐条发送 MISSION_ITEM → 等待 MISSION_REQUEST / MISSION_ACK
6. QTimer 超时重试机制保障可靠性
7. emit sendComplete(false) → UI 显示上传成功
5.3 参数修改
1. QML ParameterEditor 修改 Fact.cookedValue
2. Fact emit valueChanged → ParameterManager 检测到用户写入
3. 发送 PARAM_SET MAVLink 消息
4. 等待 PARAM_VALUE 确认
5. 更新本地 Fact 值 + 磁盘缓存
六、扩展与定制指南
6.1 添加新飞控支持
- 在
FirmwarePlugin/下创建新插件类,继承FirmwarePlugin+FirmwarePluginFactory - 在
AutoPilotPlugins/下创建对应AutoPilotPlugin - 在
qgroundcontrol.pro中添加编译配置 - 提供参数元数据 XML 和 MAVLink 命令 JSON
6.2 OEM 定制
- 创建
custom/custom.pri,定义CUSTOMCLASS和CUSTOMHEADER - 继承
QGCCorePlugin,覆盖settingsPages、options、brandImageIndoor等 - 替换
custom/qgroundcontrol.qrc中的资源
6.3 添加新 QML 页面
- 在
src/相应目录创建 QML + Controller - 在
QGCApplication.cc中qmlRegisterType - 通过
QGCCorePlugin::analyzePages或settingsPages注册入口
七、总结
QGroundControl 4.0 是一个 Qt 原生、插件化、MAVLink 通用 的地面站架构:
| 维度 | 设计要点 |
|---|---|
| 分层 | UI(QML) → 业务(Vehicle/Plugin) → 通信(Link/MAVLink) → 数据(Fact/Settings) |
| 组织 | QGCToolbox 统一管理 20+ 顶层服务,两阶段构造避免循环依赖 |
| 扩展 | FirmwarePlugin + AutoPilotPlugin 双插件体系隔离飞控差异 |
| 数据 | Fact/FactGroup 统一建模,Q_PROPERTY 驱动 QML 绑定 |
| 通信 | Link 线程 IO + 主线程 MAVLink 解析 + 信号广播分发 |
| 线程 | IO 隔离、消息归主、信号中转、定时器调度 |
理解以上架构后,可按 "从 QGCToolbox 找到服务 → 从 Vehicle 找到业务 → 从 Fact 找到数据 → 从信号找到流程" 的路径快速定位任意功能的实现代码。