Files
xplane-cockpit/web/src/components/citation/CitAP.jsx
T
karim b05ffedbc1 Citation X cockpit profile: full Primus 2000 suite (PFD/MFD/EICAS/AP/RMU)
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 <noreply@anthropic.com>
2026-06-04 12:09:55 +02:00

101 lines
5.1 KiB
React

import React, { useState } from 'react';
import { num } from '../../api/useXplane.js';
// ============================================================================
// Citation X — Honeywell Primus 2000 Autopilot / Flight Guidance Controller.
// Exact button layout per the manual (pages 26-28):
// col1: HDG NAV APP BC col2: ALT VNAV BANK STBY
// col3: FLC C/O VS center: PITCH WHEEL (NOSE DN / NOSE UP)
// col4: AP YD M TRIM PFD SEL
// Mode lamps read the per-mode *_status datarefs (0 off · 1 armed · 2 active),
// the same reliable source the PFD uses. Buttons fire X-Plane AP commands.
// ============================================================================
export default function CitAP({ xp }) {
const V = xp.values || {};
const cmd = xp.command;
const stat = (k) => num(V[k]); // 0 off · 1 armed · 2 active
const apOn = num(V.apEngaged) > 0 || num(V.apMode) >= 2;
const ydOn = num(V.ydOn) > 0 || apOn;
const [bank, setBank] = useState(false); // BANK (low-bank 17°) — annunc only
const [mtrim, setMtrim] = useState(true); // M TRIM (mach trim) — annunc only
const [pfdSel, setPfdSel] = useState('PILOT'); // PFD SEL — pilot/copilot guidance
// active mode strings for the annunciator bar (matches the PFD)
const lateral = stat('aprStatus') ? ['APP', stat('aprStatus')] : stat('navStatus') ? ['NAV', stat('navStatus')]
: stat('bcStatus') ? ['BC', stat('bcStatus')] : stat('hdgStatus') ? ['HDG', stat('hdgStatus')] : ['ROL', 2];
const vertical = stat('gsStatus') ? ['GS', stat('gsStatus')] : stat('vnavStatus') ? ['VNAV', stat('vnavStatus')]
: stat('flcStatus') ? ['FLC', stat('flcStatus')] : stat('vsStatus') ? ['VS', stat('vsStatus')]
: stat('altStatus') ? ['ALT', stat('altStatus')] : ['PIT', 2];
// A mode button: green lamp when its status is active(2), amber when armed(1).
const lamp = (k) => (stat(k) >= 2 ? 'active' : stat(k) === 1 ? 'armed' : '');
const Btn = ({ label, cmd: c, on, cls = '', onClick }) => (
<button className={`citap-btn ${on || cls} ${cls}`} onClick={onClick || (() => c && cmd(c))}>
<span className="citap-arrow"></span>{label}
</button>
);
const sel = num(V.apAltBug);
return (
<div className="cit-screen citap-screen">
{/* selected references row (alt / hdg / spd / vs) */}
<div className="citap-refs">
<div><span>ALT SEL</span><b>{Math.round(num(V.apAltBug))}</b></div>
<div><span>HDG</span><b>{String(Math.round(num(V.apHdgBug)) % 360).padStart(3, '0')}</b></div>
<div><span>IAS/M</span><b>{num(V.mach) >= 0.4 ? num(V.mach).toFixed(2) : Math.round(num(V.apSpdBug))}</b></div>
<div><span>VS</span><b>{Math.round(num(V.apVsBug))}</b></div>
</div>
{/* FMA annunciator bar (active = green, armed = white) */}
<div className="citap-fma">
<span className={lateral[1] >= 2 ? 'fma-act' : 'fma-arm'}>{lateral[0]}</span>
<span className="fma-ap">{apOn ? 'AP' : 'FD'}{ydOn ? ' · YD' : ''}</span>
<span className={vertical[1] >= 2 ? 'fma-act' : 'fma-arm'}>{vertical[0]}</span>
</div>
<div className="citap-panel">
<div className="citap-col">
<Btn label="HDG" cmd="hdg" on={lamp('hdgStatus')} />
<Btn label="NAV" cmd="nav" on={lamp('navStatus')} />
<Btn label="APP" cmd="apr" on={lamp('aprStatus')} />
<Btn label="BC" cmd="backCourse" on={lamp('bcStatus')} />
</div>
<div className="citap-col">
<Btn label="ALT" cmd="altHold" on={lamp('altStatus')} />
<Btn label="VNAV" cmd="vnav" on={lamp('vnavStatus')} />
<Btn label="BANK" on={bank ? 'active' : ''} onClick={() => setBank((v) => !v)} />
<Btn label="STBY" cmd="apStby" onClick={() => cmd('apStby')} />
</div>
<div className="citap-col">
<Btn label="FLC" cmd="flc" on={lamp('flcStatus')} />
<Btn label="C/O" cls="dim" onClick={() => {}} />
<Btn label="VS" cmd="vs" on={lamp('vsStatus')} />
</div>
{/* PITCH WHEEL — VS rate (in VS) or IAS/Mach target (in FLC) */}
<div className="citap-wheel">
<div className="citap-wlbl">NOSE UP</div>
<button className="citap-wbtn" onClick={() => cmd(stat('flcStatus') ? 'spdDown' : 'vsUp')}></button>
<div className="citap-wheelface" />
<button className="citap-wbtn" onClick={() => cmd(stat('flcStatus') ? 'spdUp' : 'vsDown')}></button>
<div className="citap-wlbl">NOSE DN</div>
</div>
<div className="citap-col citap-master">
<Btn label="AP" cmd="apToggle" on={apOn ? 'active' : ''} />
<Btn label="YD" cmd="yawDamper" on={ydOn ? 'active' : ''} />
<Btn label="M TRIM" on={mtrim ? 'active' : ''} onClick={() => setMtrim((v) => !v)} />
<Btn label="PFD SEL" onClick={() => setPfdSel((p) => (p === 'PILOT' ? 'COPILOT' : 'PILOT'))} />
</div>
</div>
<div className="citap-foot">
AP MASTER engages Yaw Damper automatically · PITCH WHEEL sets V/S rate or FLC speed ·
PFD SEL: <b>{pfdSel}</b> guidance{bank ? ' · LOW BANK 17°' : ''}{mtrim ? ' · MACH TRIM' : ''}
</div>
</div>
);
}