Nchu Aircraft Instrument Panel
南昌航空大学飞机仪表面板模拟器
基于 EGE 图形库(Easy Graphics Engine)的 Windows 桌面应用,用于模拟飞机驾驶舱仪表盘。支持两种机型,通过 Windows 共享内存接收 X-Plane 11 飞行模拟器的实时数据驱动仪表显示,无外部数据时自动运行内置 SIM 仿真模式。
目录
- 项目概述
- 项目结构
- EGE 图形库说明
- 核心架构
- 数据流与协议
- 仪表实现状态
- 音频告警系统
- 搭建与构建
- X-Plane Monitor 工具
- 开发者指南
- 常见问题与排错
1. 项目概述
本项目是南昌航空大学(Nchu)的飞行模拟器仪表面板软件,使用 EGE 图形库(Easy Graphics Engine,一个轻量级 Windows GDI+ 封装库)实现飞机驾驶舱仪表盘的实时渲染。
支持的机型
| 机型 | 仪表布局 | 说明 |
|---|---|---|
| Cessna 172SP(水上型) | 6 块主仪表(中间 "六块")+ 9 块辅助仪表(左右两侧) | 传统机械仪表布局,共 15 个槽位。中间 6 块已完整实现,左/右 9 块为 stub 框架 |
| Boeing 737-800 | PFD + ND + CLOCK + ISFD + Upper/Lower DU + FMC/CDU | 现代玻璃座舱布局,各 DU 模块独立实现中 |
数据来源
| 模式 | 说明 |
|---|---|
| SIM 模式(默认) | 内置 sim_tick_default() 自动生成仿真飞行数据,无需外部连接即可独立演示 |
| UDP 模式 | 通过 Windows 命名共享内存接收 X-Plane 11 飞行模拟器实时数据,驱动仪表真实反映飞行状态 |
按 Space 键可在两种模式间切换。两种模式共享同一套 FlightParams 数据结构和渲染管线。
技术栈
| 组件 | 技术 |
|---|---|
| 图形渲染 | EGE 25.11(基于 GDI+) |
| 编程语言 | C++17(Visual C++) |
| 构建系统 | MSBuild(Visual Studio 2022 / 2026 Preview) |
| 网络通信 | Winsock2 UDP + Windows 命名共享内存(mmap) |
| 音频告警 | Windows PlaySound API(winmm.lib) |
| 外部工具 | Python 3 + Tkinter(xplane_monitor) |
2. 项目结构
AircraftInstrumentPanel/
│
├── AircraftInstrumentPanel.sln ← Visual Studio 解决方案文件
├── AircraftInstrumentPanel.vcxproj ← VS 项目文件(x64 Debug/Release)
├── AircraftInstrumentPanel.vcxproj.filters
├── AircraftInstrumentPanel.vcxproj.user ← VS 用户设置(调试工作目录等)
├── COMMIT_CONVENTION.md ← 提交规范文档
│
├── ege/ ★ EGE 图形库 v25.11(核心依赖)
│ ├── include/
│ │ ├── ege.h ← EGE 主头文件(自动导入 graphics.h)
│ │ ├── graphics.h ← 图形 API(项目直接引用)
│ │ ├── ege.zh_CN.h ← 中文资源文件
│ │ └── ege/ ← 子模块(button/fps/label/sys_edit/types 等)
│ └── lib/
│ ├── vs2022/x64/graphics.lib ← Release 静态库
│ ├── vs2022/x64/graphicsd.lib ← Debug 静态库
│ └── vs2010~vs2026/ ← 各版本预编译库(含 x86/x64)
│
├── Nchu_Aircraft_instrument_panel/ ★ 项目源码根目录
│ │
│ ├── launcher.cpp ← 启动器入口:创建选择菜单窗口
│ ├── panel_interface.h ← PanelInterface 抽象接口定义
│ ├── panel_runtime.h / .cpp ← 共享运行时:EGE 主循环、面板切换
│ │
│ ├── Cessna Skyhawk(Floats)/ ★ Cessna 172SP(水上型)机型
│ │ ├── main.cpp ← 面板入口,导出 cessna_panel
│ │ ├── demo3.png ← 1920×1200 面板背景图
│ │ ├── gauge_common/ ← 公共仪表基础设施
│ │ │ ├── headers/gauge_common.h ← FlightParams(33 个字段)、数学/颜色工具
│ │ │ └── sources/gauge_common.cpp ← SIM 仿真器、UDP 线程、绘图基元
│ │ ├── image_panel/ ← 面板渲染管线 + 15 槽位布局
│ │ │ ├── headers/image_panel.h
│ │ │ └── sources/image_panel.cpp ← 槽位定义、层合成、鼠标交互、预览
│ │ │
│ │ ├── Mid_1_1_Airspeed_Indicator/ ← ✅ 空速表(已实现)
│ │ ├── Mid_1_2_Attitude_Indicator/ ← ✅ 姿态仪(已实现)
│ │ ├── Mid_1_3_Altimeter/ ← ✅ 高度表(已实现)
│ │ ├── Mid_2_1_Turn_Coordinator/ ← ✅ 转弯协调仪(已实现)
│ │ ├── Mid_2_2_Heading_Indicator/ ← ✅ 航向指示器(已实现)
│ │ ├── Mid_2_3_Vertical_Speed_Indicator/ ← ✅ 升降速度表(已实现)
│ │ ├── Left_1_1_Clock_OAT_Volt/ ← ⏳ 时钟/气温/电压表(stub)
│ │ ├── Left_2_1_Fuel_Quantity/ ← ⏳ 燃油量表(stub)
│ │ ├── Left_2_2_EGT_Fuel_Flow/ ← ⏳ 排气温度/燃油流量表(stub)
│ │ ├── Left_3_1_Oil_Temp_Pressure/ ← ⏳ 油温/油压表(stub)
│ │ ├── Left_3_2_Vacuum_Ammeter/ ← ⏳ 真空/电流表(stub)
│ │ ├── Right_1_1_VOR1_ILS_CDI/ ← ⏳ VOR1/ILS CDI(stub)
│ │ ├── Right_2_1_VOR2_CDI/ ← ⏳ VOR2 CDI(stub)
│ │ ├── Right_3_1_Tachometer_Hobbs/ ← ⏳ 转速表/霍布斯计时(stub)
│ │ ├── Right_3_2_ADF_Indicator/ ← ⏳ ADF 方位指示器(stub)
│ │ └── docs/
│ │ ├── gauge_integration_guide.md ← 仪表集成指南(比例映射、坐标系统)
│ │ └── purple_issue_fix.md ← 紫色锯齿问题排查与修复
│ │
│ ├── Boeing737-800/ ★ Boeing 737-800 机型
│ │ ├── main.cpp ← 面板入口,导出 boeing_panel
│ │ ├── Boeing737-800.png ← 1920×1200 面板背景图
│ │ ├── Boeing737-800.txt ← SVG 仪表槽位多边形标定数据
│ │ ├── B737_image_panel/ ← B737 面板渲染管理
│ │ ├── CLOCK/ ← ⏳ 飞行时钟
│ │ ├── MIP_PFD/ ← ⏳ 主飞行显示器(PFD)
│ │ ├── MIP_ND/ ← ⏳ 导航显示器(ND)
│ │ ├── ISFD/ ← ⏳ 集成备用飞行显示器
│ │ ├── UPPER_DU/ ← ⏳ 上部显示单元
│ │ ├── LOWER_DU/ ← ⏳ 下部显示单元
│ │ ├── FMC_CDU/ ← ⏳ 飞行管理计算机/CDU
│ │ └── B737_instrument_integration_guide.md ← B737 集成指南
│ │
│ ├── udp_module/ ★ 数据通信模块
│ │ ├── headers/
│ │ │ ├── fleet_manager.h ← 机队管理器(多机数据统一管理)
│ │ │ ├── shared_mem_receiver.h ← 共享内存接收器(Python→C++ 桥接)
│ │ │ └── aircraft_config.h ← 飞机配置数据结构
│ │ └── sources/
│ │ ├── fleet_manager.cpp
│ │ └── shared_mem_receiver.cpp
│ │
│ ├── audio_alert/ ★ 音频告警模块
│ │ ├── headers/
│ │ │ ├── alert_rules.h ← 告警规则引擎(C172 / B737 双规则表)
│ │ │ └── alert_sound.h ← 异步 .wav 播放器(PlaySound API)
│ │ └── sources/
│ │ ├── alert_rules.cpp
│ │ └── alert_sound.cpp
│ │
│ └── alert/ ← 📁 告警资源目录(.wav 文件等)
│
└── tools/ ★ Python 辅助工具├── xplane_monitor.py ← X-Plane UDP 监听/监控工具(Tkinter GUI)├── build_exe.py ← PyInstaller 打包脚本(onefile / onedir)└── file_version.txt ← 版本信息(用于 exe 文件属性)
3. EGE 图形库说明
ege/ 目录是 Easy Graphics Engine (EGE) v25.11,由 xege.org 维护的开源 Windows 图形库。EGE 基于 GDI+ 封装,提供简洁易用的 C/C++ 绘图 API,兼具性能与可移植性。官方网站:https://xege.org,GitHub: x-ege/xege。
3.1 在项目中的配置
| 环节 | 配置值 |
|---|---|
| 头文件包含 | #include <graphics.h> 或 #include <ege.h> |
| 附加包含目录 | ege\include |
| Release 链接库 | graphics.lib → ege\lib\vs2022\x64\graphics.lib |
| Debug 链接库 | graphicsd.lib → ege\lib\vs2022\x64\graphicsd.lib |
| 系统链接依赖 | gdiplus.lib gdi32.lib imm32.lib msimg32.lib ole32.lib oleaut32.lib winmm.lib uuid.lib |
| 网络链接依赖 | ws2_32.lib(Winsock2,UDP 通信) |
项目根据 Visual Studio 版本自动选择 EGE 库目录:
$(VisualStudioVersion)=='17.0'→vs2022\x64,否则 →vs2026\x64,未来可扩展支持更多版本。
3.2 EGE 核心 API 在本项目中的应用
| API | 用途 |
|---|---|
initgraph(), closegraph() |
窗口创建与销毁(由共享运行时管理) |
setinitmode() |
初始化模式(如 INIT_RENDERMANUAL 手动控制渲染) |
getimage(), putimage() |
加载面板背景图、离屏缓冲输出 |
newimage(), delimage(), resize_f() |
离屏缓冲管理(双缓冲消除闪烁) |
ege_enable_aa() |
开启抗锯齿,提高仪表刻度/指针渲染质量 |
setcolor(), setfillcolor(), settextcolor() |
颜色状态控制 |
ege_fillcircle(), ege_arc(), ege_fillpoly() |
图形基元绘制 |
outtextxy(), setfont(), setbkmode() |
文字渲染 |
getkey(), random() |
键盘输入与随机数 |
setcaption() |
设置窗口标题 |
3.3 项目特定约定
- 离屏缓冲:所有面板使用离屏
PIMAGE缓冲完成全部绘制后,一次性putimage输出到窗口,彻底消除画面闪烁 - 抗锯齿:离屏缓冲均调用
ege_enable_aa(true, buf)开启抗锯齿 - 坐标系统:x 向右为正,y 向下为正;角度 0° = 3 点钟方向,逆时针递增。
- Winsock2 兼容:因 EGE 内部包含
windows.h(会拉入winsock.h),项目中所有包含winsock2.h的源文件必须在包含任何 EGE/Windows 头文件之前先包含winsock2.h,并定义WIN32_LEAN_AND_MEAN。gauge_common.h中对此有详细注释说明。
3.4 EGE 已知问题(紫色/残缺修复)
多个仪表曾出现文字发紫、画面残缺的问题,根因有二:
- 文字颜色状态残留:
settextcolor()未显式设置,沿用了前序模块的紫色状态 → 修复:所有outtextxy()前加settextcolor() - 弓形覆盖绘制顺序错误:
ege_fillpoly()绘制的弓形层在指针/文字之后执行,大面积遮盖前景 → 修复:将弓形绘制移至指针之前
详见 docs/purple_issue_fix.md。
4. 核心架构
4.1 软件分层
┌──────────────────────────────────────────────────────┐
│ Launcher (启动器) │
│ launcher.cpp — 选择菜单窗口 │
├──────────────────────────────────────────────────────┤
│ Shared Runtime (共享运行时) │
│ panel_runtime.cpp — EGE 主循环、面板切换 │
│ panel_interface.h — PanelInterface 抽象 │
├──────────────────────────────────────────────────────┤
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Cessna 172SP │ │ Boeing 737-800 │ │
│ │ main.cpp │ │ main.cpp │ │
│ │ image_panel/ │ │ B737_image_panel/ │ │
│ │ gauge_common/ │ │ (共用 gauge_common) │ │
│ │ 6+9 块仪表 │ │ 8 个 DU 模块 │ │
│ └────────┬─────────┘ └───────────┬─────────────────┘ │
│ │ │ │
│ └──────────┬──────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ udp_module (数据通信层) │ │
│ │ FleetManager → SharedMemReceiver → FlightParams │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ audio_alert (音频告警层) │ │
│ │ AlertRules → AlertSound → PlaySound API │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
4.2 启动流程
启动 AircraftInstrumentPanel.exe│▼
┌─────────────────────────────────┐
│ 启动器选择窗口 │
│ │
│ [1] Cessna 172SP │
│ [2] Boeing 737-800 │
│ [3] X-Plane Monitor(外部工具)│
│ [4] Exit │
└─────────────────────────────────┘│▼ 选择 1 或 2 → DestroyWindow 关闭启动器
panel_main(initial_panel_id) ← 共享运行时入口│├─ DPI 感知 → 计算缩放后的窗口尺寸├─ initgraph() 创建 EGE 窗口├─ 加载面板背景图(getimage)├─ 创建离屏渲染缓冲(newimage + 抗锯齿)├─ 调用面板 init 回调(初始化仪表、FleetManager 等)└─ 进入主循环:├─ getkey() / peekkey() 处理键盘├─ 处理鼠标事件├─ update(dt) → 仿真推进 / UDP 数据同步├─ render(w, h) → 绘制到离屏缓冲 → putimage 输出└─ 循环直到 Esc 退出
4.3 PanelInterface 抽象接口
每个机型通过统一的 PanelInterface 结构体向共享运行时注册。运行时通过回调驱动面板生命周期:
typedef struct {const char *name; // 面板名称(显示在窗口标题)int logical_w, logical_h; // 面板逻辑尺寸(用于初始窗口大小)int (*init)(int w, int h); // 初始化:加载资源、创建离屏缓冲、启动 FleetManagervoid (*shutdown)(void); // 清理:释放图片、关闭共享内存void (*update)(double dt); // 每帧更新:仿真物理推进或 UDP 数据同步void (*render)(int w, int h);// 每帧渲染:绘制到 EGE 离屏缓冲void (*on_space)(void); // Space 键:切换 SIM/UDP 模式void (*on_mouse)(const mouse_msg *msg); // 鼠标事件(悬停、双击放大等)void (*on_resize)(int w, int h); // 窗口缩放回调
} PanelInterface;
两个面板实例分别在各自 main.cpp 中定义:extern PanelInterface cessna_panel 和 extern PanelInterface boeing_panel,运行时通过 g_panels[0] / g_panels[1] 引用。
4.4 渲染管线
每帧 render(w, h) 调用:│├─ 1. 绘制面板背景图(缩放到窗口尺寸)├─ 2. 遍历 15 个(或 B737 的 8 个)仪表槽位│ ├─ 计算槽位中心、半径(比例映射)│ ├─ 调用该仪表的 draw_xxx_panel_from_udp()│ │ ├─ 绘制静态刻度/数字/标签│ │ ├─ 根据 FlightParams 绘制指针/数值│ │ └─ 绘制弓形覆盖、塑料边框效果│ └─ 下一个槽位├─ 3. 绘制状态指示(模式标签、数据链路状态等)└─ 4. putimage() 将离屏缓冲输出到窗口
4.5 运行时快捷键
| 按键 | 功能 |
|---|---|
1 |
切换到 Cessna 172SP |
2 |
切换到 Boeing 737-800 |
Space |
切换 SIM(仿真)/ UDP(外部数据)模式 |
F11 |
切换全屏(WS_POPUP 模式) |
Esc |
返回启动器选择窗口 |
5. 数据流与协议
5.1 完整数据管道
┌─────────────────────────────────────────────────────────┐
│ X-Plane 11 │
│ 通过 UDP 端口 49000/49010/49020 广播飞行数据 │
│ 协议: DATA (每帧)、RREF (订阅响应)、BEACON (发现) │
└──────────────────────┬──────────────────────────────────┘│ UDP 网络包▼
┌─────────────────────────────────────────────────────────┐
│ xplane_monitor.py (Python 监听器) │
│ │
│ 1. UDP 监听线程 → 解析 DATA/RREF/BEACON 包 │
│ 2. 维护 live_values 字典 (实时数据缓存) │
│ 3. 共享内存写入线程 → 序列化 FlightParams → 写 mmap │
│ 4. (可选) NIC 嗅探 → Scapy 抓包分析 │
│ 5. (可选) CSV 录制/回放 │
└──────────────────────┬──────────────────────────────────┘│ Windows 命名共享内存│ "Local\AircraftPanelSharedMem"│ 4096 字节, packed 二进制布局▼
┌─────────────────────────────────────────────────────────┐
│ SharedMemReceiver (C++, udp_module) │
│ │
│ 1. OpenFileMapping → MapViewOfFile → pData │
│ 2. 检查 data_ready 标志 (volatile LONG) │
│ 3. 读取 FlightParams → 清零 data_ready │
│ 4. 由 FleetManager 分发到面板 │
└──────────────────────┬──────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────┐
│ FleetManager → FlightParams → 各仪表绘制函数 │
│ 最终由 EGE putimage() 渲染到屏幕 │
└─────────────────────────────────────────────────────────┘
5.2 共享内存协议
| 项目 | 值 |
|---|---|
| 共享内存名称 | Local\AircraftPanelSharedMem |
| 大小 | 4096 字节 |
| 布局 | SharedMemLayout(packed 对齐) |
| 同步方式 | volatile LONG data_ready 标志位 |
#pragma pack(push, 1)
typedef struct {volatile LONG data_ready; // 1 = 有新数据, 读取后置 0FlightParams params; // 33 个字段的飞行数据
} SharedMemLayout;
#pragma pack(pop)
Python 端 (xplane_monitor) 使用
ctypes构造相同的二进制布局写入,C++ 端通过SharedMemReceiver读取。零拷贝、零序列化开销。
5.3 FlightParams 数据结构
FlightParams 结构体包含 33 个字段(27 个 double + 6 个 int),定义于 gauge_common.h:
| 类别 | 字段 | 单位 | 来源 |
|---|---|---|---|
| 姿态 | theta (俯仰), phi (滚转), psi (航向) |
度 | X-Plane RREF |
| 飞行 | alt_m (高度), ias_kts (空速), vs_fpm (升降率) |
米/节/英尺·分⁻¹ | X-Plane RREF |
| Left_1_1 | oat_c, bus_volts, clock_seconds |
°C/V/s | X-Plane + 本地仿真 |
| Left_2_1 | fuel_left_gal, fuel_right_gal |
加仑 | X-Plane RREF |
| Left_2_2 | egt_c, fuel_flow_gph |
°C/gal·h⁻¹ | X-Plane RREF |
| Left_3_1 | oil_temp_c, oil_press_psi |
°C/psi | X-Plane RREF |
| Left_3_2 | vacuum_inhg, ammeter_amp |
inHg/A | X-Plane RREF |
| Right_1_1 | nav1_obs_deg, nav1_dev_dot, nav1_gs_dot, nav1_to_from, nav1_nav_flag, nav1_gs_flag, nav_source_mode |
度/dot/— | X-Plane RREF |
| Right_2_1 | nav2_obs_deg, nav2_dev_dot, nav2_to_from, nav2_nav_flag |
度/dot/— | X-Plane RREF |
| Right_3_1 | rpm, hobbs_hours |
RPM/h | X-Plane RREF |
| Right_3_2 | adf_bearing_deg, adf_card_deg |
度 | X-Plane RREF + 本地派生 |
5.4 X-Plane RREF 映射
xplane_monitor 通过 RREF 协议订阅 X-Plane dataref,按索引映射到 FlightParams:
| Index | 字段 | X-Plane dataref | 换算 |
|---|---|---|---|
| 0 | theta |
sim/cockpit2/gauges/indicators/pitch_deg |
— |
| 1 | phi |
sim/cockpit2/gauges/indicators/roll_deg |
— |
| 2 | psi |
sim/cockpit2/gauges/indicators/heading_electric_deg |
缠绕修正 |
| 3 | ias_kts |
sim/cockpit2/gauges/indicators/airspeed_kts_pilot |
指数平滑 |
| 4 | alt_m |
sim/cockpit2/gauges/indicators/altitude_ft_pilot |
×0.3048 |
| 5 | vs_fpm |
sim/cockpit2/gauges/indicators/vvi_fpm_pilot |
指数平滑 |
| 6 | oat_c |
sim/cockpit2/temperature/outside_air_temp_degc |
— |
| 7 | bus_volts |
sim/cockpit2/electrical/bus_volts |
— |
| 8 | fuel_left_gal |
sim/cockpit2/fuel/fuel_quantity_left_gal |
— |
| 9 | fuel_right_gal |
sim/cockpit2/fuel/fuel_quantity_right_gal |
— |
| 10 | egt_c |
sim/cockpit2/engine/indicators/egt_deg_C |
— |
| 11 | fuel_flow_gph |
sim/cockpit2/engine/indicators/fuel_flow_kg_sec |
÷2.72×3600 |
| 12 | oil_temp_c |
sim/cockpit2/engine/indicators/oil_temperature_deg_C |
— |
| 13 | oil_press_psi |
sim/cockpit2/engine/indicators/oil_pressure_psi |
— |
| 14 | vacuum_inhg |
sim/cockpit2/electrical/vacuum_in_hg |
— |
| 15 | ammeter_amp |
sim/cockpit2/electrical/ammeter_indicated_amps |
— |
| 16 | nav1_obs_deg |
sim/cockpit2/radios/indicators/nav1_obs_deg |
— |
| 17 | nav1_dev_dot |
sim/cockpit2/radios/indicators/nav1_deviation_dot |
— |
| 18 | nav1_gs_dot |
sim/cockpit2/radios/indicators/nav1_gs_deviation_dot |
— |
| 19 | nav1_flag |
sim/cockpit2/radios/indicators/nav1_flag |
→ to_from/nav_flag/gs_flag |
| 20~25 | nav2 / rpm / hobbs / adf | 对应 dataref | — |
指数平滑系数 α=0.3 用于平滑姿态、空速等噪声较大的数据。
5.5 SIM 仿真模式
当无 X-Plane 连接时,gauge_common.cpp 中的 sim_tick_default() 自动生成仿真数据:
- 姿态:小幅正弦振荡模拟湍流
- 空速/高度:巡航恒定值
- 燃油:随时间递减
- 发动机参数:根据推力状态变化
- 导航:预设 OBS 缓转、偏离振荡
此模式确保项目可在无任何外部依赖的情况下独立演示和开发。
6. 仪表实现状态
6.1 Cessna 172SP 仪表盘
面板背景图 demo3.png(1920×1200)定义了 15 个仪表槽位。中间 6 块主仪表已完整实现,左右 9 块辅助仪表已注册(kInstrumentDefinitions)但为 stub 框架。
| 槽位 | 工程名 | 实现状态 | 说明 |
|---|---|---|---|
| Mid_1_1 | Airspeed_Indicator |
✅ 已完成 | 空速表,含彩色弧段(白/绿/黄/红区) |
| Mid_1_2 | Attitude_Indicator |
✅ 已完成 | 姿态仪,蓝天/大地背景,俯仰滚转指示 |
| Mid_1_3 | Altimeter |
✅ 已完成 | 三针高度表,含气压设定窗 |
| Mid_2_1 | Turn_Coordinator |
✅ 已完成 | 转弯协调仪,飞机符号 + 侧滑球 |
| Mid_2_2 | Heading_Indicator |
✅ 已完成 | 航向陀螺,含 OBS 设定指针 |
| Mid_2_3 | Vertical_Speed_Indicator |
✅ 已完成 | 升降速度表,±2000 fpm |
| Left_1_1 | Clock_OAT_Volt |
⏳ stub | 数码组合表(时钟/气温/电压) |
| Left_2_1 | Fuel_Quantity |
⏳ stub | 双油箱双针燃油量表 |
| Left_2_2 | EGT_Fuel_Flow |
⏳ stub | 排气温度/燃油流量组合表 |
| Left_3_1 | Oil_Temp_Pressure |
⏳ stub | 滑油温度/压力双针表 |
| Left_3_2 | Vacuum_Ammeter |
⏳ stub | 真空度/电流组合表 |
| Right_1_1 | VOR1_ILS_CDI |
⏳ stub | VOR1/ILS 航道偏离指示器 |
| Right_2_1 | VOR2_CDI |
⏳ stub | VOR2 航道偏离指示器 |
| Right_3_1 | Tachometer_Hobbs |
⏳ stub | 转速表/霍布斯计时表 |
| Right_3_2 | ADF_Indicator |
⏳ stub | ADF 方位指示器 |
所有 stub 仪表已在 kInstrumentDefinitions 中注册,其 draw_xxx_panel_from_udp() 函数均为含 TODO 注释的空实现。FlightParams 中的对应字段已就绪,欢迎贡献代码。
6.2 Boeing 737-800 仪表盘
面板背景图 Boeing737-800.png(1920×1200),基于 SVG 标定数据定义槽位多边形。
| 模块 | 实现状态 | 说明 |
|---|---|---|
CLOCK |
⏳ 开发中 | 飞行时钟 |
MIP_PFD |
⏳ 开发中 | 主飞行显示器(PFD)— 含空速带、高度带、姿态、航向 |
MIP_ND |
⏳ 开发中 | 导航显示器(ND)— 弧形航图、VOR/ILS 模式 |
ISFD |
⏳ 开发中 | 集成备用飞行显示器 |
UPPER_DU |
⏳ 开发中 | 上部显示单元 — 发动机参数 |
LOWER_DU |
⏳ 开发中 | 下部显示单元 — 系统状态 |
FMC_CDU |
⏳ 开发中 | 飞行管理计算机 |
B737_image_panel |
⏳ 开发中 | 面板渲染管理 |
7. 音频告警系统
音频告警模块位于 audio_alert/,为仪表面板提供基于飞行参数的声音告警能力。
7.1 架构
alert_rules_evaluate(fp, is_boeing, now)│├─ 遍历机型对应的告警规则表│ ├─ 调用规则 check_fn(fp) 判断条件│ ├─ 通过去抖时间(debounce_sec)防频繁触发│ └─ 条件满足 → 调用 alert_sound_play(filename)│└─ AlertRule 结构体├─ id / name ← 规则标识├─ wav_filename ← 对应的 .wav 文件├─ debounce_sec ← 去抖时间└─ check_fn ← 条件判断函数指针
7.2 告警规则
支持 Cessna 172SP 和 Boeing 737-800 两套独立规则表(最多 32 条规则):
| 规则示例 | 触发条件 | 机型 |
|---|---|---|
| 失速告警 | 空速低于失速门限 | C172 + B737 |
| 高度告警 | 低于最低安全高度 | C172 + B737 |
| 燃油低 | 燃油量低于设定值 | C172 + B737 |
| 超速告警 | 空速超过 Vne | C172 + B737 |
| 发动机参数超限 | EGT/油温/油压越界 | C172 (+ B737 双发) |
7.3 音频引擎
基于 Windows PlaySound API 实现异步 .wav 播放:
| API | 说明 |
|---|---|
alert_sound_init(base_dir) |
指定 .wav 文件目录 |
alert_sound_play(filename) |
异步播放,不阻塞渲染线程 |
alert_sound_stop_all() |
停止所有播放 |
alert_sound_shutdown() |
释放资源 |
依赖 winmm.lib(已在项目链接器中配置)。
8. 搭建与构建
8.1 前置条件
| 组件 | 要求 |
|---|---|
| Visual Studio | 2022(v143 工具集)或 2026 Preview(v145 工具集) |
| 工作负载 | 「使用 C++ 的桌面开发」 |
| Windows SDK | 10.0(项目目标版本) |
| 操作系统 | Windows 10/11 64 位 |
| Python | 3.7+(仅运行 xplane_monitor.py 时需要) |
8.2 构建步骤
方式一:Visual Studio IDE(推荐)
- 双击打开
AircraftInstrumentPanel.sln - 工具栏选择
x64平台 +Debug/Release配置 Ctrl+Shift+B编译- 输出:
build/x64/Debug/AircraftInstrumentPanel.exebuild/x64/Release/AircraftInstrumentPanel.exe
方式二:命令行 MSBuild
# Debug
msbuild AircraftInstrumentPanel.sln /p:Configuration=Debug /p:Platform=x64# Release
msbuild AircraftInstrumentPanel.sln /p:Configuration=Release /p:Platform=x64
8.3 运行
直接运行编译生成的 AircraftInstrumentPanel.exe。
⚠️ 工作目录要求:运行时的 CWD 需能访问背景图片(
demo3.png/Boeing737-800.png)。建议:
- 在项目根目录启动 exe
- 或在 VS 中设置「工作目录」为
$(ProjectDir)..\- 启动器会自动搜索 exe 同级目录、项目子目录等多个候选路径
8.4 编译配置详解
| 配置项 | Debug | Release |
|---|---|---|
| 输出目录 | build/x64/Debug/ |
build/x64/Release/ |
| 中间目录 | build/obj/x64/Debug/... |
build/obj/x64/Release/... |
| EGE 链接库 | graphicsd.lib |
graphics.lib |
| 优化 | 无 (/Od) |
全程序优化 (/GL, /LTCG) |
| 运行时库 | 多线程调试 DLL (/MDd) |
多线程 DLL (/MD) |
| 预处理器 | _DEBUG |
NDEBUG |
| C++ 标准 | C++17 (/std:c++17) |
C++17 (/std:c++17) |
| 字符集 | Unicode | Unicode |
| 多核编译 | 启用 (/MP) |
启用 (/MP) |
8.5 调试提示
- VS 中按
F5启动调试,确保工作目录设置为$(ProjectDir)..\(项目 → 属性 → 调试 → 工作目录) - 控制台输出可通过
OutputDebugStringA()查看(VS 输出窗口的「调试」面板) - EGE 窗口模式下
printf不可见,建议用OutputDebugStringA调试
9. X-Plane Monitor 工具
tools/xplane_monitor.py 是一个 X-Plane UDP 网络监听与数据转发工具,采用 Tkinter GUI(暗色 VSCode 风格界面),是连接 X-Plane 飞行模拟器与本仪表面板程序的桥梁。
9.1 功能特性
| 功能 | 说明 |
|---|---|
| UDP 监听 | 监听 X-Plane 发出的 UDP 数据包(DATA、RREF、BEACON 等协议) |
| RREF 订阅 | 向 X-Plane 发送 RREF 请求,订阅指定的 dataref(飞行数据变量),按指定频率接收 |
| NIC 嗅探 | 基于 Scapy 的网卡抓包,实时监控 X-Plane 网络流量(需 Npcap/WinPcap) |
| 实时数据显示 | 仪表盘风格面板,实时显示关键飞行参数(高度、空速、姿态等) |
| 数据录制/回放 | 将接收到的飞行数据录制为 CSV 文件,支持离线回放分析 |
| 共享内存转发 | 通过 Windows 命名共享内存(Local\AircraftPanelSharedMem)将数据转发给 C++ 仪表面板程序 |
| 多飞机管理 | 支持多架飞机数据同时接收与管理(通过 FleetManager) |
9.2 安装依赖
# 创建虚拟环境(可选但推荐)
python -m venv venv
venv\Scripts\activate# 安装核心依赖
pip install psutil # 网络接口信息(可选,增强功能)
pip install scapy # 网卡抓包(可选,需 Npcap 驱动)
pip install ujson # 更快 JSON 解析(可选)# 如需打包为 exe
pip install pyinstaller # 用于 build_exe.py 打包
关于 Npcap:Scapy 抓包功能需要安装 Npcap(或 WinPcap)。安装时请勾选 "Install Npcap in WinPcap API-compatible Mode"。如果不使用 NIC 嗅探功能,Scapy 和 Npcap 非必需。
9.3 运行方式
直接运行 Python 脚本
cd tools
python xplane_monitor.py
打包为独立 exe 运行
cd tools
python build_exe.py # 打包为单个 exe(onefile 模式)
# 或
python build_exe.py --onedir # 目录模式(启动更快,便于调试)
打包后产物在:
tools/dist/onefile/xplane_monitor.exe(单文件模式)tools/dist/onedir/xplane_monitor.exe(目录模式)
在启动器菜单中按
[3]可自动定位并启动xplane_monitor.exe。
9.4 界面说明
┌─────────────────────────────────────────────────────────────┐
│ X-Plane 11 UDP Monitor · v3.0-ui-vscode │
├────┬────────────────────────────────────────────────────────┤
│ 活 │ ┌──────────────────────────────────────────────────┐ │
│ 动 │ │ 监听控制 [启动] [停止] [RREF 订阅管理] │ │
│ 栏 │ │ 绑定IP: [0.0.0.0] 端口: [49020] │ │
│ │ └──────────────────────────────────────────────────┘ │
│ 📡 │ ┌──────────────────────────────────────────────────┐ │
│ 监 │ │ Tab1: 实时数据 Tab2: 仪表盘 Tab3: NIC嗅探 │ │
│ 听 │ │ Tab4: 录制/回放 Tab5: 日志 │ │
│ │ │ │ │
│ 📊 │ │ 高度: 1500.00 m 空速: 120.00 kts │ │
│ 仪 │ │ 俯仰: 0.00° 滚转: 0.00° │ │
│ 表 │ │ 航向: 180.00° 升降率: 0.00 fpm │ │
│ │ │ 燃油左: 12.5 gal 燃油右: 12.5 gal │ │
│ 🎬 │ │ ... │ │
│ 录 │ └──────────────────────────────────────────────────┘ │
│ 制 │ │
│ │ ┌──────────────────────────────────────────────────┐ │
│ 📋 │ │ [状态栏] 📡 已连接 🟢 共享内存 OK │ │
│ 日 │ └──────────────────────────────────────────────────┘ │
│ 志 │ │
└────┴────────────────────────────────────────────────────────┘
左侧 Activity Bar 包含:
- 📡 监听(Monitor):UDP 监听控制、端口配置
- 📊 仪表(Instruments):飞行参数仪表盘
- 🎬 录制(Recording):飞行数据录制与回放
- 📋 日志(Log):程序运行日志
9.5 典型使用流程
- 启动 X-Plane 11,确保已开启 UDP 输出(设置 → 网络 → 设置本机 IP 和端口 49000/49010)
- 运行
xplane_monitor.py(或打包的 exe) - 点击 「开始监听」,绑定端口(默认
0.0.0.0:49020) - 程序自动发现 X-Plane 的 UDP 数据包,开始接收 DATA 数据
- 在 RREF 面板订阅需要的
dataref(如sim/cockpit2/gauges/indicators/airspeed_kts_pilot) - 数据通过共享内存自动转发给 C++ 仪表面板程序
- 启动
AircraftInstrumentPanel.exe,按Space切换到 UDP 模式即可看到实时飞行数据
9.6 录制与回放
# 录制文件保存在
tools/Flightdata/*.csv
- 录制:在 Recording 面板点击「开始录制」,选择一个 CSV 保存路径,所有接收到的数据将被记录
- 回放:加载已录制的 CSV 文件,点击「播放」,可调节播放速度(0.1x ~ 5.0x),支持暂停/继续
10. 开发者指南
10.1 添加一个新仪表(Cessna 172SP)
- 在
Cessna Skyhawk(Floats)/下创建模块目录:<Position>_<Name>/headers/和sources/ - 实现
draw_xxx_panel_from_udp()函数,遵循比例映射原则:void draw_xxx_panel_from_udp(int window_w, int window_h,int cx, int cy, double panel_radius, FlightParams *data); - 在
image_panel.cpp的kInstrumentDefinitions中注册 - 在
image_panel.cpp顶部添加#include头文件引用 - 在
vcxproj中添加新的.cpp和.h文件引用
10.2 比例映射核心原则
所有独立编写的仪表基于固定设计尺寸(通常半径 220px),集成到项目时不能直接缩放偏移,而应采用比例映射:
// ✅ 正确:一步到位,仅在画像素前四舍五入
int x = (int)(cx + dx / 220.0 * panel_radius + 0.5);
int r = (int)(v / 220.0 * panel_radius + 0.5);// ❌ 错误:两步取整导致精度损失叠加
double scale = panel_radius / 220.0;
int x = cx + irnd(dx * scale);
10.3 EGE 渲染注意事项
- settextcolor() 必须显式设置:
outtextxy()的文字颜色受settextcolor()控制,而非setcolor()。忘记设置会导致颜色状态残留(如紫色文字问题) - 绘制顺序:背景 → 刻度/数字 → 指针 → 弓形覆盖(塑料边框效果)。弓形覆盖必须在指针之前绘制,否则会遮盖前景
- 线宽恢复:
setlinewidth()修改线宽后记得恢复为1.0,避免污染后续仪表 - 离屏缓冲:始终在离屏
PIMAGE上完成全部绘制再putimage,不要直接画到窗口
10.4 代码规范
- 命名:变量/函数使用英文,注释使用中文(关键逻辑用中文注释,API 调用用英文)
- Winsock2 顺序:任何包含
winsock2.h的文件必须在 EGE/Windows 头之前包含,并定义WIN32_LEAN_AND_MEAN - UTF-8:项目使用
/utf-8编译选项,所有源文件保存为 UTF-8 编码
11. 常见问题与排错
编译问题
Q: 编译时报错找不到 graphics.h?
确保 ege/include 目录存在,且 vcxproj 中的附加包含目录配置正确。项目已自动配置,直接打开 .sln 编译即可。
Q: 链接时报错 LNK2019 找不到 EGE 函数?
确认 EGE 库文件路径正确。项目根据 Visual Studio 版本自动选择 ege/lib/vs2022/x64/ 或 vs2026/x64/。如使用 VS2022,确保 graphics.lib / graphicsd.lib 在该目录下。
Q: 编译报 winsock.h / winsock2.h 冲突?
这是 EGE + Winsock2 的经典问题。确保源文件中 winsock2.h 最先被包含(在任何 Windows/EGE 头之前),并定义 WIN32_LEAN_AND_MEAN。参考 gauge_common.h 顶部注释的解决方案。
运行问题
Q: 运行程序时闪退或找不到图片?
运行工作目录需要能访问到 demo3.png(Cessna)或 Boeing737-800.png(Boeing)。建议在项目根目录运行 exe,或使用 Visual Studio 调试时设置工作目录为项目根目录。
Q: 仪表文字发紫或画面残缺?
这是 EGE 渲染中的常见问题:
- 发紫:缺少
settextcolor()→ 在每个outtextxy()前显式设置 - 残缺:弓形覆盖(
ege_fillpoly)绘制顺序错误 → 将弓形绘制移到指针之前
详见 docs/purple_issue_fix.md。
数据连接问题
Q: X-Plane 数据收不到?
- 确认 X-Plane 已开启 UDP 网络输出(设置 → 网络 → UDP 设置)
- 检查
xplane_monitor.py监听的端口是否与 X-Plane 设置一致 - 如果使用 NIC 嗅探功能,确保已安装 Npcap 并选择兼容模式
- 检查 Windows 防火墙是否阻止了相关端口
Q: 共享内存连接不上?
- 确保先运行
xplane_monitor.py(或打包的 exe),它负责创建共享内存 - 共享内存名称:
Local\AircraftPanelSharedMem,两端需保持一致 - 以 exe 方式运行
xplane_monitor时,共享内存功能完全不受影响(mmap 调用的是 Windows 内核 API)
Q: xplane_monitor 启动报错 "Scapy 不可用"?
这是正常现象,Scapy 和 Npcap 仅用于 NIC 嗅探功能,不影响 UDP 监听和共享内存转发。可忽略此警告。
许可证
本项目为南昌航空大学(Nchu)教学/研究项目。