当前位置: 首页 > news >正文

QGC地图界面自定义数据面板开发实战

1. 理解QGC地图界面自定义数据面板的需求

第一次接触QGroundControl(QGC)地图界面自定义数据面板开发时,我完全被各种技术术语绕晕了。后来在实际项目中才发现,这个功能对于无人机开发者来说简直是刚需。想象一下,你正在开发一个农业植保无人机,除了常规的飞行高度、速度外,还需要实时显示喷洒量、作业面积等专业数据。这时候默认的QGC界面就显得力不从心了。

QGC的地图界面默认显示的是基础飞行数据,比如高度、速度、GPS信息等。但实际业务中,我们往往需要展示更多自定义数据。比如:

  • 环境监测无人机需要显示PM2.5、温湿度等传感器数据
  • 测绘无人机需要实时显示拍摄张数、覆盖面积
  • 物流无人机需要展示货物重量、配送状态

这些需求都需要我们扩展QGC的地图数据显示能力。好消息是,QGC提供了完善的扩展机制,允许开发者通过FactGroup体系添加自定义数据。我去年给一个光伏巡检项目做开发时,就成功实现了组件温度、缺陷数量等专业数据的实时展示。

2. 搭建开发环境与基础准备

工欲善其事,必先利其器。在开始编码前,我们需要准备好开发环境。根据我的经验,最稳定的组合是:

  • Ubuntu 18.04 LTS(20.04也行,但需要处理更多依赖问题)
  • Qt 5.13.2开发套件
  • QGC 4.1.16源码

安装Qt时有个小技巧:建议使用官方在线安装器,勾选以下组件:

  • Qt 5.13.2 → Desktop gcc 64-bit
  • Qt Charts
  • Qt Quick 2D Renderer

我第一次搭建环境时,因为漏装了Qt Charts,导致编译时各种奇怪的链接错误。后来发现QGC的很多可视化组件都依赖这个模块。另外,建议预留至少30GB磁盘空间,因为Qt和QGC的编译会产生大量中间文件。

获取QGC源码后,先别急着修改。我建议先尝试编译原始版本,确保基础环境没问题。编译命令很简单:

mkdir build cd build qmake ../qgroundcontrol.pro make -j$(nproc)

如果编译成功,你会看到生成的qgroundcontrol可执行文件。这时候运行它,应该能看到完整的QGC界面。这个验证步骤很重要,可以避免后续开发中遇到环境问题。

3. 创建自定义FactGroup数据组

FactGroup是QGC中管理数据的基本单元,相当于一个数据容器。我们要显示自定义数据,首先得创建自己的FactGroup。以显示四个测试数据为例,我们需要创建两个文件:TestInfoFactGroup.h和TestInfoFactGroup.cc。

在头文件中,我们定义数据结构和处理逻辑:

// TestInfoFactGroup.h #pragma once #include "FactGroup.h" #include "QGCMAVLink.h" class TestInfoFactGroup : public FactGroup { Q_OBJECT public: TestInfoFactGroup(QObject* parent = nullptr); Q_PROPERTY(Fact* temperature READ temperature CONSTANT) Q_PROPERTY(Fact* humidity READ humidity CONSTANT) Q_PROPERTY(Fact* pressure READ pressure CONSTANT) Q_PROPERTY(Fact* airQuality READ airQuality CONSTANT) Fact* temperature() { return &_temperatureFact; } Fact* humidity() { return &_humidityFact; } Fact* pressure() { return &_pressureFact; } Fact* airQuality() { return &_airQualityFact; } void handleMessage(Vehicle* vehicle, mavlink_message_t& message) override; private: Fact _temperatureFact; Fact _humidityFact; Fact _pressureFact; Fact _airQualityFact; static const char* _temperatureFactName; static const char* _humidityFactName; static const char* _pressureFactName; static const char* _airQualityFactName; };

实现文件中,我们需要初始化各个Fact并处理MAVLink消息:

