From 0ceb1dede3498478242cc020e2b9c6f9b379d652 Mon Sep 17 00:00:00 2001 From: karim Date: Thu, 4 Jun 2026 20:10:13 +0200 Subject: [PATCH] Citation: ADF/VOR/FMS bearing pointers wired through + PFD FMA mode bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nav Source Selector (p24) now fully drives the PFD: shared brg1/brg2 state (App ↔ RMU ↔ PFD). RMU has both pointer knobs — ◯ blue (OFF/VOR1/ADF1/FMS1) and ◇ white (OFF/VOR2/ADF2/FMS2). The PFD resolves each to a magnetic bearing (VOR bearing, ADF relative+heading, GPS bearing) and the HSI legend reflects the selected sources. - PFD FMA / AFCS mode annunciation bar across the top (lateral · AP/FD · vertical) reading the per-mode *_status datarefs — active green, armed white, matching the real Primus PFD. Co-Authored-By: Claude Opus 4.8 --- web/src/App.jsx | 9 +++-- web/src/components/citation/CitDuo.jsx | 4 +- web/src/components/citation/CitPFD.jsx | 53 ++++++++++++++++++++------ web/src/components/citation/CitRMU.jsx | 15 ++++++-- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/web/src/App.jsx b/web/src/App.jsx index a5d2ced..3512569 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -74,6 +74,9 @@ export default function App() { const xp = useXplane(); // Active cockpit profile — persisted; switches the whole avionics suite. const [profile, setProfile] = useState(() => localStorage.getItem('cockpitProfile') || 'g1000'); + // Citation Nav Source Selector bearing-pointer sources (p24): pointer 1 = cyan + // circle, pointer 2 = white diamond. Each OFF/VORn/ADFn/FMSn. Shared PFD↔RMU. + const [navSrc, setNavSrc] = useState({ brg1: 'VOR1', brg2: 'VOR2' }); const [profMenu, setProfMenu] = useState(false); const PROF = PROFILES[profile] || PROFILES.g1000; const TABS = PROF.tabs; @@ -236,12 +239,12 @@ export default function App() { )} {/* ---- Cessna Citation X suite (Honeywell Primus 2000) ---- */} - {profile === 'citation' && tab === 'duo' && } - {profile === 'citation' && tab === 'pfd' && } + {profile === 'citation' && tab === 'duo' && } + {profile === 'citation' && tab === 'pfd' && } {profile === 'citation' && tab === 'mfd' && } {profile === 'citation' && tab === 'eicas' && } {profile === 'citation' && tab === 'ap' && } - {profile === 'citation' && tab === 'rmu' && } + {profile === 'citation' && tab === 'rmu' && } {/* ---- shared tabs ---- */} {tab === 'map' && } diff --git a/web/src/components/citation/CitDuo.jsx b/web/src/components/citation/CitDuo.jsx index 88cadb0..a98759d 100644 --- a/web/src/components/citation/CitDuo.jsx +++ b/web/src/components/citation/CitDuo.jsx @@ -5,10 +5,10 @@ import CitMFD from './CitMFD.jsx'; // Side-by-side PFD + MFD — the two pilot tubes of the Citation X panel on one // tablet screen (landscape). Each keeps its own bezel/soft-keys; they scale to // fill half the width like the real instrument panel (DU-870 displays). -export default function CitDuo({ xp }) { +export default function CitDuo({ xp, navSrc }) { return (
-
+
); diff --git a/web/src/components/citation/CitPFD.jsx b/web/src/components/citation/CitPFD.jsx index 3077adb..cd85b9c 100644 --- a/web/src/components/citation/CitPFD.jsx +++ b/web/src/components/citation/CitPFD.jsx @@ -265,8 +265,9 @@ function HSI({ hdg, trk, crs, hdgBug, cdi, toFrom, brg1, brg2, srcLabel, srcColo ); } -export default function CitPFD({ xp }) { +export default function CitPFD({ xp, navSrc }) { const V = xp.values || {}; + const bsrc = navSrc || { brg1: 'VOR1', brg2: 'VOR2' }; const [std, setStd] = React.useState(false); const [raBaro, setRaBaro] = React.useState(false); // #9 RA/BARO minimums source const [min, setMin] = React.useState({ on: false, ft: 200 }); @@ -287,15 +288,37 @@ export default function CitPFD({ xp }) { const cdi = useEased(num(V.hsiDef), 0.12); const mach = useEased(num(V.mach), 0.2); const aoa = useEased(num(V.aoa), 0.12); - const brg1e = useEasedAngle(num(V.nav1Brg), 0.12); - const brg2e = useEasedAngle(num(V.nav2Brg), 0.12); + const hdgRaw = num(V.heading); + const vor1e = useEasedAngle(num(V.nav1Brg), 0.12); + const vor2e = useEasedAngle(num(V.nav2Brg), 0.12); + const adf1e = useEasedAngle(mod360(hdgRaw + num(V.adf1Brg)), 0.12); // ADF relative → mag bearing + const adf2e = useEasedAngle(mod360(hdgRaw + num(V.adf2Brg)), 0.12); + const gpsBrgE = useEasedAngle(num(V.gpsBearing), 0.12); const trk = num(V.track), toFrom = num(V.hsiToFrom); const baro = num(V.baro, 29.92); const radAlt = num(V.radioAlt, 99999); const fdOn = num(V.apMode) >= 1 || num(V.apEngaged) > 0; - // bearing pointers only when a station is received (finite, nonzero) - const brg1 = (num(V.nav1Dme) > 0 || num(V.nav1Brg) > 0) ? brg1e : null; - const brg2 = (num(V.nav2Dme) > 0 || num(V.nav2Brg) > 0) ? brg2e : null; + // bearing pointer source (Nav Source Selector): resolve each pointer to a + // magnetic bearing or null (no station / OFF). Pointer 1 = cyan ◯, 2 = white ◇. + const pickBrg = (sel) => { + if (sel === 'VOR1') return (num(V.nav1Dme) > 0 || num(V.nav1Brg) > 0) ? vor1e : null; + if (sel === 'VOR2') return (num(V.nav2Dme) > 0 || num(V.nav2Brg) > 0) ? vor2e : null; + if (sel === 'ADF1') return num(V.adf1Brg) ? adf1e : null; + if (sel === 'ADF2') return num(V.adf2Brg) ? adf2e : null; + if (sel === 'FMS1' || sel === 'FMS') return num(V.gpsBearing) ? gpsBrgE : null; + return null; + }; + const brg1 = pickBrg(bsrc.brg1); + const brg2 = pickBrg(bsrc.brg2); + // FMA / AFCS mode annunciation (active = green, armed = white) + const st = (k) => num(V[k]); + const latM = st('aprStatus') ? ['LOC', st('aprStatus')] : (st('navStatus') || st('gpssStatus')) ? ['NAV', Math.max(st('navStatus'), st('gpssStatus'))] + : st('bcStatus') ? ['BC', st('bcStatus')] : st('hdgStatus') ? ['HDG', st('hdgStatus')] : ['ROLL', 2]; + const vertM = st('gsStatus') ? ['GS', st('gsStatus')] : st('vnavStatus') ? ['VNAV', st('vnavStatus')] + : st('flcStatus') ? ['FLC', st('flcStatus')] : st('vsStatus') ? ['VS', st('vsStatus')] + : st('altStatus') ? ['ALT', st('altStatus')] : ['PITCH', 2]; + const apTxt = num(V.apEngaged) > 0 || num(V.apMode) >= 2 ? 'AP' : fdOn ? 'FD' : ''; + const fmaColor = (s) => (s >= 2 ? '#16e000' : '#fff'); // Nav source (Nav Source Selector, manual p24): 0 VOR1 · 1 VOR2 · 2 FMS. // FMS course is magenta, VOR course is green (Honeywell convention). const cdiSrc = num(V.cdiSrc); @@ -315,6 +338,14 @@ export default function CitPFD({ xp }) {
+ {/* FMA / AFCS mode annunciation bar (active green · armed white) */} + + + + {latM[0]} + {apTxt} + {vertM[0]} + {/* attitude */} {/* speed tape (#2,#3) */} @@ -336,12 +367,12 @@ export default function CitPFD({ xp }) { HDG {String(Math.round(mod360(hdgBug))).padStart(3, '0')} - {/* secondary NAV legend (#6) */} + {/* secondary NAV legend (#6) — reflects the Nav Source Selector */} - - VOR1 - - VOR2 + {bsrc.brg1 !== 'OFF' && <> + {bsrc.brg1}} + {bsrc.brg2 !== 'OFF' && <> + {bsrc.brg2}} {/* DME (#16) */} {dme > 0 && {dme.toFixed(1)}NM} diff --git a/web/src/components/citation/CitRMU.jsx b/web/src/components/citation/CitRMU.jsx index 0701243..b74fa29 100644 --- a/web/src/components/citation/CitRMU.jsx +++ b/web/src/components/citation/CitRMU.jsx @@ -15,14 +15,15 @@ const XPDR = ['OFF', 'STBY', 'ON', 'ALT']; const TCAS_RNG = [6, 12, 20, 40]; const TCAS_MODE = ['NORMAL', 'ABOVE', 'BELOW']; -export default function CitRMU({ xp }) { +export default function CitRMU({ xp, navSrc, onNavSrc }) { const V = xp.values || {}; const cmd = xp.command, sd = xp.setDataref; const [bank, setBank] = useState(1); // tuning radio: 1 or 2 const [sel, setSel] = useState('com'); // which standby is armed for tuning: com|nav|adf|xpdr const [tcasR, setTcasR] = useState(1); // index into TCAS_RNG const [tcasM, setTcasM] = useState(0); - const [navSrc, setNavSrc] = useState('VOR1'); // bearing-pointer source (#6 secondary) + const bsrc = navSrc || { brg1: 'VOR1', brg2: 'VOR2' }; + const setBrg = (k, v) => onNavSrc && onNavSrc((s) => ({ ...s, [k]: v })); const r = bank; // 1 / 2 const tuneUp = () => cmd(`${sel}${r}CoarseUp`); @@ -116,12 +117,18 @@ export default function CitRMU({ xp }) {
BRG POINTER ◯ (blue)
{['OFF', 'VOR1', 'ADF1', 'FMS1'].map((s) => ( - + + ))} +
+
BRG POINTER ◇ (white)
+
+ {['OFF', 'VOR2', 'ADF2', 'FMS2'].map((s) => ( + ))}
NAV → CDI source (green) on the PFD · FMS = flight-plan guidance · - BRG pointers (◯ blue / ◇ white) show VOR / ADF / FMS bearings. + BRG pointers (◯ blue VOR1/ADF1/FMS · ◇ white VOR2/ADF2) appear on the PFD HSI.