From b05ffedbc1e5533805a93b11f1336a4b867c6eb9 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 4 Jun 2026 12:09:55 +0200 Subject: [PATCH] Citation X cockpit profile: full Primus 2000 suite (PFD/MFD/EICAS/AP/RMU) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a switchable cockpit-profile selector (Garmin G1000 / Cessna Citation X / GA steam) and recreate the Citation X Honeywell Primus 2000 avionics line-for- line from the X-Plane Citation X + FMS manuals: - CitPFD: attitude w/ FD command bars, speed tape (Vmo barber-pole, Vfe, low- speed red/amber bands), AOA index, altitude tape + trend, VSI, round HSI with CDI/course pointer + VOR/ADF bearing pointers, radar altimeter, minimums, STD/BARO/CRS/HDG bezel. - CitEICAS: twin FAN%/ITT bar gauges, OIL °C/PSI, FUEL (flow/qty PPH·LBS), ELECTRICAL, HYDRAULICS, slat chevron, STAB trim, FLAPS, CAS message stack, softkeys NORM/FUEL-HYD/ELEC/CTRL-POS/ENG + control-position overlay. - CitMFD: Honeywell heading-up arc map, FMS route (magenta active/white future), TCAS, terrain/WX, range arc, ETE/SAT/TAS/GSPD block, clock + ET/FT timer, V-SPEEDS reference card, MFD-setup overlays (TRAFFIC/TERRAIN/APTS/VOR). - CitAP: HDG/NAV/APP/BC · ALT/VNAV/BANK/STBY · FLC/C-O/VS · pitch wheel · AP/YD/M-TRIM/PFD-SEL, FMA bar + lamps from per-mode *_status datarefs. - CitRMU: COM/NAV active+standby tuning, transponder, ADF, TCAS range/mode, IDENT + Nav Source Selector (NAV1/2/FMS, VOR/ADF/FMS bearing source). Integration: all avionics stream live via the X-Plane Web API (new datarefs for N1/N2/ITT, radar-alt, AOA, hydraulics, trim, flaps/slats/gear, control positions, ADF, mach, yaw-damper); the existing fms-sync.lua drives the Citation's built-in FMS (aircraft-agnostic XPLM FMS SDK). Demo seeds added so every panel renders offline. Verified headless via Playwright (no console errors; G1000/GA profiles unaffected). Co-Authored-By: Claude Opus 4.8 --- plugins/README.md | 23 ++ server/bridge.js | 12 + server/config.js | 49 ++++ web/src/App.jsx | 97 +++++-- web/src/citation.css | 150 ++++++++++ web/src/components/citation/CitAP.jsx | 100 +++++++ web/src/components/citation/CitEICAS.jsx | 205 +++++++++++++ web/src/components/citation/CitMFD.jsx | 196 +++++++++++++ web/src/components/citation/CitPFD.jsx | 353 +++++++++++++++++++++++ web/src/components/citation/CitRMU.jsx | 129 +++++++++ web/src/main.jsx | 1 + 11 files changed, 1298 insertions(+), 17 deletions(-) create mode 100644 web/src/citation.css create mode 100644 web/src/components/citation/CitAP.jsx create mode 100644 web/src/components/citation/CitEICAS.jsx create mode 100644 web/src/components/citation/CitMFD.jsx create mode 100644 web/src/components/citation/CitPFD.jsx create mode 100644 web/src/components/citation/CitRMU.jsx diff --git a/plugins/README.md b/plugins/README.md index 47a075f..dc241ca 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -36,3 +36,26 @@ desktop app sets it to the bundled scripts; otherwise it finds `plugins/` itself A 3-decimal lat/lon signature de-dupes the round-trip, so the two sides never loop. Waypoints are pushed as lat/lon legs (exact route; in-sim idents are generic — route accuracy over cosmetics). + +## Cessna Citation X (Honeywell Primus 2000) + +The app ships three switchable **cockpit profiles** (sidebar dropdown): the +Garmin **G1000**, the **Citation X**, and a **GA steam** panel. The Citation +profile recreates the Primus 2000 suite line-for-line from the X-Plane Citation +X manual — **PFD** (attitude, speed/alt tapes with Vmo barber-pole + Vfe + AOA, +HSI with VOR/ADF bearing pointers, CDI, VSI, FD bars, radar altimeter, +minimums), **MFD** (Honeywell arc map, FMS route, TCAS, terrain/WX, ETE/SAT/TAS/ +GSPD, ET/FT timer, V-SPEEDS card), **EICAS** (twin N1/ITT/oil, fuel, electrical, +hydraulics, slats, stab-trim, flaps, CAS), the **autopilot/flight-guidance** +controller (HDG/NAV/APP/BC · ALT/VNAV/BANK/STBY · FLC/VS · AP/YD/M-TRIM/PFD-SEL ++ pitch wheel), the **Radio Management Unit**, and the **Nav Source Selector**. + +Integration: +- **Avionics (PFD/MFD/EICAS/AP/RMU):** every value is a universal X-Plane + dataref / command streamed live by the bridge over the **Web API** — no Lua + needed (N1/N2/ITT, radar-alt, AOA, hydraulics, trim, flaps/slats/gear, control + positions, ADF, mach, yaw-damper, the per-mode `*_status` AFCS annunciation…). +- **FMS / CDU:** the same **`fms-sync.lua`** bridges the web CDU ↔ the in-sim + FMS. It uses the aircraft-agnostic XPLM FMS SDK, so it drives the Citation X's + built-in FMS exactly as it does the G1000's — load/build a plan on a tablet and + the Citation flies it; build it in the sim and it shows on every tablet. diff --git a/server/bridge.js b/server/bridge.js index 8358f77..79fd970 100644 --- a/server/bridge.js +++ b/server/bridge.js @@ -337,6 +337,18 @@ function startDemo() { { lat: 47.7, lon: -122.0, r: 8, lvl: 2 }, { lat: 47.75, lon: -121.9, r: 5, lvl: 3 }, { lat: 47.2, lon: -122.6, r: 10, lvl: 1 }, { lat: 47.25, lon: -122.5, r: 6, lvl: 2 }, ], + + // --- Citation X demo (twin turbofan @ FL280 cruise) --- + n1: [88.4, 88.1], n2: [94.6, 94.5], itt: [702, 698], + radioAlt: 5500, mach: 0.74, aoa: 2.4, + adf1Brg: 135, adf2Brg: 295, adf1: 375, adf2: 290, + hydPress: [3120, 3120], elevTrim: -0.25, flapRatio: 0, flapDeploy: 0, + slatRatio: 0, gearHandle: 0, gearDeploy: [0, 0, 0], speedbrake: 0, parkBrake: 0, + ailDefl: 0.04, elevDefl: -0.08, rudDefl: 0.02, + battVolt: [28.0, 27.8], battTemp: [24, 24], ydOn: 1, + // override the GA single-engine arrays with twin-jet values + oilTemp: [88, 87], oilPress: [52, 53], fuelFlow: [0.366, 0.364], fuelQty: [1500, 1500], + egt: [702, 698], volts: [28.0, 27.8], amps: [-2, -2], genAmps: [120, 118], }); // a sample plan so the map/FMS show something in demo mode fp.setPlan({ name: 'DEMO', waypoints: [ diff --git a/server/config.js b/server/config.js index 86ce91f..eec9d1c 100644 --- a/server/config.js +++ b/server/config.js @@ -126,6 +126,45 @@ export const DATAREFS = { flcStatus: 'sim/cockpit2/autopilot/speed_status', gsStatus: 'sim/cockpit2/autopilot/glideslope_status', vnavStatus: 'sim/cockpit2/autopilot/vnav_status', + + // ==================================================================== + // CESSNA CITATION X (model 750) — twin Rolls-Royce AE3007C turbofans. + // Honeywell Primus 2000 suite: PFD / MFD / EICAS / dual CDU. + // All of these are universal sim datarefs, streamed live with no Lua; + // only the FMS flight-plan needs the FlyWithLua bridge (fms-sync.lua). + // ==================================================================== + // --- engine (arrays index 0 = LH, 1 = RH) --- + n1: 'sim/cockpit2/engine/indicators/N1_percent', // Fan RPM % (EICAS FAN%) + n2: 'sim/cockpit2/engine/indicators/N2_percent', // Core RPM % (standby panel) + itt: 'sim/cockpit2/engine/indicators/ITT_deg_C', // Interstage Turbine Temp °C + fuelPress: 'sim/cockpit2/engine/indicators/fuel_pressure_psi', + throttle: 'sim/cockpit2/engine/actuators/throttle_ratio', // per-engine commanded thrust + // --- Citation PFD extras --- + radioAlt: 'sim/cockpit2/gauges/indicators/radio_altimeter_height_ft_pilot', // RA (<2500 ft AGL) + mach: 'sim/cockpit2/gauges/indicators/mach_pilot', // PFD Mach / FLC target + aoa: 'sim/flightmodel/position/alpha', // angle of attack (deg) → normalised + adf1Brg: 'sim/cockpit2/radios/indicators/adf1_relative_bearing_deg', + adf2Brg: 'sim/cockpit2/radios/indicators/adf2_relative_bearing_deg', + adf1: 'sim/cockpit2/radios/actuators/adf1_frequency_hz', + adf2: 'sim/cockpit2/radios/actuators/adf2_frequency_hz', + // --- EICAS systems --- + hydPress: 'sim/cockpit2/hydraulics/indicators/hydraulic_pressure_psi', // array A/B (PSI) + elevTrim: 'sim/cockpit2/controls/elevator_trim', // -1..1 → STAB deg + flapRatio: 'sim/cockpit2/controls/flap_ratio', // 0..1 → SLAT/5/15/FULL + flapDeploy: 'sim/flightmodel2/controls/flap1_deploy_ratio', // actual trailing-edge flap + slatRatio: 'sim/flightmodel2/controls/slat_ratio', // leading-edge slat status + gearHandle: 'sim/cockpit2/controls/gear_handle_down', // 0 up / 1 down + gearDeploy: 'sim/flightmodel2/gear/deploy_ratio', // array: per-gear 0..1 + speedbrake: 'sim/cockpit2/controls/speedbrake_ratio', + parkBrake: 'sim/cockpit2/controls/parking_brake_ratio', + // control-position graphic (CTRL POS page): commanded surface ratios -1..1 + ailDefl: 'sim/cockpit2/controls/yoke_roll_ratio', + elevDefl: 'sim/cockpit2/controls/yoke_pitch_ratio', + rudDefl: 'sim/cockpit2/controls/yoke_heading_ratio', + battVolt: 'sim/cockpit2/electrical/battery_voltage', // array per battery + battTemp: 'sim/cockpit2/electrical/battery_temp_C', // ELEC page (append) + // --- yaw damper / mach-trim annunciation (Citation AP YD / M TRIM) --- + ydOn: 'sim/cockpit2/switches/yaw_damper_on', }; // Datarefs the frontend may WRITE (e.g. turning the heading bug knob). @@ -164,6 +203,16 @@ export const COMMANDS = { hdgUp: 'sim/autopilot/heading_up', hdgDown: 'sim/autopilot/heading_down', xpdrIdent: 'sim/transponder/transponder_ident', + + // --- Citation X autopilot (Honeywell Primus) extras --- + // The mode buttons reuse the universal AP commands above (hdg/nav/apr/bc/ + // altHold/flc/vs/vnav). These add the Citation-specific master functions. + yawDamper: 'sim/systems/yaw_damper_toggle', // YD button (engages w/ AP too) + apStby: 'sim/autopilot/control_wheel_steer', // STBY → basic pitch/roll (CWS), drops modes + spdUp: 'sim/autopilot/airspeed_up', // pitch wheel in FLC = target IAS/Mach + spdDown: 'sim/autopilot/airspeed_down', + vsUp: 'sim/autopilot/vertical_speed_up', // pitch wheel in V/S = target fpm + vsDown: 'sim/autopilot/vertical_speed_down', }; // Per-radio standby tuning (coarse = MHz, fine = kHz) + active/standby flip. diff --git a/web/src/App.jsx b/web/src/App.jsx index e935760..8fca01a 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -11,6 +11,12 @@ import DirectTo from './components/DirectTo.jsx'; import Proc from './components/Proc.jsx'; import FplPage from './components/FplPage.jsx'; import AudioPanel from './components/AudioPanel.jsx'; +import KAP140 from './components/KAP140.jsx'; +import CitPFD from './components/citation/CitPFD.jsx'; +import CitMFD from './components/citation/CitMFD.jsx'; +import CitEICAS from './components/citation/CitEICAS.jsx'; +import CitAP from './components/citation/CitAP.jsx'; +import CitRMU from './components/citation/CitRMU.jsx'; // Compact line icons for the nav rail (stroke = currentColor). const ICONS = { @@ -21,29 +27,58 @@ const ICONS = { ap: 'M11 4a7 7 0 100 14 7 7 0 000-14zM11 4v3M11 15v3M4 11h3M15 11h3', vfr: 'M11 4a7 7 0 100 14 7 7 0 000-14zM11 11l4.5-3', audio: 'M11 4a6 6 0 00-6 6v5M17 15v-5a6 6 0 00-6-6M4 14h2.5v4.5H4zM15.5 14H18v4.5h-2.5z', + eicas: 'M5 4v14M9 4v14M5 11h4M13 7h5M13 11h5M13 15h5', + rmu: 'M4 5h14v12H4zM7 8h8M7 11h8M7 14h4', }; function Icon({ name }) { return ( - + {name === 'map' && } ); } -const TABS = [ - { id: 'pfd', label: 'PFD' }, - { id: 'mfd', label: 'MFD' }, - { id: 'map', label: 'Map' }, - { id: 'fms', label: 'FMS' }, - { id: 'vfr', label: 'VFR' }, - { id: 'ap', label: 'Autopilot' }, - { id: 'audio', label: 'Audio' }, -]; +// Three selectable cockpit profiles. Each maps the app to a different aircraft's +// avionics suite, sharing the same bridge/datarefs underneath. +const PROFILES = { + g1000: { + label: 'Garmin G1000', short: 'G1000', + tabs: [ + { id: 'pfd', label: 'PFD' }, { id: 'mfd', label: 'MFD' }, { id: 'map', label: 'Map' }, + { id: 'fms', label: 'FMS' }, { id: 'vfr', label: 'VFR' }, { id: 'ap', label: 'Autopilot' }, + { id: 'audio', label: 'Audio' }, + ], + }, + citation: { + label: 'Cessna Citation X', short: 'CITATION X', + tabs: [ + { id: 'pfd', label: 'PFD' }, { id: 'mfd', label: 'MFD' }, { id: 'eicas', label: 'EICAS' }, + { id: 'fms', label: 'CDU/FMS' }, { id: 'ap', label: 'Autopilot' }, { id: 'rmu', label: 'Radios' }, + { id: 'map', label: 'Map' }, + ], + }, + ga: { + label: 'GA Steam (Bendix/King)', short: 'GA PANEL', + tabs: [ + { id: 'vfr', label: 'Panel' }, { id: 'ap', label: 'KAP 140' }, + { id: 'map', label: 'Map' }, { id: 'audio', label: 'Audio' }, + ], + }, +}; export default function App() { const xp = useXplane(); + // Active cockpit profile — persisted; switches the whole avionics suite. + const [profile, setProfile] = useState(() => localStorage.getItem('cockpitProfile') || 'g1000'); + const [profMenu, setProfMenu] = useState(false); + const PROF = PROFILES[profile] || PROFILES.g1000; + const TABS = PROF.tabs; + const pickProfile = (p) => { + localStorage.setItem('cockpitProfile', p); setProfile(p); setProfMenu(false); + const first = PROFILES[p].tabs[0].id; setTab(first); history.replaceState(null, '', `#${first}`); + }; const [tab, setTab] = useState(() => location.hash.replace('#', '') || 'pfd'); // Collapsible nav rail: narrow (icons) ↔ wide (icons + labels), remembered. const [navWide, setNavWide] = useState(() => localStorage.getItem('navWide') === '1'); @@ -93,6 +128,11 @@ export default function App() { const uiInset = xp.values.uiInset, uiPage = xp.values.uiMfdPage; useEffect(() => { if (uiInset === 0 || uiInset === 1) setInset(!!uiInset); }, [uiInset]); useEffect(() => { if (typeof uiPage === 'number' && MFD_PAGES[uiPage]) setMfdPage(MFD_PAGES[uiPage]); }, [uiPage]); + // Keep the active tab valid for the current profile (e.g. after a hash deep-link + // into a tab the profile doesn't have). + useEffect(() => { + if (!TABS.some((t) => t.id === tab)) { const f = TABS[0].id; setTab(f); history.replaceState(null, '', `#${f}`); } + }, [profile]); // eslint-disable-line react-hooks/exhaustive-deps const connKind = xp.xpConnected ? 'ok' : xp.connected ? 'warn' : 'bad'; const connText = xp.xpConnected ? 'X-PLANE' : xp.connected ? 'NO SIM' : 'OFFLINE'; @@ -131,10 +171,22 @@ export default function App() { return (