// TestInfoFactGroup.cc #include "TestInfoFactGroup.h" #include "Vehicle.h" const char* TestInfoFactGroup::_temperatureFactName = "temperature"; const char* TestInfoFactGroup::_humidityFactName = "humidity"; const char* TestInfoFactGroup::_pressureFactName = "pressure"; const char* TestInfoFactGroup::_airQualityFactName = "airQuality"; TestInfoFactGroup::TestInfoFactGroup(QObject* parent) : FactGroup(1000, ":/json/Vehicle/TestInfoFact.json", parent) , _temperatureFact(0, _temperatureFactName, FactMetaData::valueTypeDouble) , _humidityFact(0, _humidityFactName, FactMetaData::valueTypeDouble) , _pressureFact(0, _pressureFactName, FactMetaData::valueTypeDouble) , _airQualityFact(0, _airQualityFactName, FactMetaData::valueTypeUint32) { _addFact(&_temperatureFact, _temperatureFactName); _addFact(&_humidityFact, _humidityFactName); _addFact(&_pressureFact, _pressureFactName); _addFact(&_airQualityFact, _airQualityFactName); // 初始化默认值 _temperatureFact.setRawValue(qQNaN()); _humidityFact.setRawValue(qQNaN()); _pressureFact.setRawValue(qQNaN()); _airQualityFact.setRawValue(0); } void TestInfoFactGroup::handleMessage(Vehicle*, mavlink_message_t& message) { if (message.msgid != MAVLINK_MSG_ID_SCALED_PRESSURE) { return; } mavlink_scaled_pressure_t pressure; mavlink_msg_scaled_pressure_decode(&message, &pressure); temperature()->setRawValue(pressure.temperature / 100.0f); pressure()->setRawValue(pressure.press_abs); _setTelemetryAvailable(true); }

这里有几个关键点需要注意:

  1. Fact的valueType必须与数据类型匹配,比如浮点数用valueTypeDouble,整数用valueTypeUint32
  2. 更新频率(构造函数中的1000)表示数据刷新间隔,单位毫秒
  3. handleMessage方法中需要过滤和解析特定的MAVLink消息

4. 集成FactGroup到QGC主框架

创建好FactGroup后,我们需要把它集成到QGC的主框架中。这个过程就像给房子安装新的水电线路,需要找到正确的接入点。

首先修改Vehicle.h,添加我们的FactGroup:

// 在文件头部添加包含 #include "TestInfoFactGroup.h" // 在类定义中添加成员变量 TestInfoFactGroup _testInfoFactGroup; // 添加静态名称定义 static const char* _testInfoFactGroupName; // 添加访问方法 FactGroup* testInfoFactGroup() { return &_testInfoFactGroup; } // 添加Q_PROPERTY声明 Q_PROPERTY(FactGroup* testInfo READ testInfoFactGroup CONSTANT)

然后在Vehicle.cc中进行初始化:

// 定义静态名称 const char* Vehicle::_testInfoFactGroupName = "testInfo"; // 在构造函数初始化列表中添加 , _testInfoFactGroup(this) // 在初始化方法中添加注册 _addFactGroup(&_testInfoFactGroup, _testInfoFactGroupName);

这里最容易出错的是初始化顺序。我遇到过因为初始化顺序不对导致的段错误。记住一个原则:基础组件先初始化,依赖组件后初始化。如果遇到崩溃问题,可以检查构造函数初始化列表的顺序。

最后,别忘了在qgroundcontrol.pro中添加我们的源文件:

# 在HEADERS部分添加 HEADERS += \ src/Vehicle/TestInfoFactGroup.h \ ... # 在SOURCES部分添加 SOURCES += \ src/Vehicle/TestInfoFactGroup.cc \ ...

5. 设计数据元信息与JSON配置

QGC使用JSON文件来定义数据的显示属性,这就像给数据穿衣服,决定它们在前端如何展示。创建TestInfoFact.json文件:

