news 2026/10/30 19:15:23

PyQt+QML 实现飞控仪表盘:姿态、高度、速度,飞行数据等 实时显示(三)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyQt+QML 实现飞控仪表盘:姿态、高度、速度,飞行数据等 实时显示(三)

PyQt+QML 实现飞控仪表盘:姿态、高度、速度,飞行数据等 实时显示(三)

  • 一、说明
  • 二、效果展示
  • 三、源码分享
    • 1、工程结构
    • 2、库安装
    • 3、main.py
    • 4、main.qml
    • 5、Vsi.qml
    • 6、图片资源下载
  • 四、实现原理
    • 1、整体数据流
    • 2、QML 属性绑定机制
    • 3、仪表绘制原理
    • 4、定时刷新与实时性
    • 5、扩展:接入真实飞行数据
    • 6、小结

一、说明

由于篇幅限制此篇只显示3个仪表,剩余见下一篇

二、效果展示


三、源码分享

1、工程结构

2、库安装

3、main.py

importsysimportosfromPyQt6.QtCoreimportQUrlfromPyQt6.QtGuiimportQGuiApplicationfromPyQt6.QtQmlimportQQmlApplicationEnginedefmain():app=QGuiApplication(sys.argv)engine=QQmlApplicationEngine()qml_path=os.path.join(os.path.dirname(os.path.abspath(__file__)),"main.qml")engine.load(QUrl.fromLocalFile(qml_path))ifnotengine.rootObjects():print("错误:无法加载 QML 文件",file=sys.stderr)sys.exit(-1)sys.exit(app.exec())if__name__=="__main__":main()

4、main.qml

