Nchu Aircraft Instrument Panel

Nchu Aircraft Instrument Panel

南昌航空大学飞机仪表面板模拟器

C++
EGE
X-Plane
Platform
VS

基于 EGE 图形库(Easy Graphics Engine)的 Windows 桌面应用,用于模拟飞机驾驶舱仪表盘。支持两种机型,通过 Windows 共享内存接收 X-Plane 11 飞行模拟器的实时数据驱动仪表显示,无外部数据时自动运行内置 SIM 仿真模式。


目录

  1. 项目概述
  2. 项目结构
  3. EGE 图形库说明
  4. 核心架构
  5. 数据流与协议
  6. 仪表实现状态
  7. 音频告警系统
  8. 搭建与构建
  9. X-Plane Monitor 工具
  10. 开发者指南
  11. 常见问题与排错

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.libege\lib\vs2022\x64\graphics.lib
Debug 链接库 graphicsd.libege\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_MEANgauge_common.h 中对此有详细注释说明。

3.4 EGE 已知问题(紫色/残缺修复)

多个仪表曾出现文字发紫、画面残缺的问题,根因有二:

  1. 文字颜色状态残留settextcolor() 未显式设置,沿用了前序模块的紫色状态 → 修复:所有 outtextxy() 前加 settextcolor()
  2. 弓形覆盖绘制顺序错误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_panelextern 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(推荐)

  1. 双击打开 AircraftInstrumentPanel.sln
  2. 工具栏选择 x64 平台 + Debug / Release 配置
  3. Ctrl+Shift+B 编译
  4. 输出:
    • build/x64/Debug/AircraftInstrumentPanel.exe
    • build/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 典型使用流程

  1. 启动 X-Plane 11,确保已开启 UDP 输出(设置 → 网络 → 设置本机 IP 和端口 49000/49010)
  2. 运行 xplane_monitor.py(或打包的 exe)
  3. 点击 「开始监听」,绑定端口(默认 0.0.0.0:49020
  4. 程序自动发现 X-Plane 的 UDP 数据包,开始接收 DATA 数据
  5. 在 RREF 面板订阅需要的 dataref(如 sim/cockpit2/gauges/indicators/airspeed_kts_pilot
  6. 数据通过共享内存自动转发给 C++ 仪表面板程序
  7. 启动 AircraftInstrumentPanel.exe,按 Space 切换到 UDP 模式即可看到实时飞行数据

9.6 录制与回放

# 录制文件保存在
tools/Flightdata/*.csv
  • 录制:在 Recording 面板点击「开始录制」,选择一个 CSV 保存路径,所有接收到的数据将被记录
  • 回放:加载已录制的 CSV 文件,点击「播放」,可调节播放速度(0.1x ~ 5.0x),支持暂停/继续

10. 开发者指南

10.1 添加一个新仪表(Cessna 172SP)

  1. Cessna Skyhawk(Floats)/ 下创建模块目录:<Position>_<Name>/headers/sources/
  2. 实现 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);
    
  3. image_panel.cppkInstrumentDefinitions 中注册
  4. image_panel.cpp 顶部添加 #include 头文件引用
  5. 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)教学/研究项目。