2. QGC源码结构解析
做航线规划插件,说白了你得先搞清楚QGC这个大家伙是怎么搭起来的。我刚开始接触QGC源码时,第一反应是「这玩意儿也太大了」——几百个类、上千个文件,从哪下手?
后来我总结出一个经验:别试图一次看懂所有代码。你只需要抓住三个关键点——主框架怎么启动、模块怎么划分、核心类怎么协作。今天我们就围绕这三块来拆解。
2.1 QGC主框架:从main()到界面显示
QGC的入口在 src/main.cpp。嗯,这里其实没什么魔法,就是一个标准的Qt应用启动流程:
// 简化后的main函数逻辑
int main(int argc, char *argv[])
{
QGCApplication app(argc, argv); // 自定义QApplication子类
app.initialize(); // 初始化:日志、设置、插件系统
MainWindow window; // 主窗口
window.show();
return app.exec();
}
我个人习惯把QGC的启动分为三个阶段:
| 阶段 | 关键操作 | 涉及类 |
|---|---|---|
| 初始化 | 加载设置、初始化日志、注册QML类型 | QGCApplication |
| 插件加载 | 扫描插件目录、加载自定义插件 | QGCCorePlugin、FactSystem |
| 界面构建 | 创建主窗口、加载QML页面 | MainWindow、QmlApplication |
小技巧:调试时可以在
main.cpp 里加个 qDebug() 输出启动耗时。我曾经发现插件加载占了启动时间的40%,后来优化了插件初始化逻辑,启动快了将近一倍。
2.2 模块划分:QGC的「五脏六腑」
QGC的源码目录结构其实挺清晰的。我把它画成了一张图,你一看就明白:
你看,整个架构是分层解耦的。通信层只管收发MAVLink消息,业务层只管处理航线逻辑,UI层只管展示。各层之间通过信号与槽沟通——这也是Qt最强大的地方。
2.3 核心类:Vehicle 和 MissionController
做航线规划插件,你打交道最多的就是这两个类。我分别说说。
2.3.1 Vehicle:无人机的「数字孪生」
Vehicle 类在 src/Vehicle/Vehicle.h,它代表一架真实的无人机。说白了,QGC里所有关于飞机的信息——位置、姿态、电池、航线——都挂在这个类上。
关键成员:
class Vehicle : public QObject
{
Q_OBJECT
public:
// 无人机状态
VehicleState* vehicleState(); // 位置、姿态、速度
FactSystem* factSystem(); // 参数系统(PID、飞控参数)
MissionManager* missionManager(); // 航线管理器
// 通信
void sendMavCommand(MAV_CMD cmd, ...); // 发送MAVLink命令
LinkInterface* activeLink(); // 当前通信链路
signals:
void armedChanged(bool armed); // 解锁状态变化
void flightModeChanged(const QString& mode); // 飞行模式变化
void missionChanged(); // 航线变化
};
重点:Vehicle 是单例模式。整个QGC只有一个Vehicle实例(除非你连了多架飞机)。所有模块都通过
QGCApplication::vehicle() 获取它。
2.3.2 MissionController:航线的「大脑」
MissionController 在 src/MissionManager/MissionController.h。它负责管理整条航线——增删改查航点、上传下载、校验合法性。
我当初做自定义航线插件时,就是在这个类上扩展的。它的核心逻辑:
class MissionController : public QObject
{
Q_OBJECT
public:
// 航线操作
void insertMissionItem(int index, MissionItem* item);
void removeMissionItem(int index);
void clearMission();
// 上传/下载
void startUpload(); // 上传航线到飞控
void startDownload(); // 从飞控下载航线
// 校验
bool isMissionValid(); // 航线是否合法
signals:
void missionItemsChanged(); // 航点列表变化
void uploadProgress(int percent);
void downloadComplete();
private:
QList<MissionItem*> m_missionItems; // 航点列表
Vehicle* m_vehicle; // 关联的无人机
};
避坑指南:我曾经在自定义航点时,直接修改了
m_missionItems 列表,但没有发射 missionItemsChanged() 信号。结果UI死活不刷新,排查了半小时才发现。记住:修改数据后一定要发射对应的信号。
2.4 信号与槽机制:模块间的「神经纤维」
QGC里到处都是信号与槽。你想想看,通信层收到一条MAVLink消息,怎么通知UI更新?就是靠信号。
一个典型的流程:
- 通信层收到MAVLink消息 → 解析 → 发射
mavlinkMessageReceived() - Vehicle 接收信号 → 更新内部状态 → 发射
armedChanged() - MissionController 接收信号 → 更新航线数据 → 发射
missionItemsChanged() - UI层 接收信号 → 刷新地图/列表显示
这种链式传递的好处是:每个模块只关心自己那一段。比如你要加一个新功能,只需要在合适的位置监听或发射信号,不用改别人的代码。
我的习惯:写插件时,我会先画一张「信号流图」——哪些信号我需要监听,哪些信号我需要发射。画清楚了再动手写代码,效率高很多。
2.5 本章小结
好了,我们快速过一下这章的核心:
- 主框架:从
main()到MainWindow,分初始化、插件加载、界面构建三个阶段 - 模块划分:通信层、业务逻辑层、UI层,各层通过信号与槽解耦
- 核心类:
Vehicle是无人机数字孪生,MissionController是航线大脑 - 信号与槽:模块间通信的「总线」,修改数据后记得发射信号