{ "version": 1, "fileType": "FactMetaData", "QGC.MetaData.Facts": [ { "name": "temperature", "shortDesc": "温度", "type": "double", "decimalPlaces": 1, "units": "°C" }, { "name": "humidity", "shortDesc": "湿度", "type": "double", "decimalPlaces": 1, "units": "%" }, { "name": "pressure", "shortDesc": "气压", "type": "double", "decimalPlaces": 2, "units": "hPa" }, { "name": "airQuality", "shortDesc": "空气质量", "type": "uint32", "units": "AQI" } ] }

这个配置文件有几个关键字段:

  • name:必须与代码中的Fact名称完全一致
  • shortDesc:前端显示的标签文本
  • type:数据类型,与代码中的valueType对应
  • decimalPlaces:小数位数(仅数值类型有效)
  • units:单位符号,会显示在值后面

把JSON文件添加到资源系统中也很重要。打开qgroundcontrol.qrc文件,添加我们的JSON文件:

<qresource prefix="/"> ... <file alias="Vehicle/TestInfoFact.json">src/Vehicle/TestInfoFact.json</file> ... </qresource>

这里alias的路径必须与FactGroup构造函数中指定的路径一致。我遇到过因为路径大小写不一致导致找不到文件的问题,在Linux环境下尤其要注意。

6. 前端界面集成与数据绑定

数据准备好了,现在要让它们在地图界面上显示出来。QGC使用QML作为前端技术,我们需要修改地图页面的QML文件。

找到src/uis/Vehicle/VehicleValues.qml文件,添加我们的数据显示组件:

// 在适当位置添加我们的数据显示项 VehicleValuesGroup { name: qsTr("环境数据") width: parent.width VehicleValuesRow { VehicleValuesColumn { FactValue { fact: vehicle.testInfo.temperature showUnits: true } Label { text: qsTr("温度") } } VehicleValuesColumn { FactValue { fact: vehicle.testInfo.humidity showUnits: true } Label { text: qsTr("湿度") } } } VehicleValuesRow { VehicleValuesColumn { FactValue { fact: vehicle.testInfo.pressure showUnits: true } Label { text: qsTr("气压") } } VehicleValuesColumn { FactValue { fact: vehicle.testInfo.airQuality showUnits: true } Label { text: qsTr("空气质量") } } } }

QML的布局系统需要一些时间来适应。我的经验是:

  1. VehicleValuesGroup是一个垂直容器
  2. VehicleValuesRow代表一行
  3. VehicleValuesColumn代表一行中的一列
  4. 每列通常包含一个FactValue和一个Label

调试QML界面时,可以使用Qt Creator的QML调试器。设置环境变量QML_DEBUG=true后启动QGC,就能在Qt Creator中实时查看和修改QML属性。

7. 测试与调试技巧

完成代码编写后,就是紧张的测试阶段了。我总结了一套有效的测试方法:

编译检查

make clean qmake ../qgroundcontrol.pro make -j$(nproc)

如果编译失败,重点关注:

  1. 头文件包含是否正确
  2. 初始化顺序是否合理
  3. 是否有拼写错误

运行时测试

  1. 启动QGC并连接飞控或模拟器
  2. 在地图界面检查自定义数据是否显示
  3. 发送测试MAVLink消息,验证数据更新

常见问题排查:

  1. 数据不显示:检查JSON文件路径是否正确,Q_PROPERTY声明是否完整
  2. 数据显示NaN:检查handleMessage是否被调用,数据解析是否正确
  3. 界面布局错乱:检查QML层次结构,确保宽度设置合理

一个实用的调试技巧是在代码中添加qDebug()输出:

qDebug() << "收到MAVLink消息,msgid:" << message.msgid;

可以在启动QGC时加上参数查看日志:

./qgroundcontrol --logging:full

8. 进阶优化与扩展思路

基础功能实现后,可以考虑进一步优化:

数据持久化