importQtQuickimportQtQuick.WindowimportQtQuick.ControlsimportQtQuick.Layouts ApplicationWindow{id:root visible:true width:1200height:900title:"Flight Instruments"color:"#1a1a2e"//Flight parameters(driven by test timerormanual control)propertyreal alpha:0propertyreal beta:0propertyreal roll:0propertyreal pitch:0propertyreal heading:0propertyreal slipSkid:0propertyreal turnRate:0propertyreal devH:0propertyreal devV:0propertyreal airspeed:0propertyreal altitude:0propertyreal pressure:30.0propertyreal climbRate:0propertyreal machNo:0propertyreal adf:0propertyreal dme:0//Simulation statepropertyboolautoMode:truepropertyreal realTime:0//3x3 Grid layout GridLayout{anchors.fill:parent columns:3rows:3rowSpacing:4columnSpacing:4//Row1:ADI,ALT,ASI Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"ADI"color:"#aaaacc"font.pixelSize:12z:10}Adi{anchors.fill:parent roll:root.roll pitch:root.pitch}}Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"ALT"color:"#aaaacc"font.pixelSize:12z:10}Alt{anchors.fill:parent altitude:root.altitude pressure:root.pressure}}Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"ASI"color:"#aaaacc"font.pixelSize:12z:10}Asi{anchors.fill:parent airspeed:root.airspeed}}//Row2:HSI,TC,NAV Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"HSI"color:"#aaaacc"font.pixelSize:12z:10}Hsi{anchors.fill:parent heading:root.heading}}Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"TC"color:"#aaaacc"font.pixelSize:12z:10}Tc{anchors.fill:parent turnRate:root.turnRate slipSkid:root.slipSkid*15.0}}Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"NAV"color:"#aaaacc"font.pixelSize:12z:10}Nav{anchors.fill:parent heading:root.heading headingBug:0course:0bearing:root.adf bearingVisible:true deviation:root.devH deviationVisible:true distance:root.dme distanceVisible:true}}//Row3:VSI,Controls Item{Layout.fillWidth:true Layout.fillHeight:true Label{anchors.top:parent.top anchors.horizontalCenter:parent.horizontalCenter text:"VSI"color:"#aaaacc"font.pixelSize:12z:10}Vsi{anchors.fill:parent climbRate:root.climbRate}}//Control Panel-spans2columns Item{Layout.columnSpan:2Layout.fillWidth:true Layout.fillHeight:true Rectangle{anchors.fill:parent anchors.margins:8color:"#16213e"radius:8border.color:"#334466"border.width:1Column{anchors.centerIn:parent spacing:12width:parent.width*0.8Label{text:"Flight Instruments Test Panel"color:"#ffffff"font.pixelSize:16font.bold:true anchors.horizontalCenter:parent.horizontalCenter}Button{text:root.autoMode ?"⏸ Stop Auto":"▶ Start Auto"anchors.horizontalCenter:parent.horizontalCenter width:200palette.button:root.autoMode ?"#e94560":"#0f3460"palette.buttonText:"#ffffff"onClicked:root.autoMode=!root.autoMode}Grid{columns:3spacing:8anchors.horizontalCenter:parent.horizontalCenter Label{text:"Roll:";color:"#aaaacc";font.pixelSize:11}Label{text:roll.toFixed(1)+"°";color:"#ffffff";font.pixelSize:11}Label{text:"";color:"#aaaacc";font.pixelSize:11}Label{text:"Pitch:";color:"#aaaacc";font.pixelSize:11}Label{text:pitch.toFixed(1)+"°";color:"#ffffff";font.pixelSize:11}Label{text:"";color:"#aaaacc";font.pixelSize:11}Label{text:"Heading:";color:"#aaaacc";font.pixelSize:11}Label{text:heading.toFixed(1)+"°";color:"#ffffff";font.pixelSize:11}Label{text:"";color:"#aaaacc";font.pixelSize:11}Label{text:"Speed:";color:"#aaaacc";font.pixelSize:11}Label{text:airspeed.toFixed(0)+" kt";color:"#ffffff";font.pixelSize:11}Label{text:"Alt:";color:"#aaaacc";font.pixelSize:11}Label{text:altitude.toFixed(0)+" ft";color:"#ffffff";font.pixelSize:11}Label{text:"V/S:";color:"#aaaacc";font.pixelSize:11}Label{text:climbRate.toFixed(0)+" ft/m";color:"#ffffff";font.pixelSize:11}}}}}}//Test Timer-generates sinusoidal flight data(portedfromMainWindow.cpp)Timer{id:testTimer interval:50repeat:true running:true triggeredOnStart:false onTriggered:{if(!root.autoMode)returnconst dt=interval/1000.0root.realTime+=dt const t=root.realTime root.alpha=20.0*Math.sin(t/10.0)root.beta=15.0*Math.sin(t/10.0)root.roll=180.0*Math.sin(t/10.0)root.pitch=90.0*Math.sin(t/20.0)root.heading=360.0*Math.sin(t/40.0)root.slipSkid=1.0*Math.sin(t/10.0)root.turnRate=7.0*Math.sin(t/10.0)root.devH=1.0*Math.sin(t/20.0)root.devV=1.0*Math.sin(t/20.0)root.airspeed=125.0*Math.sin(t/40.0)+125.0root.altitude=9000.0*Math.sin(t/40.0)+9000.0root.pressure=2.0*Math.sin(t/20.0)+30.0root.climbRate=650.0*Math.sin(t/20.0)root.machNo=root.airspeed/650.0root.adf=-360.0*Math.sin(t/50.0)root.dme=99.0*Math.sin(t/100.0)}}}

5、Vsi.qml

importQtQuick Item{id:root//Vertical Speed Indicator//Original size:240x240propertyreal climbRate:0//ft/min,range:-2000to2000readonlypropertyreal origW:240readonlypropertyreal origH:240propertyreal s:Math.min(width/origW,height/origH)Item{width:root.origW height:root.origH scale:root.s anchors.centerIn:parent//Face(z:-20)Image{width:240height:240source:"image/vsi/vsi_face.svg"fillMode:Image.PreserveAspectFit}//Hand(z:-10)-rotates by climbRate*0.086Image{id:vsiHand width:240height:240source:"image/vsi/vsi_hand.svg"fillMode:Image.PreserveAspectFit transformOrigin:Item.Center rotation:{var clamped=Math.max(-2000,Math.min(2000,root.climbRate))returnclamped*0.086}}//Case(z:10)Image{width:240height:240source:"image/vsi/vsi_case.svg"fillMode:Image.PreserveAspectFit}}}

6、图片资源下载

https://download.csdn.net/download/qq_15181569/93089298

四、实现原理

本项目的核心思路是PyQt6 负责程序入口与 QML 引擎加载,QML 负责界面绘制与飞行数据驱动,两者通过 Qt 的属性绑定机制实现数据与界面的解耦。下面从数据流、仪表绘制、定时刷新三个层面展开说明。

1、整体数据流

整个系统采用单向数据流设计,数据从main.qml顶层的ApplicationWindow属性出发,向下传递给各个仪表组件:

Timer 定时器
每 50ms 触发一次

更新 root 属性
roll / pitch / altitude / airspeed 等

QML 属性绑定
子组件自动感知变化

仪表指针/刻度
旋转与位移

  • 数据源:main.qml中的Timer组件每 50ms 触发一次onTriggered,用正弦函数模拟飞行数据(如roll = 180 * sin(t/10))。
  • 数据分发:所有模拟数据写入ApplicationWindow的property real属性中,例如roll、pitch、airspeed、altitude、climbRate等。
  • 数据消费:各仪表组件(Adi、Alt、Asi、Vsi等)通过属性绑定(如roll: root.roll)自动接收最新值,无需手动刷新界面。

2、QML 属性绑定机制

QML 最强大的特性之一是属性绑定(Property Binding)。当绑定表达式中引用的属性发生变化时,绑定会自动重新求值,并更新界面。

以Vsi.qml为例:

property real climbRate: 0 // 外部传入的升降速率 Image { id: vsiHand rotation: { var clamped = Math.max(-2000, Math.min(2000, root.climbRate)) return clamped * 0.086 } }
  • climbRate是Vsi组件对外暴露的属性,由main.qml中Vsi { climbRate: root.climbRate }绑定传入。
  • 当root.climbRate变化时,vsiHand.rotation的绑定表达式自动重新计算,指针随之旋转。
  • 0.086是刻度系数,将-2000 ~ 2000 ft/min的输入映射到约-172° ~ 172°的指针转角,对应表盘刻度范围。

3、仪表绘制原理

每个仪表组件(如Vsi.qml)采用三层图片叠加 + 旋转变换的方式绘制:

图层作用z 值
表盘底图(vsi_face.svg)显示刻度、数字、单位-20
指针(vsi_hand.svg)随数据旋转,指示当前值-10
表壳(vsi_case.svg)覆盖指针转轴,增强立体感10

关键代码:

// 自适应缩放:以 240x240 为基准,按实际尺寸等比缩放 readonly property real origW: 240 readonly property real origH: 240 property real s: Math.min(width / origW, height / origH) Item { width: root.origW height: root.origH scale: root.s anchors.centerIn: parent // 三个 Image 图层叠加 }
  • scale: root.s实现仪表随容器大小自适应缩放,保证在不同窗口尺寸下不变形。
  • 指针旋转使用transformOrigin: Item.Center,以图片中心为旋转轴,符合仪表指针的物理行为。

4、定时刷新与实时性

main.qml中的Timer是数据驱动的核心:

Timer { id: testTimer interval: 50 // 50ms 刷新一次,即 20 FPS repeat: true running: true onTriggered: { if (!root.autoMode) return const dt = interval / 1000.0 root.realTime += dt const t = root.realTime // 用正弦函数模拟飞行数据变化 root.roll = 180.0 * Math.sin(t / 10.0) root.pitch = 90.0 * Math.sin(t / 20.0) root.airspeed = 125.0 * Math.sin(t / 40.0) + 125.0 root.altitude = 9000.0 * Math.sin(t / 40.0) + 9000.0 root.climbRate = 650.0 * Math.sin(t / 20.0) // ... 其余参数同理 } }
  • 刷新频率:50ms 间隔对应 20 FPS,兼顾流畅度与 CPU 占用。
  • 数据平滑:正弦函数保证数据连续变化,指针不会出现跳变。
  • 手动模式:点击控制面板的按钮可切换autoMode,关闭自动刷新后,可通过外部接口(如串口、UDP)注入真实飞行数据,实现真实场景接入。

5、扩展:接入真实飞行数据

当前使用Timer模拟数据,实际项目中只需将数据源替换为真实设备即可:

// 示例:通过信号槽接收外部数据 Connections { target: backendObj // PyQt6 暴露的 QObject function onFlightDataChanged(roll, pitch, airspeed, altitude, climbRate) { root.roll = roll root.pitch = pitch root.airspeed = airspeed root.altitude = altitude root.climbRate = climbRate } }

在main.py中,可通过engine.rootContext().setContextProperty("backendObj", backend)将 Python 对象注入 QML,实现 Python 与 QML 的双向通信,从而对接真实飞控数据。

6、小结

模块职责
main.py创建 QGuiApplication,加载 QML 引擎
main.qml定义窗口布局、数据属性、定时器、控制面板
Vsi.qml等仪表组件接收数据,通过图片叠加与旋转绘制仪表
属性绑定数据变化自动驱动界面刷新,无需手动更新

通过PyQt6 + QML 属性绑定 + 定时器驱动的组合,实现了高性能、易扩展的飞控仪表盘。后续接入真实数据时,只需替换数据源,界面层无需任何改动。

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

AI漫剧2026下半年实战:监管应对、模型选型与出海工业化

1. 从“能跑通”到“能赚钱”:AI漫剧产业进阶的底层逻辑2026年过半,如果你还在用“AI能不能做漫剧”这种问题去问圈内人,大概率会被笑话。真正在牌桌上的那批人,早就不纠结工具能不能出图、能不能配音了,他们焦虑的是另…

作者头像 李华
网站建设 2026/10/22 6:09:33

哪个写歌软件可以用自己声音?MELO音乐详解

直接结论 MELO音乐支持使用自己的声音创作歌曲。用户可上传本人干音训练专属AI音色,用于AI翻唱和原创歌曲演唱,并可自动修正音准与节奏。它还支持通过一句话、照片、歌词或哼唱生成完整歌曲,提供WAV、MP3、MIDI以及最高12轨分轨导出&#xff…

作者头像 李华
网站建设 2026/10/22 21:07:37

基于Python实现OCR:从选型到部署的完整实战指南

光学字符识别这件事,我在好几个项目里都落地过,从最早的扫描件文档电子化,到后来的票据自动录入、证件信息提取,再到给一些内部系统做图片文字检索,绕不开的核心工具就是 Python 生态里的那几套方案。很多人一提到 OCR…

作者头像 李华
网站建设 2026/10/22 0:38:07

【单片机课程设计/毕业设计】基于STM32的老人跌倒检测短信报警与位置定位系统设计 基于单片机的老人跌倒识别与远程求救装置设计(032601)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/10/23 6:22:51

紧固件选型与维护全指南:从基础分类到实战应用

1. 紧固件行业概述在机械制造、建筑工程、汽车维修等众多领域,紧固件就像人体的关节一样,将各个部件牢固地连接在一起。作为一名从业十余年的机械工程师,我见过太多因为紧固件选择不当导致的设备故障案例。有一次,某工厂的生产线突…

作者头像 李华
网站建设 2026/10/22 19:14:23

纯 Java 打造企业级 Agent Harness:BizBuddy 架构设计与工程实践

1. 为什么我要用纯 Java 造一个 Agent Harness1.1 从一个真实的痛点说起去年下半年,我所在的团队接了一个内部效率工具的需求:把公司里散落在各个业务系统中的重复性操作,用一个"智能代理"串起来。比如每天早上自动拉取前一天的订单…

作者头像 李华