diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index e941b20c566..8750b398b7b 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -105,6 +105,7 @@ 1. [ND] Fix color of navaids at the active leg termination - @BlueberryKing (BlueberryKing) 1. [EFB] Added a takeoff performance calculator - @donstim (donbikes), @tracernz (Mike) 1. [MCDU] Removed V-speed auto-fill function - @tracernz (Mike) +1. [PFD] Implement alerts within artificial horizon (ROP, ROW, OANS, stall, windshear) @flogross89 (Flo) ## 0.11.0 diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/sound/sound.xml b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/sound/sound.xml index 1927b61bc40..eddb808bf5b 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/sound/sound.xml +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/SimObjects/AirPlanes/FlyByWire_A320_NEO/sound/sound.xml @@ -2306,6 +2306,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2313,8 +2350,6 @@ - - @@ -2388,13 +2423,6 @@ - - - - - - - diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_GPWS.js b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_GPWS.js index 81b35706a18..8db734859b8 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_GPWS.js +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/A32NX_Core/A32NX_GPWS.js @@ -98,7 +98,7 @@ class A32NX_GPWS { {}, ], onChange: (current, _) => { - SimVar.SetSimVarValue("L:A32NX_GPWS_GS_Warning_Active", "Bool", current >= 1); + this.setGlideSlopeWarning(current >= 1); } } ]; @@ -109,6 +109,44 @@ class A32NX_GPWS { this.AltCallState.setState("ground"); this.RetardState = A32NX_Util.createMachine(RetardStateMachine); this.RetardState.setState("landed"); + + this.egpwsAlertDiscreteWord1 = Arinc429Word.empty(); + this.egpwsAlertDiscreteWord2 = Arinc429Word.empty(); + } + + gpwsUpdateDiscreteWords() { + this.egpwsAlertDiscreteWord1.ssm = Arinc429Word.SignStatusMatrix.NormalOperation; + this.egpwsAlertDiscreteWord1.setBitValue(11, this.modes[0].current === 1); + this.egpwsAlertDiscreteWord1.setBitValue(12, this.modes[0].current === 2); + this.egpwsAlertDiscreteWord1.setBitValue(13, this.modes[1].current === 1); + this.egpwsAlertDiscreteWord1.setBitValue(12, this.modes[1].current === 2); + this.egpwsAlertDiscreteWord1.setBitValue(14, this.modes[2].current === 1); + this.egpwsAlertDiscreteWord1.setBitValue(15, this.modes[3].current === 1); + this.egpwsAlertDiscreteWord1.setBitValue(16, this.modes[3].current === 2); + this.egpwsAlertDiscreteWord1.setBitValue(17, this.modes[3].current === 3); + this.egpwsAlertDiscreteWord1.setBitValue(18, this.modes[4].current === 1); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_1_DISCRETE_WORD_1', this.egpwsAlertDiscreteWord1.value, this.egpwsAlertDiscreteWord1.ssm); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_2_DISCRETE_WORD_1', this.egpwsAlertDiscreteWord1.value, this.egpwsAlertDiscreteWord1.ssm); + + this.egpwsAlertDiscreteWord2.ssm = Arinc429Word.SignStatusMatrix.NormalOperation; + this.egpwsAlertDiscreteWord2.setBitValue(14, false); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_1_DISCRETE_WORD_2', this.egpwsAlertDiscreteWord2.value, this.egpwsAlertDiscreteWord2.ssm); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_2_DISCRETE_WORD_2', this.egpwsAlertDiscreteWord2.value, this.egpwsAlertDiscreteWord2.ssm); + } + + setGlideSlopeWarning(state) { + SimVar.SetSimVarValue('L:A32NX_GPWS_GS_Warning_Active', 'Bool', state ? 1 : 0); // Still need this for XML + this.egpwsAlertDiscreteWord2.setBitValue(11, state); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_1_DISCRETE_WORD_2', this.egpwsAlertDiscreteWord2.value, this.egpwsAlertDiscreteWord2.ssm); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_2_DISCRETE_WORD_2', this.egpwsAlertDiscreteWord2.value, this.egpwsAlertDiscreteWord2.ssm); + } + + setGpwsWarning(state) { + SimVar.SetSimVarValue('L:A32NX_GPWS_Warning_Active', 'Bool', state ? 1 : 0); // Still need this for XML + this.egpwsAlertDiscreteWord2.setBitValue(12, state); + this.egpwsAlertDiscreteWord2.setBitValue(13, state); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_1_DISCRETE_WORD_2', this.egpwsAlertDiscreteWord2.value, this.egpwsAlertDiscreteWord2.ssm); + Arinc429Word.toSimVarValue('L:A32NX_EGPWS_ALERT_2_DISCRETE_WORD_2', this.egpwsAlertDiscreteWord2.value, this.egpwsAlertDiscreteWord2.ssm); } init() { @@ -116,8 +154,8 @@ class A32NX_GPWS { this.radnav.init(NavMode.FOUR_SLOTS); - SimVar.SetSimVarValue("L:A32NX_GPWS_GS_Warning_Active", "Bool", 0); - SimVar.SetSimVarValue("L:A32NX_GPWS_Warning_Active", "Bool", 0); + this.setGlideSlopeWarning(false); + this.setGpwsWarning(false); NXDataStore.getAndSubscribe('CONFIG_A32NX_FWC_RADIO_AUTO_CALL_OUT_PINS', (k, v) => k === 'CONFIG_A32NX_FWC_RADIO_AUTO_CALL_OUT_PINS' && (this.autoCallOutPins = v), DEFAULT_RADIO_AUTO_CALL_OUTS); } @@ -171,11 +209,12 @@ class A32NX_GPWS { this.Mode4MaxRAAlt = NaN; } - SimVar.SetSimVarValue("L:A32NX_GPWS_GS_Warning_Active", "Bool", 0); - SimVar.SetSimVarValue("L:A32NX_GPWS_Warning_Active", "Bool", 0); + this.setGlideSlopeWarning(false); + this.setGpwsWarning(false); } this.GPWSComputeLightsAndCallouts(); + this.gpwsUpdateDiscreteWords(); if ((mda !== 0 || (dh !== -1 && dh !== -2) && phase === FmgcFlightPhases.APPROACH)) { let minimumsDA; //MDA or DH @@ -276,7 +315,7 @@ class A32NX_GPWS { } const illuminateGpwsLight = activeTypes.some((type) => type.gpwsLight); - SimVar.SetSimVarValue("L:A32NX_GPWS_Warning_Active", "Bool", illuminateGpwsLight); + this.setGpwsWarning(illuminateGpwsLight); } /** diff --git a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/NavSystems/A320_Neo/A32NX_NavSystem.js b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/NavSystems/A320_Neo/A32NX_NavSystem.js index 1ea84ca0d35..3b6c427fabf 100644 --- a/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/NavSystems/A320_Neo/A32NX_NavSystem.js +++ b/fbw-a32nx/src/base/flybywire-aircraft-a320-neo/html_ui/Pages/VCockpit/Instruments/NavSystems/A320_Neo/A32NX_NavSystem.js @@ -438,7 +438,9 @@ class NavSystem extends BaseInstrument { } try { this.onUpdate(this.accumulatedDeltaTime); - } catch (e) {} + } catch (e) { + console.log("Uncaught exception", e); + } const t = performance.now() - t0; NavSystem.maxTimeUpdateAllTime = Math.max(t, NavSystem.maxTimeUpdateAllTime); NavSystem.maxTimeUpdate = Math.max(t, NavSystem.maxTimeUpdate); diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index 1a40cf57230..6b90ea4a62f 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -12,10 +12,13 @@ import { ConsumerSubject, SimVarValueType, SubscribableMapFunctions, + StallWarningEvents, } from '@microsoft/msfs-sdk'; import { Arinc429Register, + Arinc429RegisterSubject, + Arinc429SignStatusMatrix, Arinc429Word, NXDataStore, NXLogicClockNode, @@ -32,6 +35,15 @@ export function xor(a: boolean, b: boolean): boolean { return !!((a ? 1 : 0) ^ (b ? 1 : 0)); } +/** + * Counts the number of truthy values in an array of booleans + * @param args + * @returns + */ +function countTrue(...args: boolean[]): number { + return args.reduce((accu, b) => (b ? accu + 1 : accu), 0); +} + interface EWDItem { flightPhaseInhib: number[]; /** warning is active */ @@ -59,7 +71,9 @@ enum FwcAuralWarning { } export class PseudoFWC { - private readonly sub = this.bus.getSubscriber(); + private readonly sub = this.bus.getSubscriber(); + /** Time to inhibit master warnings and cautions during startup in ms */ + private static readonly FWC_STARTUP_TIME = 5000; /** Time to inhibit SCs after one is trigger in ms */ private static readonly AURAL_SC_INHIBIT_TIME = 2000; @@ -88,6 +102,9 @@ export class PseudoFWC { ); /* PSEUDO FWC VARIABLES */ + private readonly startupTimer = new DebounceTimer(); + + private readonly startupCompleted = Subject.create(false); private readonly allCurrentFailures: string[] = []; @@ -117,10 +134,13 @@ export class PseudoFWC { private nonCancellableWarningCount = 0; + private readonly stallWarning = Subject.create(false); + private readonly masterWarningOutput = MappedSubject.create( SubscribableMapFunctions.or(), this.masterWarning, this.fireActive, + this.stallWarning, ); private readonly auralCrcOutput = MappedSubject.create( @@ -129,6 +149,8 @@ export class PseudoFWC { this.fireActive, ); + private readonly fwcOut126 = Arinc429RegisterSubject.createEmpty(); + /* 21 - AIR CONDITIONING AND PRESSURIZATION */ private readonly acsc1DiscreteWord1 = Arinc429Register.empty(); @@ -584,7 +606,7 @@ export class PseudoFWC { /* LANDING GEAR AND LIGHTS */ - private readonly aircraftOnGround = Subject.create(0); + private readonly aircraftOnGround = Subject.create(false); private readonly antiskidActive = Subject.create(false); @@ -604,6 +626,10 @@ export class PseudoFWC { private readonly lgciu2DiscreteWord1 = Arinc429Register.empty(); + private readonly lgciu1DiscreteWord2 = Arinc429Register.empty(); + + private readonly lgciu2DiscreteWord2 = Arinc429Register.empty(); + private readonly nwSteeringDisc = Subject.create(false); private readonly parkBrake = Subject.create(false); @@ -618,6 +644,28 @@ export class PseudoFWC { private readonly lgNotDownPulse2 = new NXLogicPulseNode(); + private readonly lgciu1OnGroundDisagreeConf = new NXLogicConfirmNode(1, true); + + private readonly lgciu1OnGroundAgreeConf = new NXLogicConfirmNode(0.5, true); + + private readonly lgciu1OnGroundDisagreeMem = new NXLogicMemoryNode(true); + + private readonly lgciu2OnGroundDisagreeConf = new NXLogicConfirmNode(1, true); + + private readonly lgciu2OnGroundAgreeConf = new NXLogicConfirmNode(0.5, true); + + private readonly lgciu2OnGroundDisagreeMem = new NXLogicMemoryNode(true); + + private readonly ra1OnGroundMem = new NXLogicMemoryNode(true); + + private readonly ra2OnGroundMem = new NXLogicMemoryNode(true); + + private readonly ignoreRaOnGroundTrigger = new NXLogicTriggeredMonostableNode(10, true); + + private readonly onGroundConf = new NXLogicConfirmNode(1, true); + + private onGroundImmediate = false; + /* NAVIGATION */ private readonly adirsRemainingAlignTime = Subject.create(0); @@ -628,9 +676,13 @@ export class PseudoFWC { private readonly adiru3State = Subject.create(0); - private readonly computedAirSpeed = Subject.create(Arinc429Word.empty()); + private readonly adr1Cas = Subject.create(Arinc429Word.empty()); + + private readonly adr2Cas = Arinc429Register.empty(); + + private readonly adr3Cas = Arinc429Register.empty(); - private readonly computedAirSpeedToNearest2 = this.computedAirSpeed.map((it) => Math.round(it.value / 2) * 2); + private readonly computedAirSpeedToNearest2 = this.adr1Cas.map((it) => Math.round(it.value / 2) * 2); private readonly height1Failed = Subject.create(false); @@ -642,6 +694,8 @@ export class PseudoFWC { private readonly flapsIndex = Subject.create(0); + private stallWarningRaw = ConsumerValue.create(this.sub.on('stall_warning_on'), false); + /** ENGINE AND THROTTLE */ private readonly engine1Master = ConsumerSubject.create(this.sub.on('engine1Master'), 0); @@ -884,11 +938,13 @@ export class PseudoFWC { this.auralCrcOutput.sub((crc) => SimVar.SetSimVarValue('L:A32NX_FWC_CRC', 'bool', crc)); this.masterCaution.sub((caution) => { - SimVar.SetSimVarValue('L:A32NX_MASTER_CAUTION', 'bool', caution); + // Inhibit master warning/cautions until FWC startup has been completed + SimVar.SetSimVarValue('L:A32NX_MASTER_CAUTION', 'bool', this.startupCompleted.get() ? caution : false); }, true); this.masterWarningOutput.sub((warning) => { - SimVar.SetSimVarValue('L:A32NX_MASTER_WARNING', 'Bool', warning); + // Inhibit master warning/cautions until FWC startup has been completed + SimVar.SetSimVarValue('L:A32NX_MASTER_WARNING', 'Bool', this.startupCompleted.get() ? warning : false); }, true); // L/G lever red arrow sinking outputs @@ -898,6 +954,21 @@ export class PseudoFWC { SimVar.SetSimVarValue('L:A32NX_FWC_2_LG_RED_ARROW', SimVarValueType.Bool, on); }, true); + this.stallWarning.sub((v) => { + this.fwcOut126.setBitValue(17, v); + // set the sound on/off + SimVar.SetSimVarValue('L:A32NX_AUDIO_STALL_WARNING', 'bool', v); + }, true); + this.aircraftOnGround.sub((v) => this.fwcOut126.setBitValue(28, v)); + + this.fwcOut126.sub((v) => { + Arinc429Word.toSimVarValue('L:A32NX_FWC_1_DISCRETE_WORD_126', v.value, v.ssm); + Arinc429Word.toSimVarValue('L:A32NX_FWC_2_DISCRETE_WORD_126', v.value, v.ssm); + }, true); + + // FIXME depend on FWC state + this.fwcOut126.setSsm(Arinc429SignStatusMatrix.NormalOperation); + const sub = this.bus.getSubscriber(); this.fuelCtrTankModeSelMan.setConsumer(sub.on('fuel_ctr_tk_mode_sel_man')); @@ -911,6 +982,25 @@ export class PseudoFWC { this.rightOuterInnerValve.setConsumer(sub.on('fuel_valve_open_5')); this.rightFuelPump1Auto.setConsumer(sub.on('fuel_pump_switch_3')); this.rightFuelPump2Auto.setConsumer(sub.on('fuel_pump_switch_6')); + + // Inhibit single chimes for the first two seconds after power-on + this.auralSingleChimeInhibitTimer.schedule( + () => (this.auralSingleChimePending = false), + PseudoFWC.AURAL_SC_INHIBIT_TIME, + ); + + this.acESSBusPowered.sub((v) => { + if (v) { + this.startupTimer.schedule(() => { + this.startupCompleted.set(true); + console.log('PseudoFWC startup completed.'); + }, PseudoFWC.FWC_STARTUP_TIME); + } else { + this.startupTimer.clear(); + this.startupCompleted.set(false); + console.log('PseudoFWC shut down.'); + } + }); } mapOrder(array, order): [] { @@ -1007,6 +1097,7 @@ export class PseudoFWC { this.flightPhase129.set([1, 2, 9].includes(this.fwcFlightPhase.get())); this.flightPhase67.set([6, 7].includes(this.fwcFlightPhase.get())); this.flightPhase78.set([7, 8].includes(this.fwcFlightPhase.get())); + const flightPhase567 = [5, 6, 7].includes(this.fwcFlightPhase.get()); // TO CONFIG button this.toConfigTestRaw = SimVar.GetSimVarValue('L:A32NX_BTN_TOCONFIG', 'bool') > 0; @@ -1049,12 +1140,22 @@ export class PseudoFWC { this.flapsIndex.set(SimVar.GetSimVarValue('L:A32NX_FLAPS_CONF_INDEX', 'number')); - this.computedAirSpeed.set(Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_1_COMPUTED_AIRSPEED')); + this.adr1Cas.set(Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_1_COMPUTED_AIRSPEED')); + this.adr2Cas.setFromSimVar('L:A32NX_ADIRS_ADR_2_COMPUTED_AIRSPEED'); + this.adr3Cas.setFromSimVar('L:A32NX_ADIRS_ADR_3_COMPUTED_AIRSPEED'); - // needs to happen before dual eng failure check - this.aircraftOnGround.set(SimVar.GetSimVarValue('SIM ON GROUND', 'Bool')); + // RA acquisition + this.radioHeight1.setFromSimVar('L:A32NX_RA_1_RADIO_ALTITUDE'); + this.radioHeight2.setFromSimVar('L:A32NX_RA_2_RADIO_ALTITUDE'); + + /* ELECTRICAL acquisition */ + this.dcESSBusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_DC_ESS_BUS_IS_POWERED', 'bool')); + this.dc2BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'bool')); + this.ac1BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_1_BUS_IS_POWERED', 'bool')); + this.ac2BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_2_BUS_IS_POWERED', 'bool')); + this.acESSBusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_ESS_BUS_IS_POWERED', 'bool')); - /* ENGINE AND THROTTLE */ + /* ENGINE AND THROTTLE acquisition */ this.engine1State.set(SimVar.GetSimVarValue('L:A32NX_ENGINE_STATE:1', 'Enum')); this.engine2State.set(SimVar.GetSimVarValue('L:A32NX_ENGINE_STATE:2', 'Enum')); @@ -1063,6 +1164,7 @@ export class PseudoFWC { this.N2Eng1.set(SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:1', 'number')); this.N2Eng2.set(SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:2', 'number')); this.N1IdleEng.set(SimVar.GetSimVarValue('L:A32NX_ENGINE_IDLE_N1', 'number')); + // FIXME move out of acquisition to logic below const oneEngineAboveMinPower = this.engine1AboveIdle.get() || this.engine2AboveIdle.get(); this.engine1Generator.set(SimVar.GetSimVarValue('L:A32NX_ELEC_ENG_GEN_1_POTENTIAL_NORMAL', 'bool')); @@ -1075,8 +1177,6 @@ export class PseudoFWC { this.apuBleedValveOpen.set(SimVar.GetSimVarValue('L:A32NX_APU_BLEED_AIR_VALVE_OPEN', 'bool')); this.radioAlt.set(SimVar.GetSimVarValue('PLANE ALT ABOVE GROUND MINUS CG', 'feet')); - this.radioHeight1.setFromSimVar('L:A32NX_RA_1_RADIO_ALTITUDE'); - this.radioHeight2.setFromSimVar('L:A32NX_RA_2_RADIO_ALTITUDE'); this.fac1Failed.set(SimVar.GetSimVarValue('L:A32NX_FBW_FAC_FAILED:1', 'boost psi')); @@ -1096,27 +1196,8 @@ export class PseudoFWC { this.autoThrustStatus.set(SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_STATUS', 'enum')); this.autothrustLeverWarningFlex.set(SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_THRUST_LEVER_WARNING_FLEX', 'bool')); this.autothrustLeverWarningToga.set(SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_THRUST_LEVER_WARNING_TOGA', 'bool')); - this.thrustLeverNotSet.set(this.autothrustLeverWarningFlex.get() || this.autothrustLeverWarningToga.get()); - // FIXME ECU doesn't have the necessary output words so we go purely on TLA - const flexThrustLimit = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_THRUST_LIMIT_TYPE', 'number') === 3; - const toPower = - this.throttle1Position.get() >= 45 || - (this.throttle1Position.get() >= 35 && flexThrustLimit) || - this.throttle2Position.get() >= 45 || - (this.throttle2Position.get() >= 35 && flexThrustLimit); - this.eng1Or2TakeoffPowerConfirm.write(toPower, deltaTime); - const raAbove1500 = this.radioHeight1.valueOr(0) > 1500 || this.radioHeight2.valueOr(0) > 1500; - this.eng1Or2TakeoffPower.set(toPower || (this.eng1Or2TakeoffPowerConfirm.read() && !raAbove1500)); - - this.engDualFault.set( - !this.aircraftOnGround.get() && - ((this.fireButton1.get() && this.fireButton2.get()) || - (!this.engine1ValueSwitch.get() && !this.engine2ValueSwitch.get()) || - (this.engine1State.get() === 0 && this.engine2State.get() === 0) || - (!this.engine1CoreAtOrAboveMinIdle.get() && !this.engine2CoreAtOrAboveMinIdle.get())), - ); - /* HYDRAULICS */ + /* HYDRAULICS acquisition */ this.blueElecPumpPBAuto.set(SimVar.GetSimVarValue('L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO', 'bool')); this.blueLP.set(SimVar.GetSimVarValue('L:A32NX_HYD_BLUE_EDPUMP_LOW_PRESS', 'bool')); @@ -1137,7 +1218,7 @@ export class PseudoFWC { const greenSysPressurised = SimVar.GetSimVarValue('L:A32NX_HYD_GREEN_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool'); const yellowSysPressurised = SimVar.GetSimVarValue('L:A32NX_HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool'); - /* ADIRS */ + /* ADIRS acquisition */ this.adirsRemainingAlignTime.set(SimVar.GetSimVarValue('L:A32NX_ADIRS_REMAINING_IR_ALIGNMENT_TIME', 'Seconds')); @@ -1155,32 +1236,11 @@ export class PseudoFWC { this.height1Failed.set(height1.isFailureWarning()); this.height2Failed.set(height2.isFailureWarning()); // overspeed - const adr3Cas = Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_3_COMPUTED_AIRSPEED'); const adr3MaxCas = Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_3_MAX_AIRSPEED'); - let overspeedWarning = this.adr3OverspeedWarning.write( - adr3Cas.isNormalOperation() && adr3MaxCas.isNormalOperation() && adr3Cas.value > adr3MaxCas.value + 8, - this.aircraftOnGround.get() || - !(adr3Cas.isNormalOperation() && adr3MaxCas.isNormalOperation()) || - adr3Cas.value < adr3MaxCas.value + 4, - ); const adr1Discrete1 = Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_1_DISCRETE_WORD_1'); const adr2Discrete1 = Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_2_DISCRETE_WORD_1'); - if ( - !(adr1Discrete1.isNormalOperation() || adr1Discrete1.isFunctionalTest()) || - !(adr2Discrete1.isNormalOperation() || adr2Discrete1.isFunctionalTest()) - ) { - const adr3Discrete1 = Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_3_DISCRETE_WORD_1'); - overspeedWarning ||= adr3Discrete1.getBitValueOr(9, false); - } - overspeedWarning ||= adr1Discrete1.getBitValueOr(9, false) || adr2Discrete1.getBitValueOr(9, false); - this.overspeedWarning.set(overspeedWarning); - - /* LANDING GEAR AND LIGHTS */ - // const [left1LandingGear] = useSimVar('L:A32NX_LGCIU_1_LEFT_GEAR_COMPRESSED', 'bool', 500); - // const [right1LandingGear] = useSimVar('L:A32NX_LGCIU_1_RIGHT_GEAR_COMPRESSED', 'bool', 500); - // const aircraftOnGround = left1LandingGear === 1 || right1LandingGear === 1; - // FIXME The landing gear triggers the dual engine failure on loading + /* LANDING GEAR AND LIGHTS acquisition */ this.antiskidActive.set(SimVar.GetSimVarValue('ANTISKID BRAKES ACTIVE', 'bool')); this.brakeFan.set(SimVar.GetSimVarValue('L:A32NX_BRAKE_FAN', 'bool')); @@ -1193,9 +1253,17 @@ export class PseudoFWC { this.lgciu1Fault.set(SimVar.GetSimVarValue('L:A32NX_LGCIU_1_FAULT', 'bool')); this.lgciu2Fault.set(SimVar.GetSimVarValue('L:A32NX_LGCIU_2_FAULT', 'bool')); this.lgciu1DiscreteWord1.setFromSimVar('L:A32NX_LGCIU_1_DISCRETE_WORD_1'); - this.lgciu2DiscreteWord1.setFromSimVar('L:A32NX_LGCIU_1_DISCRETE_WORD_1'); + this.lgciu2DiscreteWord1.setFromSimVar('L:A32NX_LGCIU_2_DISCRETE_WORD_1'); + this.lgciu1DiscreteWord2.setFromSimVar('L:A32NX_LGCIU_1_DISCRETE_WORD_2'); + this.lgciu2DiscreteWord2.setFromSimVar('L:A32NX_LGCIU_2_DISCRETE_WORD_2'); this.parkBrake.set(SimVar.GetSimVarValue('L:A32NX_PARK_BRAKE_LEVER_POS', 'Bool')); this.nwSteeringDisc.set(SimVar.GetSimVarValue('L:A32NX_HYD_NW_STRG_DISC_ECAM_MEMO', 'Bool')); + const leftCompressedHardwireLgciu1 = + this.dcESSBusPowered.get() && SimVar.GetSimVarValue('A32NX_LGCIU_1_L_GEAR_COMPRESSED', 'bool') > 0; + const leftCompressedHardwireLgciu2 = + this.dc2BusPowered.get() && SimVar.GetSimVarValue('A32NX_LGCIU_2_L_GEAR_COMPRESSED', 'bool') > 0; + + // General logic const mainGearDownlocked = (this.lgciu1DiscreteWord1.bitValueOr(23, false) || this.lgciu2DiscreteWord1.bitValueOr(23, false)) && (this.lgciu1DiscreteWord1.bitValueOr(24, false) || this.lgciu2DiscreteWord1.bitValueOr(24, false)); @@ -1204,6 +1272,83 @@ export class PseudoFWC { mainGearDownlocked && (this.lgciu1DiscreteWord1.bitValueOr(25, false) || this.lgciu2DiscreteWord1.bitValueOr(25, false)); + // on ground logic + const lgciu1Disagree = xor(leftCompressedHardwireLgciu1, this.lgciu1DiscreteWord2.bitValue(13)); + this.lgciu1OnGroundDisagreeConf.write(lgciu1Disagree, deltaTime); + this.lgciu1OnGroundAgreeConf.write(!lgciu1Disagree, deltaTime); + this.lgciu1OnGroundDisagreeMem.write( + (!this.lgciu1DiscreteWord2.isNormalOperation() && !this.lgciu1DiscreteWord2.isFunctionalTest()) || + this.lgciu1OnGroundDisagreeConf.read(), + this.lgciu1OnGroundAgreeConf.read(), + ); + const lgciu2Disagree = xor(leftCompressedHardwireLgciu2, this.lgciu2DiscreteWord2.bitValue(13)); + this.lgciu2OnGroundDisagreeConf.write(lgciu2Disagree, deltaTime); + this.lgciu2OnGroundAgreeConf.write(!lgciu2Disagree, deltaTime); + this.lgciu2OnGroundDisagreeMem.write( + (!this.lgciu2DiscreteWord2.isNormalOperation() && !this.lgciu2DiscreteWord2.isFunctionalTest()) || + this.lgciu2OnGroundDisagreeConf.read(), + this.lgciu2OnGroundAgreeConf.read(), + ); + const lgciuOnGroundDisagree = this.lgciu1OnGroundDisagreeMem.read() || this.lgciu2OnGroundDisagreeMem.read(); + const onGroundA = + leftCompressedHardwireLgciu1 && + this.lgciu1DiscreteWord2.bitValue(13) && + leftCompressedHardwireLgciu2 && + this.lgciu2DiscreteWord2.bitValue(13); + + // TODO some renaming + this.ignoreRaOnGroundTrigger.write( + this.radioHeight1.isNoComputedData() && this.radioHeight2.isNoComputedData() && !lgciuOnGroundDisagree, + deltaTime, + ); + this.ra1OnGroundMem.write( + this.radioHeight1.value < 5, + !leftCompressedHardwireLgciu1 || !leftCompressedHardwireLgciu2, + ); + this.ra2OnGroundMem.write( + this.radioHeight2.value < 5, + !leftCompressedHardwireLgciu1 || !leftCompressedHardwireLgciu2, + ); + const ra1OnGround = + (this.radioHeight1.isNormalOperation() || this.radioHeight1.isFunctionalTest()) && + (this.radioHeight1.value < 5 || this.ra1OnGroundMem.read()); + const ra2OnGround = + (this.radioHeight2.isNormalOperation() || this.radioHeight2.isFunctionalTest()) && + (this.radioHeight2.value < 5 || this.ra2OnGroundMem.read()); + const onGroundCount = countTrue( + leftCompressedHardwireLgciu1, + leftCompressedHardwireLgciu2, + ra1OnGround, + ra2OnGround, + ); + const raInvalid = this.radioHeight1.isFailureWarning() || this.radioHeight2.isFailureWarning(); + this.onGroundImmediate = + (onGroundA && this.ignoreRaOnGroundTrigger.read()) || + (onGroundCount > 2 && !raInvalid) || + (onGroundCount > 1 && raInvalid); + this.aircraftOnGround.set(this.onGroundConf.write(this.onGroundImmediate, deltaTime)); + + // Engine Logic + this.thrustLeverNotSet.set(this.autothrustLeverWarningFlex.get() || this.autothrustLeverWarningToga.get()); + // FIXME ECU doesn't have the necessary output words so we go purely on TLA + const flexThrustLimit = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_THRUST_LIMIT_TYPE', 'number') === 3; + const toPower = + this.throttle1Position.get() >= 45 || + (this.throttle1Position.get() >= 35 && flexThrustLimit) || + this.throttle2Position.get() >= 45 || + (this.throttle2Position.get() >= 35 && flexThrustLimit); + this.eng1Or2TakeoffPowerConfirm.write(toPower, deltaTime); + const raAbove1500 = this.radioHeight1.valueOr(0) > 1500 || this.radioHeight2.valueOr(0) > 1500; + this.eng1Or2TakeoffPower.set(toPower || (this.eng1Or2TakeoffPowerConfirm.read() && !raAbove1500)); + + this.engDualFault.set( + !this.aircraftOnGround.get() && + ((this.fireButton1.get() && this.fireButton2.get()) || + (!this.engine1ValueSwitch.get() && !this.engine2ValueSwitch.get()) || + (this.engine1State.get() === 0 && this.engine2State.get() === 0) || + (!this.engine1CoreAtOrAboveMinIdle.get() && !this.engine2CoreAtOrAboveMinIdle.get())), + ); + /* 22 - AUTOFLIGHT */ const fm1DiscreteWord3 = Arinc429Word.fromSimVarValue('L:A32NX_FM1_DISCRETE_WORD_3'); const fm2DiscreteWord3 = Arinc429Word.fromSimVarValue('L:A32NX_FM2_DISCRETE_WORD_3'); @@ -1214,6 +1359,22 @@ export class PseudoFWC { this.toConfigCheckedInPhase2Or3 = true; } + let overspeedWarning = this.adr3OverspeedWarning.write( + this.adr3Cas.isNormalOperation() && adr3MaxCas.isNormalOperation() && this.adr3Cas.value > adr3MaxCas.value + 8, + this.aircraftOnGround.get() || + !(this.adr3Cas.isNormalOperation() && adr3MaxCas.isNormalOperation()) || + this.adr3Cas.value < adr3MaxCas.value + 4, + ); + if ( + !(adr1Discrete1.isNormalOperation() || adr1Discrete1.isFunctionalTest()) || + !(adr2Discrete1.isNormalOperation() || adr2Discrete1.isFunctionalTest()) + ) { + const adr3Discrete1 = Arinc429Word.fromSimVarValue('L:A32NX_ADIRS_ADR_3_DISCRETE_WORD_1'); + overspeedWarning ||= adr3Discrete1.getBitValueOr(9, false); + } + overspeedWarning ||= adr1Discrete1.getBitValueOr(9, false) || adr2Discrete1.getBitValueOr(9, false); + this.overspeedWarning.set(overspeedWarning); + // TO SPEEDS NOT INSERTED const fmToSpeedsNotInserted = fm1DiscreteWord3.getBitValueOr(18, false) && fm2DiscreteWord3.getBitValueOr(18, false); @@ -1266,14 +1427,6 @@ export class PseudoFWC { /** MCDU TO CONF 3 selected */ const mcduToFlapPos3 = fm1DiscreteWord2.getBitValueOr(16, false) || fm2DiscreteWord2.getBitValueOr(16, false); - /* ELECTRICAL */ - - this.dcESSBusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_DC_ESS_BUS_IS_POWERED', 'bool')); - this.dc2BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'bool')); - this.ac1BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_1_BUS_IS_POWERED', 'bool')); - this.ac2BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_2_BUS_IS_POWERED', 'bool')); - this.acESSBusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_ESS_BUS_IS_POWERED', 'bool')); - /* 21 - AIR CONDITIONING AND PRESSURIZATION */ this.acsc1DiscreteWord1.setFromSimVar('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); @@ -1926,6 +2079,20 @@ export class PseudoFWC { !((flightPhase6 && !bothRaInvalid) || flightPhase45) && (this.lgNotDownNoCancel.get() || this.lgNotDown.get()); this.lgLeverRedArrow.set(redArrow); + // 32 - Surveillance Logic + const isNormalLaw = fcdc1DiscreteWord1.getBitValue(11) || fcdc2DiscreteWord1.getBitValue(11); + // we need to check this since the MSFS SDK stall warning does not. + const isCasAbove60 = + this.adr1Cas.get().valueOr(0) > 60 || this.adr2Cas.valueOr(0) > 60 || this.adr3Cas.valueOr(0) > 60; + this.stallWarning.set( + !isNormalLaw && + isCasAbove60 && + this.stallWarningRaw.get() && + flightPhase567 && + this.radioHeight1.valueOr(Infinity) > 1500 && + this.radioHeight2.valueOr(Infinity) > 1500, + ); + /* FIRE */ this.fireButton1.set(SimVar.GetSimVarValue('L:A32NX_FIRE_BUTTON_ENG1', 'bool')); @@ -2324,6 +2491,38 @@ export class PseudoFWC { PseudoFWC.AURAL_SC_PLAY_TIME, ); } + + this.updateRowRopWarnings(); + } + + updateRowRopWarnings() { + const w = Arinc429Word.fromSimVarValue('L:A32NX_ROW_ROP_WORD_1'); + + // ROW + SimVar.SetSimVarValue('L:A32NX_AUDIO_ROW_RWY_TOO_SHORT', 'bool', w.getBitValueOr(15, false)); + + // ROP + // MAX BRAKING, only for manual braking, if maximum pedal braking is not applied + const maxBrakingSet = + SimVar.GetSimVarValue('L:A32NX_LEFT_BRAKE_PEDAL_INPUT', 'number') > 90 || + SimVar.GetSimVarValue('L:A32NX_RIGHT_BRAKE_PEDAL_INPUT', 'number') > 90; + const maxBraking = w.getBitValueOr(13, false) && !maxBrakingSet; + SimVar.SetSimVarValue('L:A32NX_AUDIO_ROP_MAX_BRAKING', 'bool', maxBraking); + + // SET MAX REVERSE, if not already max. reverse set and !MAX_BRAKING + const maxReverseSet = + SimVar.GetSimVarValue('L:XMLVAR_Throttle1Position', 'number') < 0.1 && + SimVar.GetSimVarValue('L:XMLVAR_Throttle2Position', 'number') < 0.1; + const maxReverse = (w.getBitValueOr(12, false) || w.getBitValueOr(13, false)) && !maxReverseSet; + SimVar.SetSimVarValue('L:A32NX_AUDIO_ROW_SET_MAX_REVERSE', 'bool', !maxBraking && maxReverse); + + // At 80kt, KEEP MAX REVERSE once, if max. reversers deployed + const ias = SimVar.GetSimVarValue('AIRSPEED INDICATED', 'knots'); + SimVar.SetSimVarValue( + 'L:A32NX_AUDIO_ROP_KEEP_MAX_REVERSE', + 'bool', + ias <= 80 && ias > 4 && (w.getBitValueOr(12, false) || w.getBitValueOr(13, false)), + ); } ewdMessageFailures: EWDMessageDict = { diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx b/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx index db4206e2d0e..b00d8b5b1c2 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx @@ -2,7 +2,14 @@ // // SPDX-License-Identifier: GPL-3.0 -import { Clock, EventBus, FSComponent, InstrumentBackplane } from '@microsoft/msfs-sdk'; +import { + AdcPublisher, + Clock, + EventBus, + FSComponent, + InstrumentBackplane, + StallWarningPublisher, +} from '@microsoft/msfs-sdk'; import { FuelSystemPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/FuelSystemPublisher'; import { ArincValueProvider } from './shared/ArincValueProvider'; import { EwdComponent } from './EWD'; @@ -24,6 +31,10 @@ class A32NX_EWD extends BaseInstrument { private readonly fuelSystemPublisher = new FuelSystemPublisher(this.bus); + private readonly adcPublisher = new AdcPublisher(this.bus); + + private readonly stallWarningPublisher = new StallWarningPublisher(this.bus, 0.9); + private readonly pseudoFwc = new PseudoFWC(this.bus, this); constructor() { @@ -32,6 +43,8 @@ class A32NX_EWD extends BaseInstrument { this.backplane.addInstrument('Clock', this.clock); this.backplane.addPublisher('SimVars', this.simVarPublisher); this.backplane.addPublisher('FuelSystem', this.fuelSystemPublisher); + this.backplane.addPublisher('adc', this.adcPublisher); + this.backplane.addPublisher('stallWarning', this.stallWarningPublisher); this.backplane.addInstrument('Fwc', this.pseudoFwc); } diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/PFD.tsx b/fbw-a32nx/src/systems/instruments/src/PFD/PFD.tsx index 9d92c3adafd..f90f2cc01db 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/PFD.tsx +++ b/fbw-a32nx/src/systems/instruments/src/PFD/PFD.tsx @@ -12,6 +12,7 @@ import { } from '@flybywiresim/fbw-sdk'; import { A320Failure } from '@failures'; +import { AttitudeIndicatorWarnings } from '@flybywiresim/pfd'; import { DmcLogicEvents } from '../MsfsAvionicsCommon/providers/DmcPublisher'; import { LagFilter } from './PFDUtils'; import { Arinc429Values } from './shared/ArincValueProvider'; @@ -173,6 +174,7 @@ export class PFDComponent extends DisplayComponent { + 72; + + if (showLower) { + this.lowerRef.instance.setAttribute('visibility', 'visible'); + } else { + this.lowerRef.instance.setAttribute('visibility', 'hidden'); + } + } + } + onAfterRender(node: VNode): void { super.onAfterRender(node); @@ -399,6 +428,7 @@ export class AirspeedIndicatorOfftape extends DisplayComponent<{ bus: ArincEvent .handle((g) => { this.leftMainGearCompressed = g; this.onGround = this.rightMainGearCompressed || g; + this.setOutline(); }); sub @@ -407,36 +437,15 @@ export class AirspeedIndicatorOfftape extends DisplayComponent<{ bus: ArincEvent .handle((g) => { this.rightMainGearCompressed = g; this.onGround = this.leftMainGearCompressed || g; + this.setOutline(); }); sub .on('speedAr') .withArinc429Precision(2) .handle((speed) => { - let airspeedValue: number; - if (speed.isFailureWarning() || (speed.isNoComputedData() && !this.onGround)) { - airspeedValue = NaN; - } else if (speed.isNoComputedData()) { - airspeedValue = 30; - } else { - airspeedValue = speed.value; - } - if (Number.isNaN(airspeedValue)) { - this.offTapeRef.instance.classList.add('HiddenElement'); - this.offTapeFailedRef.instance.classList.remove('HiddenElement'); - } else { - this.offTapeRef.instance.classList.remove('HiddenElement'); - this.offTapeFailedRef.instance.classList.add('HiddenElement'); - - const clampedSpeed = Math.max(Math.min(airspeedValue, 660), 30); - const showLower = clampedSpeed > 72; - - if (showLower) { - this.lowerRef.instance.setAttribute('visibility', 'visible'); - } else { - this.lowerRef.instance.setAttribute('visibility', 'hidden'); - } - } + this.airSpeed = speed; + this.setOutline(); }); sub diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/animations.scss b/fbw-a32nx/src/systems/instruments/src/PFD/animations.scss index 1a2f5c3f992..b437cc1eb88 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/animations.scss +++ b/fbw-a32nx/src/systems/instruments/src/PFD/animations.scss @@ -7,10 +7,10 @@ @mixin GenericPulsingStroke($color, $name) { @keyframes #{$name} { - 0% {stroke: scale-color($color, $lightness: -30%);} - 50% {stroke: scale-color($color, $lightness: -30%);} - 51% {stroke: scale-color($color, $lightness: 30%);} - 100% {stroke: scale-color($color, $lightness: 30%);} + 0% {stroke: scale-color($color, $lightness: -50%);} + 50% {stroke: scale-color($color, $lightness: -50%);} + 51% {stroke: scale-color($color, $lightness: 50%);} + 100% {stroke: scale-color($color, $lightness: 50%);} } animation-name: $name; } @@ -75,3 +75,9 @@ animation-duration: 200ms; animation-iteration-count: infinite; } + +.RwyAheadAnimation { + @include GenericPulsingStroke($display-yellow, pulse-yellow-stroke); + animation-duration: 1s; + animation-iteration-count: infinite; +} diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/config.json b/fbw-a32nx/src/systems/instruments/src/PFD/config.json index ade492578f8..5fd4d2dc9f4 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/config.json +++ b/fbw-a32nx/src/systems/instruments/src/PFD/config.json @@ -1,4 +1,8 @@ { "index": "./instrument.tsx", - "isInteractive": false + "isInteractive": false, + "extraDeps": [ + "../MsfsAvionicsCommon", + "fbw-common/src/systems/instruments/src/PFD" + ] } diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/instrument.tsx b/fbw-a32nx/src/systems/instruments/src/PFD/instrument.tsx index 5e18ae5db8c..98a6f2310ab 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/instrument.tsx +++ b/fbw-a32nx/src/systems/instruments/src/PFD/instrument.tsx @@ -2,11 +2,12 @@ // // SPDX-License-Identifier: GPL-3.0 -import { Clock, FSComponent, HEventPublisher, Subject } from '@microsoft/msfs-sdk'; +import { Clock, FSComponent, HEventPublisher, InstrumentBackplane, Subject } from '@microsoft/msfs-sdk'; import { ArincEventBus } from '@flybywiresim/fbw-sdk'; +import { FwcPublisher, RopRowOansPublisher } from '@flybywiresim/msfs-avionics-common'; -import { DmcPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/DmcPublisher'; -import { FmsDataPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/FmsDataPublisher'; +import { FmsDataPublisher } from '../MsfsAvionicsCommon/providers/FmsDataPublisher'; +import { DmcPublisher } from '../MsfsAvionicsCommon/providers/DmcPublisher'; import { getDisplayIndex, PFDComponent } from './PFD'; import { AdirsValueProvider } from '../MsfsAvionicsCommon/AdirsValueProvider'; import { ArincValueProvider } from './shared/ArincValueProvider'; @@ -18,24 +19,32 @@ import './style.scss'; // TODO move this whole thing to InstrumentBackplane and GameStateProvider class A32NX_PFD extends BaseInstrument { - private bus: ArincEventBus; + private readonly bus = new ArincEventBus(); - private simVarPublisher: PFDSimvarPublisher; + private readonly backplane = new InstrumentBackplane(); - private readonly hEventPublisher; + private readonly simVarPublisher = new PFDSimvarPublisher(this.bus); - private readonly arincProvider: ArincValueProvider; + private readonly hEventPublisher = new HEventPublisher(this.bus); - private readonly simplaneValueProvider: SimplaneValueProvider; + private readonly arincProvider = new ArincValueProvider(this.bus); - private readonly clock: Clock; + private readonly simplaneValueProvider = new SimplaneValueProvider(this.bus); + private readonly clock = new Clock(this.bus); + + // FIXME fit this into the normal backplane pattern private adirsValueProvider: AdirsValueProvider; - private readonly dmcPublisher: DmcPublisher; + private readonly dmcPublisher = new DmcPublisher(this.bus); + // FIXME fit this into the normal backplane pattern private fmsDataPublisher: FmsDataPublisher; + private readonly ropRowOansPublisher = new RopRowOansPublisher(this.bus); + + private readonly fwcPublisher = new FwcPublisher(this.bus); + /** * "mainmenu" = 0 * "loading" = 1 @@ -46,13 +55,15 @@ class A32NX_PFD extends BaseInstrument { constructor() { super(); - this.bus = new ArincEventBus(); - this.simVarPublisher = new PFDSimvarPublisher(this.bus); - this.hEventPublisher = new HEventPublisher(this.bus); - this.arincProvider = new ArincValueProvider(this.bus); - this.simplaneValueProvider = new SimplaneValueProvider(this.bus); - this.clock = new Clock(this.bus); - this.dmcPublisher = new DmcPublisher(this.bus); + + this.backplane.addPublisher('PfdSimvars', this.simVarPublisher); + this.backplane.addPublisher('hEvent', this.hEventPublisher); + this.backplane.addInstrument('arincProvider', this.arincProvider); + this.backplane.addInstrument('simPlane', this.simplaneValueProvider); + this.backplane.addInstrument('Clock', this.clock); + this.backplane.addPublisher('Dmc', this.dmcPublisher); + this.backplane.addPublisher('RopRowOans', this.ropRowOansPublisher); + this.backplane.addPublisher('Fwc', this.fwcPublisher); } get templateID(): string { @@ -79,9 +90,7 @@ class A32NX_PFD extends BaseInstrument { const stateSubject = Subject.create<'L' | 'R'>(getDisplayIndex() === 1 ? 'L' : 'R'); this.fmsDataPublisher = new FmsDataPublisher(this.bus, stateSubject); - this.arincProvider.init(); - this.clock.init(); - this.dmcPublisher.startPublish(); + this.backplane.init(); FSComponent.render(, document.getElementById('PFD_CONTENT')); @@ -95,21 +104,16 @@ class A32NX_PFD extends BaseInstrument { public Update(): void { super.Update(); + this.backplane.onUpdate(); + if (this.gameState !== 3) { const gamestate = this.getGameState(); if (gamestate === 3) { - this.simVarPublisher.startPublish(); - this.hEventPublisher.startPublish(); this.adirsValueProvider.start(); - this.dmcPublisher.startPublish(); this.fmsDataPublisher.startPublish(); } this.gameState = gamestate; } else { - this.simVarPublisher.onUpdate(); - this.simplaneValueProvider.onUpdate(); - this.clock.onUpdate(); - this.dmcPublisher.onUpdate(); this.fmsDataPublisher.onUpdate(); } } diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/shared/ArincValueProvider.ts b/fbw-a32nx/src/systems/instruments/src/PFD/shared/ArincValueProvider.ts index d10f241fb84..5fafda83e16 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/shared/ArincValueProvider.ts +++ b/fbw-a32nx/src/systems/instruments/src/PFD/shared/ArincValueProvider.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0 -import { ConsumerSubject, MathUtils, Publisher, Subscription } from '@microsoft/msfs-sdk'; +import { ConsumerSubject, Instrument, MathUtils, Publisher, Subscription } from '@microsoft/msfs-sdk'; import { ArincEventBus, Arinc429Register, Arinc429Word, Arinc429WordData } from '@flybywiresim/fbw-sdk'; import { getDisplayIndex } from 'instruments/src/PFD/PFD'; @@ -55,7 +55,7 @@ export interface Arinc429Values { fmTransAltRaw: number; fmTransLvlRaw: number; } -export class ArincValueProvider { +export class ArincValueProvider implements Instrument { private roll = new Arinc429Word(0); private pitch = Arinc429Register.empty(); @@ -120,6 +120,7 @@ export class ArincValueProvider { constructor(private readonly bus: ArincEventBus) {} + /** @inheritdoc */ public init() { const publisher = this.bus.getPublisher(); const subscriber = this.bus.getSubscriber(); @@ -518,6 +519,11 @@ export class ArincValueProvider { this.fm2Healthy.sub(this.determineFmToUse.bind(this), true); } + /** @inheritdoc */ + public onUpdate(): void { + // noop + } + private determineAndPublishChosenRadioAltitude(publisher: Publisher) { const ownRadioAltitudeHasData = !this.ownRadioAltitude.isFailureWarning() && !this.ownRadioAltitude.isNoComputedData(); diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/shared/SimplaneValueProvider.ts b/fbw-a32nx/src/systems/instruments/src/PFD/shared/SimplaneValueProvider.ts index 076fc005874..5bd89b4827f 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/shared/SimplaneValueProvider.ts +++ b/fbw-a32nx/src/systems/instruments/src/PFD/shared/SimplaneValueProvider.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0 -import { Publisher } from '@microsoft/msfs-sdk'; +import { Instrument, Publisher } from '@microsoft/msfs-sdk'; import { ArincEventBus } from '@flybywiresim/fbw-sdk'; export type SimplaneBaroMode = 'QNH' | 'QFE' | 'STD'; @@ -17,14 +17,20 @@ export interface SimplaneValues { selectedAltitude: number; baroMode: SimplaneBaroMode; } -export class SimplaneValueProvider { +export class SimplaneValueProvider implements Instrument { private publisher: Publisher; constructor(private readonly bus: ArincEventBus) { this.publisher = this.bus.getPublisher(); } - public onUpdate() { + /** @inheritdoc */ + public init(): void { + // noop + } + + /** @inheritdoc */ + public onUpdate(): void { const units = Simplane.getPressureSelectedUnits(); const pressure = Simplane.getPressureValue(units); const isSelected = Simplane.getAutoPilotAirspeedSelected(); diff --git a/fbw-a32nx/src/systems/instruments/src/PFD/tsconfig.json b/fbw-a32nx/src/systems/instruments/src/PFD/tsconfig.json index 2d56720368e..85632976a98 100644 --- a/fbw-a32nx/src/systems/instruments/src/PFD/tsconfig.json +++ b/fbw-a32nx/src/systems/instruments/src/PFD/tsconfig.json @@ -27,7 +27,9 @@ "@shared/*": ["./shared/src/*"], "@tcas/*": ["./tcas/src/*"], "@typings/*": ["../../../fbw-common/src/typings/*"], - "@flybywiresim/fbw-sdk": ["../../../fbw-common/src/systems/index-no-react.ts"] + "@flybywiresim/fbw-sdk": ["../../../fbw-common/src/systems/index-no-react.ts"], + "@flybywiresim/pfd": ["../../../fbw-common/src/systems/instruments/src/PFD/index.ts"], + "@flybywiresim/msfs-avionics-common": ["../../../fbw-common/src/systems/instruments/src/MsfsAvionicsCommon/index.ts"] } } } diff --git a/fbw-a32nx/src/systems/tsconfig.json b/fbw-a32nx/src/systems/tsconfig.json index a97b05c32e6..8da0d744708 100644 --- a/fbw-a32nx/src/systems/tsconfig.json +++ b/fbw-a32nx/src/systems/tsconfig.json @@ -28,7 +28,8 @@ "@flybywiresim/fbw-sdk": ["../../../fbw-common/src/systems/index.ts"], "@flybywiresim/flypad": ["../../../fbw-common/src/systems/instruments/src/EFB/index.ts"], "@flybywiresim/clock": ["../../../fbw-common/src/systems/instruments/src/Clock/index.ts"], - "@flybywiresim/bat": ["../../../fbw-common/src/systems/instruments/src/BAT/index.ts"] + "@flybywiresim/bat": ["../../../fbw-common/src/systems/instruments/src/BAT/index.ts"], + "@flybywiresim/pfd": ["../../../fbw-common/src/systems/instruments/src/PFD/index.ts"] } }, }