// 在FactGroup中添加保存/加载方法 void saveToSettings(QSettings& settings); void loadFromSettings(QSettings& settings);

动态数据更新

// 使用ChartView实现实时曲线 ChartView { LineSeries { name: "温度变化" axisX: ValueAxis { min: 0; max: 100 } axisY: ValueAxis { min: -20; max: 50 } // 动态添加数据点 } }

多语言支持

{ "shortDesc": { "en": "Temperature", "zh": "温度" } }

性能优化

  1. 合理设置更新频率,避免不必要的重绘
  2. 使用Qt的模型/视图框架处理大量数据
  3. 考虑使用OpenGL加速图形渲染

在实际项目中,我还遇到过需要动态显示/隐藏某些数据的需求。这可以通过QML的visible属性和Fact的可用性状态来实现:

FactValue { fact: vehicle.testInfo.temperature visible: fact.available }
http://www.jsqmd.com/news/527501/

相关文章:

  • RePKG突破Wallpaper Engine资源壁垒:解锁动态壁纸创作新可能
  • 支付宝红包套装闲置不用愁?可可收一键变现,解锁福利新玩法 - 可可收
  • 2026湖南古法炭烤手撕鸭实力厂商五强甄选与深度解析报告 - 2026年企业推荐榜
  • Verilog ISP仿真框架搭建实战:从RAW到YUV的全流程解析(附完整代码)
  • AMT102磁性编码器驱动设计与实时角度反馈实现
  • Ostrakon-VL-8B基础教程:app.py源码解析与Gradio接口自定义扩展方法
  • Selenium报错‘This version of ChromeDriver only supports Chrome version XX’?5分钟教你彻底排查与修复
  • 巨人网络发布“全时智能”客服退款投诉方案快速提升效率畅通 - 王老吉弄
  • Qwen2.5-0.5B Instruct法律文书生成:合同条款智能起草
  • Qt 开发机器人客户端程序
  • 小型项目选择2核2G云服务器够用吗?
  • 改进型可调整步长PO MPPT在光储系统中的应用:二区MPPT复现与直流负载供电单级离网光伏系统
  • TSL2561光强传感器驱动开发与嵌入式工程实践
  • Roo Code深度调教指南:如何用自定义模式+提示词打造你的前端/后端/测试专属AI助手
  • 2026年工业级白油厂家推荐:潍坊晨星化工科技,化妆级白油/食品级白油/硅酮胶专用白油厂家精选 - 品牌推荐官
  • 三相并网逆变器:电网电压690V高规格,1.5MW大容量直流源稳定供电系统
  • StarUML实战:手把手教你绘制电商系统数据流图(含常见错误排查)
  • 办公家具工厂直供选购指南:避开3大陷阱,选对省心方案 - 速递信息
  • 亲测好用! 降AIGC软件 千笔·专业降AIGC智能体 VS speedai 专为毕业论文全流程设计
  • Wemos Matrix Adafruit GFX:HT16K33点阵的GFX图形接口实现
  • 重构OpenCore配置:OpCore-Simplify全流程自动化指南
  • SVG无功补偿技术实现:定电压控制,电网电压调控灵活且迅速启动无功补偿装置优化电网响应性能
  • 多线程环境下malloc死锁的5种常见场景及避坑指南(含__lll_lock_wait_private分析)
  • 2025国内Docker镜像加速全攻略:精选源与配置实战
  • 防反接电路:背靠背Pmos组成理想二极管
  • WuliArt Qwen-Image Turbo镜像优势解析:免编译、免依赖、开箱即用设计哲学
  • 瑞祥商联卡回收避坑指南:这样做安全又省心 - 团团收购物卡回收
  • # Bun 项目实战:从零搭建高性能 Node.js 替代方案,性能提升 3
  • 造相 Z-Image 应用场景:IP形象延展设计|从线稿到多风格角色图生成
  • 黑苹果系统配置难题:如何用自动化工具解决硬件兼容性与EFI配置挑战?