diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index c76d696710d..27d3ea96b20 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -73,6 +73,9 @@
1. [FMS] Fix Pause at T/D not working in selected speed - @BlueberryKing (BlueberryKing)
1. [MISC] Replaced brake temperature simulation with physics based model of brakes - @Gurgel100 (Pascal)
1. [A32NX/MCDU] Suppress TMPY FPLN when no modifications made in airways page - @robertxing2004 (robeet)
+1. [A380X/FWS] No "auto brake off" callout when double pressing A/THR instinctive disconnect - @flogross89 (floridude)
+1. [A380X/FWS] Improve landing capability downgrade triple click logic (#9008) - @flogross89 (floridude)
+1. [A32NX/FWS] Add "auto brake off" callout, improve AP/ATHR disconnect aural alert logic - @flogross89 (floridude)
1. [A32NX/FCU] The FCU LCD is now still visible with ambient light when the backlight is off @tracernz (Mike)
1. [A380X/FMC] Fix reset of perf data on done phase or database swap - @tracernz (Mike)
1. [A32NX/ATC] Fixed ATC/TCAS power supply - @tracernz (Mike)
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 1b5aced6220..cd1656bc82e 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
@@ -596,10 +596,6 @@
-
-
-
-
@@ -2150,7 +2146,7 @@
-
+
@@ -2274,54 +2270,55 @@
+
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx
index 0af0ff516e0..fae8018fc37 100644
--- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx
+++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx
@@ -27,6 +27,7 @@ const EWDMessages = {
'000002010': ' \x1b<3mFLAPS FULL',
'000002011': ' \x1b<3mFLAPS\x1b<5m.....CONF 3',
'000002012': ' \x1b<3mFLAPS CONF 3',
+ '320000001': '\x1b<4mAUTO BRK OFF',
'000002201': '\x1b<3mAUTO BRK LO',
'000002202': '\x1b<3mAUTO BRK MED',
'000002203': '\x1b<3mAUTO BRK MAX',
@@ -192,6 +193,11 @@ const EWDMessages = {
'216330503': '\x1b<4m -FWD CAB TRIM VALVE',
'216330504': '\x1b<4m -AFT CAB TRIM VALVE',
'216330505': '\x1b<4m -TRIM AIR HI PR',
+ '220000001': '\x1b<2mAP OFF',
+ '220000002': '\x1b<4mA/THR OFF',
+ '220800001': '\x1b<2m\x1b4mAUTO FLT\x1bm AP OFF',
+ '220800004': '\x1b<4m\x1b4mAUTO FLT\x1bm A/THR OFF',
+ '220800005': '\x1b<5m -THR LEVERS........MOVE',
'220020201': '\x1b<4m\x1b4mAUTO FLT\x1bm FCU 1+2 FAULT',
'220020202': '\x1b<5m -PFD BARO REF: STD ONLY',
'220021001': '\x1b<4m\x1b4mAUTO FLT\x1bm FCU 1 FAULT',
diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx b/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx
index ae54d48dd36..5fcb9206740 100644
--- a/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx
+++ b/fbw-a32nx/src/systems/instruments/src/EWD/instrument.tsx
@@ -2,20 +2,11 @@
//
// SPDX-License-Identifier: GPL-3.0
-import {
- AdcPublisher,
- Clock,
- EventBus,
- FSComponent,
- InstrumentBackplane,
- StallWarningPublisher,
-} from '@microsoft/msfs-sdk';
+import { AdcPublisher, Clock, EventBus, FSComponent, InstrumentBackplane } from '@microsoft/msfs-sdk';
import { FuelSystemPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/FuelSystemPublisher';
import { ArincValueProvider } from './shared/ArincValueProvider';
import { EwdComponent } from './EWD';
import { EwdSimvarPublisher } from './shared/EwdSimvarPublisher';
-import { A32NXFcuBusPublisher } from '@shared/publishers/A32NXFcuBusPublisher';
-import { PseudoFWC } from './PseudoFWC';
import './style.scss';
@@ -34,12 +25,6 @@ class A32NX_EWD extends BaseInstrument {
private readonly adcPublisher = new AdcPublisher(this.bus);
- private readonly stallWarningPublisher = new StallWarningPublisher(this.bus, 0.9);
-
- private readonly a32nxFcuBusPublisher = new A32NXFcuBusPublisher(this.bus);
-
- private readonly pseudoFwc = new PseudoFWC(this.bus, this);
-
constructor() {
super();
@@ -47,10 +32,6 @@ class A32NX_EWD extends BaseInstrument {
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.addPublisher('A32NXFcuBus', this.a32nxFcuBusPublisher);
-
- this.backplane.addInstrument('Fwc', this.pseudoFwc);
}
get templateID(): string {
diff --git a/fbw-a32nx/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx b/fbw-a32nx/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx
new file mode 100644
index 00000000000..2a7c0695682
--- /dev/null
+++ b/fbw-a32nx/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx
@@ -0,0 +1,33 @@
+// Copyright (c) 2021-2023 FlyByWire Simulations
+//
+// SPDX-License-Identifier: GPL-3.0
+
+import { EventBus, SimVarDefinition, SimVarValueType, SimVarPublisher } from '@microsoft/msfs-sdk';
+
+export type PseudoFwcSimvars = {
+ engine1Master: number;
+ engine2Master: number;
+ engine3Master: number;
+ engine4Master: number;
+ fmgc1DiscreteWord3: number;
+ fmgc2DiscreteWord3: number;
+ fmgc1DiscreteWord4: number;
+ fmgc2DiscreteWord4: number;
+};
+
+export class PseudoFwcSimvarPublisher extends SimVarPublisher {
+ private static simvars = new Map([
+ ['engine1Master', { name: 'A:FUELSYSTEM VALVE SWITCH:1', type: SimVarValueType.Bool }],
+ ['engine2Master', { name: 'A:FUELSYSTEM VALVE SWITCH:2', type: SimVarValueType.Bool }],
+ ['engine3Master', { name: 'A:FUELSYSTEM VALVE SWITCH:3', type: SimVarValueType.Bool }],
+ ['engine4Master', { name: 'A:FUELSYSTEM VALVE SWITCH:4', type: SimVarValueType.Bool }],
+ ['fmgc1DiscreteWord3', { name: 'L:A32NX_FMGC_1_DISCRETE_WORD_3', type: SimVarValueType.Number }],
+ ['fmgc2DiscreteWord3', { name: 'L:A32NX_FMGC_2_DISCRETE_WORD_3', type: SimVarValueType.Number }],
+ ['fmgc1DiscreteWord4', { name: 'L:A32NX_FMGC_1_DISCRETE_WORD_4', type: SimVarValueType.Number }],
+ ['fmgc2DiscreteWord4', { name: 'L:A32NX_FMGC_2_DISCRETE_WORD_4', type: SimVarValueType.Number }],
+ ]);
+
+ public constructor(bus: EventBus) {
+ super(PseudoFwcSimvarPublisher.simvars, bus);
+ }
+}
diff --git a/fbw-a32nx/src/systems/systems-host/index.ts b/fbw-a32nx/src/systems/systems-host/index.ts
index 38afa459c5c..33210e739cf 100644
--- a/fbw-a32nx/src/systems/systems-host/index.ts
+++ b/fbw-a32nx/src/systems/systems-host/index.ts
@@ -2,18 +2,43 @@
//
// SPDX-License-Identifier: GPL-3.0
-import { EventBus, HEventPublisher } from '@microsoft/msfs-sdk';
+import {
+ Clock,
+ ClockEvents,
+ EventBus,
+ HEventPublisher,
+ InstrumentBackplane,
+ StallWarningPublisher,
+} from '@microsoft/msfs-sdk';
import { AtsuSystem } from './systems/atsu';
import { PowerSupplyBusses } from './systems/powersupply';
+import { PseudoFWC } from 'systems-host/systems/FWC/PseudoFWC';
+import { FuelSystemPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/FuelSystemPublisher';
+import { A32NXFcuBusPublisher } from '@shared/publishers/A32NXFcuBusPublisher';
+import { PseudoFwcSimvarPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher';
class SystemsHost extends BaseInstrument {
- private readonly bus: EventBus;
+ private readonly bus = new EventBus();
- private readonly hEventPublisher: HEventPublisher;
+ private readonly backplane = new InstrumentBackplane();
- private readonly powerSupply: PowerSupplyBusses;
+ private readonly clock = new Clock(this.bus);
- private readonly atsu: AtsuSystem;
+ private readonly hEventPublisher = new HEventPublisher(this.bus);
+
+ private readonly powerSupply = new PowerSupplyBusses(this.bus);
+
+ private readonly atsu = new AtsuSystem(this.bus);
+
+ private readonly fuelSystemPublisher = new FuelSystemPublisher(this.bus);
+
+ private readonly stallWarningPublisher = new StallWarningPublisher(this.bus, 0.9);
+
+ private readonly a32nxFcuBusPublisher = new A32NXFcuBusPublisher(this.bus);
+
+ private readonly pseudoFwcPublisher = new PseudoFwcSimvarPublisher(this.bus);
+
+ private readonly pseudoFwc = new PseudoFWC(this.bus, this);
/**
* "mainmenu" = 0
@@ -26,10 +51,26 @@ class SystemsHost extends BaseInstrument {
constructor() {
super();
- this.bus = new EventBus();
- this.hEventPublisher = new HEventPublisher(this.bus);
- this.powerSupply = new PowerSupplyBusses(this.bus);
- this.atsu = new AtsuSystem(this.bus);
+ this.backplane.addInstrument('Clock', this.clock);
+ this.backplane.addInstrument('AtsuSystem', this.atsu);
+ this.backplane.addPublisher('FuelSystem', this.fuelSystemPublisher);
+ this.backplane.addPublisher('PowerPublisher', this.powerSupply);
+ this.backplane.addPublisher('stallWarning', this.stallWarningPublisher);
+ this.backplane.addPublisher('a32nxFcuBusPublisher', this.a32nxFcuBusPublisher);
+ this.backplane.addPublisher('PseudoFwcPublisher', this.pseudoFwcPublisher);
+
+ this.pseudoFwc.init();
+ let lastUpdateTime: number;
+ this.bus
+ .getSubscriber()
+ .on('simTimeHiFreq')
+ .atFrequency(50)
+ .handle((now) => {
+ const dt = lastUpdateTime === undefined ? 0 : now - lastUpdateTime;
+ lastUpdateTime = now;
+
+ this.pseudoFwc.update(dt);
+ });
}
get templateID(): string {
@@ -47,9 +88,6 @@ class SystemsHost extends BaseInstrument {
public connectedCallback(): void {
super.connectedCallback();
- this.powerSupply.connectedCallback();
- this.atsu.connectedCallback();
-
// Needed to fetch METARs from the sim
RegisterViewListener(
'JS_LISTENER_FACILITY',
@@ -58,6 +96,8 @@ class SystemsHost extends BaseInstrument {
},
true,
);
+
+ this.backplane.init();
}
public Update(): void {
@@ -67,14 +107,11 @@ class SystemsHost extends BaseInstrument {
const gamestate = this.getGameState();
if (gamestate === 3) {
this.hEventPublisher.startPublish();
- this.powerSupply.startPublish();
- this.atsu.startPublish();
}
this.gameState = gamestate;
}
- this.powerSupply.update();
- this.atsu.update();
+ this.backplane.onUpdate();
}
}
diff --git a/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts b/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts
new file mode 100644
index 00000000000..1010f16b25a
--- /dev/null
+++ b/fbw-a32nx/src/systems/systems-host/systems/FWC/FwsSoundManager.ts
@@ -0,0 +1,428 @@
+// Copyright (c) 2021-2024 FlyByWire Simulations
+//
+// SPDX-License-Identifier: GPL-3.0
+
+import { EventBus, SimVarValueType, Subscribable } from '@microsoft/msfs-sdk';
+
+export interface FwsSoundManagerControlEvents {
+ enqueueSound: string;
+ dequeueSound: string;
+}
+
+// Synthetic voice has priority over everything, SC is least important
+enum FwsAuralWarningType {
+ SingleChime,
+ AuralWarning,
+ SyntheticVoice,
+}
+
+export enum FwsAuralVolume {
+ Full, // 0 dB
+ Attenuated, // -6dB
+ Silent, // -200 dB
+}
+
+interface FwsAural {
+ /** The LocalVar which triggers the playback. Not prefixed by L: here. Either localVarName or wwiseEventName has to be defined. */
+ localVarName?: string;
+ /** The Wwise event which triggers the playback. Either localVarName or wwiseEventName has to be defined. */
+ wwiseEventName?: string;
+ /** Sounds are queued based on type and priority (highest priority = gets queued first within same type) */
+ priority: number;
+ type: FwsAuralWarningType;
+ /** Length of audio in seconds, if non-repetitive */
+ length?: number;
+ /** If this is set, this sound is repeated periodically with the specified pause in seconds */
+ periodicWithPause?: number;
+ continuous?: boolean;
+}
+
+export const FwsAuralsList: Record = {
+ continuousRepetitiveChime: {
+ localVarName: 'A32NX_FWC_CRC',
+ priority: 5,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: true,
+ },
+ singleChime: {
+ localVarName: 'A32NX_FWC_SC',
+ length: 0.54,
+ priority: 0,
+ type: FwsAuralWarningType.SingleChime,
+ continuous: false,
+ },
+ cavalryChargeOnce: {
+ localVarName: 'A32NX_FWC_CAVALRY_CHARGE',
+ length: 0.9,
+ priority: 4,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ cavalryChargeCont: {
+ localVarName: 'A32NX_FWC_CAVALRY_CHARGE',
+ priority: 4,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: true,
+ },
+ tripleClick: {
+ localVarName: 'A32NX_FMA_TRIPLE_CLICK',
+ length: 0.62,
+ priority: 3,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ pause0p8s: {
+ localVarName: 'A32NX_AUDIO_PAUSE',
+ length: 0.8,
+ priority: 4,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ autoBrakeOff: {
+ localVarName: 'A32NX_AUDIO_AUTOBRAKE_OFF',
+ length: 1.5,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ runwayTooShort: {
+ localVarName: 'A32NX_AUDIO_ROW_RWY_TOO_SHORT',
+ length: 1.6,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: true,
+ },
+ keepMaxReverse: {
+ localVarName: 'A32NX_AUDIO_ROP_KEEP_MAX_REVERSE',
+ length: 1.4,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ setMaxReverse: {
+ localVarName: 'A32NX_AUDIO_ROW_SET_MAX_REVERSE',
+ length: 1.62,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ brakeMaxBraking: {
+ localVarName: 'A32NX_AUDIO_ROP_MAX_BRAKING',
+ length: 3.1,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: true,
+ },
+ stall: {
+ localVarName: 'A32NX_AUDIO_ROP_MAX_BRAKING',
+ length: 3.0,
+ priority: 5,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: true,
+ },
+ cChordOnce: {
+ localVarName: 'A32NX_ALT_DEVIATION',
+ length: 1.0,
+ priority: 3,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ cChordCont: {
+ localVarName: 'A32NX_ALT_DEVIATION',
+ priority: 3,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: true,
+ },
+ // Altitude callouts
+ minimums: {
+ wwiseEventName: 'aural_minimumnew',
+ length: 0.67,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ hundred_above: {
+ wwiseEventName: 'aural_100above',
+ length: 0.72,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ retard: {
+ wwiseEventName: 'new_retard',
+ length: 0.9,
+ periodicWithPause: 0.2,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_2500: {
+ wwiseEventName: 'new_2500',
+ length: 1.1,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_2500b: {
+ wwiseEventName: 'new_2_500',
+ length: 1.047,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_2000: {
+ wwiseEventName: 'new_2000',
+ length: 0.72,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_1000: {
+ wwiseEventName: 'new_1000',
+ length: 0.9,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_500: {
+ wwiseEventName: 'new_500',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_400: {
+ wwiseEventName: 'new_400',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_300: {
+ wwiseEventName: 'new_300',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_200: {
+ wwiseEventName: 'new_200',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_100: {
+ wwiseEventName: 'new_100',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_50: {
+ wwiseEventName: 'new_50',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_40: {
+ wwiseEventName: 'new_40',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_30: {
+ wwiseEventName: 'new_30',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_20: {
+ wwiseEventName: 'new_20',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_10: {
+ wwiseEventName: 'new_10',
+ length: 0.3,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_5: {
+ wwiseEventName: 'new_5',
+ length: 0.3,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+};
+
+// FIXME Not all sounds are added to this yet (e.g. CIDS chimes), consider adding them in the future
+// Also, single chimes are not filtered (in RL only once every two seconds)
+export class FwsSoundManager {
+ private readonly soundQueue = new Set();
+
+ private singleChimesPending = 0;
+
+ private currentSoundPlaying: keyof typeof FwsAuralsList | null = null;
+
+ /** in seconds */
+ private currentSoundPlayTimeRemaining = 0;
+
+ constructor(
+ private bus: EventBus,
+ private startupCompleted: Subscribable,
+ ) {
+ // Stop all sounds
+ Object.values(FwsAuralsList).forEach((a) => {
+ if (a.localVarName) {
+ SimVar.SetSimVarValue(`L:${a.localVarName}`, SimVarValueType.Bool, false);
+ }
+ });
+
+ const sub = this.bus.getSubscriber();
+ sub.on('enqueueSound').handle((s) => this.enqueueSound(s));
+ sub.on('dequeueSound').handle((s) => this.dequeueSound(s));
+ }
+
+ /** Add sound to queue. Don't add if already playing */
+ enqueueSound(soundKey: keyof typeof FwsAuralsList) {
+ const sound = FwsAuralsList[soundKey];
+ if (!sound || this.currentSoundPlaying === soundKey) {
+ return;
+ }
+
+ if (sound.type === FwsAuralWarningType.SyntheticVoice || sound.type === FwsAuralWarningType.AuralWarning) {
+ this.soundQueue.add(soundKey);
+ } else if (sound.type === FwsAuralWarningType.SingleChime) {
+ this.singleChimesPending++;
+ }
+ }
+
+ /** Remove sound from queue, e.g. when condition doesn't apply anymore. If sound is currently playing, stops sound immediately */
+ dequeueSound(soundKey: keyof typeof FwsAuralsList) {
+ // Check if this sound is currently playing
+ if (this.currentSoundPlaying === soundKey && FwsAuralsList[this.currentSoundPlaying]?.continuous) {
+ this.stopCurrentSound();
+ }
+ this.soundQueue.delete(soundKey);
+ }
+
+ private stopCurrentSound() {
+ // Only LVar sounds which are continuous can be stopped
+ if (
+ this.currentSoundPlaying &&
+ FwsAuralsList[this.currentSoundPlaying].localVarName &&
+ FwsAuralsList[this.currentSoundPlaying]?.continuous
+ ) {
+ SimVar.SetSimVarValue(`L:${FwsAuralsList[this.currentSoundPlaying].localVarName}`, SimVarValueType.Bool, false);
+ this.currentSoundPlaying = null;
+ this.currentSoundPlayTimeRemaining = 0;
+ }
+ }
+
+ /**
+ * Convenience function for FWS: If condition true and sound not already playing, add to queue. If not, dequeue sound
+ * */
+ handleSoundCondition(soundKey: keyof typeof FwsAuralsList, condition: boolean) {
+ if (condition && this.currentSoundPlaying !== soundKey) {
+ this.enqueueSound(soundKey);
+ } else if (!condition) {
+ this.dequeueSound(soundKey);
+ }
+ }
+
+ /** This only has an effect on sounds defining WwiseRTPC behavior/var for volume */
+ setVolume(volume: FwsAuralVolume) {
+ SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, volume);
+ }
+
+ /** Play now, not to be called from the outside */
+ private playSound(soundKey: keyof typeof FwsAuralsList) {
+ const sound = FwsAuralsList[soundKey];
+ if (!sound) {
+ return;
+ }
+
+ if (sound.localVarName) {
+ SimVar.SetSimVarValue(`L:${sound.localVarName}`, SimVarValueType.Bool, true);
+ } else if (sound.wwiseEventName) {
+ Coherent.call('PLAY_INSTRUMENT_SOUND', sound.wwiseEventName);
+ }
+ this.currentSoundPlaying = soundKey;
+ this.currentSoundPlayTimeRemaining = sound.continuous ? Infinity : sound.length;
+ this.soundQueue.delete(soundKey);
+ }
+ /** Find most important sound from soundQueue and play */
+ private selectAndPlayMostImportantSound(): keyof typeof FwsAuralsList | null {
+ if (!this.startupCompleted.get()) {
+ return;
+ }
+
+ // Logic for scheduling new sounds: Take sound from soundQueue of most important type
+ // (SyntheticVoice > AuralWarning > SingleChime) with highest priority, and play it
+ let selectedSoundKey: keyof typeof FwsAuralsList | null = null;
+ this.soundQueue.forEach((sk) => {
+ const s = FwsAuralsList[sk];
+ if (
+ selectedSoundKey === null ||
+ s.type > FwsAuralsList[selectedSoundKey].type ||
+ (s.type === FwsAuralsList[selectedSoundKey].type && s.priority > FwsAuralsList[selectedSoundKey].priority)
+ ) {
+ selectedSoundKey = sk;
+ }
+ });
+
+ if (selectedSoundKey) {
+ this.playSound(selectedSoundKey);
+ return selectedSoundKey;
+ }
+
+ // See if single chimes are left
+ if (this.singleChimesPending) {
+ this.playSound('singleChime');
+ this.singleChimesPending--;
+ return 'singleChime';
+ }
+
+ // Ok, nothing to play
+ return null;
+ }
+
+ onUpdate(deltaTime: number) {
+ // Either wait for the current sound to finish, or schedule the next sound
+ if (this.currentSoundPlaying && this.currentSoundPlayTimeRemaining > 0) {
+ if (this.currentSoundPlayTimeRemaining - deltaTime / 1_000 > 0) {
+ // Wait for sound to be finished
+ this.currentSoundPlayTimeRemaining -= deltaTime / 1_000;
+ } else {
+ // Sound finishes in this cycle
+ if (FwsAuralsList[this.currentSoundPlaying].localVarName) {
+ SimVar.SetSimVarValue(
+ `L:${FwsAuralsList[this.currentSoundPlaying].localVarName}`,
+ SimVarValueType.Bool,
+ false,
+ );
+ }
+ this.currentSoundPlaying = null;
+ this.currentSoundPlayTimeRemaining = 0;
+ }
+
+ // Interrupt if sound with higher category is present in queue and current sound is continuous
+ let shouldInterrupt = false;
+ let rescheduleSound: keyof typeof FwsAuralsList | null = null;
+ this.soundQueue.forEach((sk) => {
+ const s = FwsAuralsList[sk];
+ if (
+ s &&
+ this.currentSoundPlaying &&
+ FwsAuralsList[this.currentSoundPlaying]?.continuous &&
+ s.type > FwsAuralsList[this.currentSoundPlaying].type
+ ) {
+ shouldInterrupt = true;
+ }
+ });
+
+ if (shouldInterrupt) {
+ if (this.currentSoundPlaying && FwsAuralsList[this.currentSoundPlaying]?.continuous) {
+ rescheduleSound = this.currentSoundPlaying;
+ this.stopCurrentSound();
+ if (rescheduleSound) {
+ this.enqueueSound(rescheduleSound);
+ }
+ }
+ }
+ } else {
+ // Play next sound
+ this.selectAndPlayMostImportantSound();
+ }
+ }
+}
diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts
similarity index 88%
rename from fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts
rename to fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts
index 228279f0ca1..4266ca34ee1 100644
--- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts
+++ b/fbw-a32nx/src/systems/systems-host/systems/FWC/PseudoFWC.ts
@@ -13,6 +13,10 @@ import {
SimVarValueType,
SubscribableMapFunctions,
StallWarningEvents,
+ KeyEventManager,
+ GameStateProvider,
+ Wait,
+ KeyEvents,
} from '@microsoft/msfs-sdk';
import {
@@ -21,17 +25,20 @@ import {
Arinc429RegisterSubject,
Arinc429SignStatusMatrix,
Arinc429Word,
+ Arinc429WordData,
NXDataStore,
NXLogicClockNode,
NXLogicConfirmNode,
NXLogicMemoryNode,
NXLogicPulseNode,
NXLogicTriggeredMonostableNode,
+ UpdateThrottler,
} from '@flybywiresim/fbw-sdk';
import { VerticalMode } from '@shared/autopilot';
-import { EwdSimvars } from 'instruments/src/EWD/shared/EwdSimvarPublisher';
-import { FuelSystemEvents } from '../MsfsAvionicsCommon/providers/FuelSystemPublisher';
+import { FuelSystemEvents } from '../../../instruments/src/MsfsAvionicsCommon/providers/FuelSystemPublisher';
import { A32NXFcuBusEvents } from '../../../shared/src/publishers/A32NXFcuBusPublisher';
+import { FwsSoundManager } from 'systems-host/systems/FWC/FwsSoundManager';
+import { PseudoFwcSimvars } from 'instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher';
export function xor(a: boolean, b: boolean): boolean {
return !!((a ? 1 : 0) ^ (b ? 1 : 0));
@@ -70,10 +77,21 @@ enum FwcAuralWarning {
None,
SingleChime,
Crc,
+ CavalryCharge,
}
export class PseudoFWC {
- private readonly sub = this.bus.getSubscriber();
+ private readonly sub = this.bus.getSubscriber<
+ PseudoFwcSimvars & StallWarningEvents & KeyEvents & A32NXFcuBusEvents
+ >();
+
+ private readonly fwsUpdateThrottler = new UpdateThrottler(125); // has to be > 100 due to pulse nodes
+
+ private keyEventManager: KeyEventManager;
+
+ private readonly startupCompleted = Subject.create(false);
+
+ public readonly soundManager = new FwsSoundManager(this.bus, this.startupCompleted);
/** Time to inhibit master warnings and cautions during startup in ms */
private static readonly FWC_STARTUP_TIME = 5000;
@@ -81,9 +99,6 @@ export class PseudoFWC {
/** Time to inhibit SCs after one is trigger in ms */
private static readonly AURAL_SC_INHIBIT_TIME = 2000;
- /** The time to play the single chime sound in ms */
- private static readonly AURAL_SC_PLAY_TIME = 500;
-
private static readonly EWD_MESSAGE_LINES = 7;
private static readonly ewdMessageSimVarsLeft = Array.from(
@@ -104,11 +119,16 @@ export class PseudoFWC {
Subject.create(''),
);
+ // Input buffering
+ public readonly toConfigInputBuffer = new NXLogicMemoryNode(false);
+ public readonly clearButtonInputBuffer = new NXLogicMemoryNode(false);
+ public readonly recallButtonInputBuffer = new NXLogicMemoryNode(false);
+ public readonly aThrDiscInputBuffer = new NXLogicMemoryNode(false);
+ public readonly apDiscInputBuffer = new NXLogicMemoryNode(false);
+
/* PSEUDO FWC VARIABLES */
private readonly startupTimer = new DebounceTimer();
- private readonly startupCompleted = Subject.create(false);
-
private readonly allCurrentFailures: string[] = [];
private readonly failuresLeft: string[] = [];
@@ -117,6 +137,15 @@ export class PseudoFWC {
private recallFailures: string[] = [];
+ private requestMasterCautionFromFaults = false;
+ private requestMasterCautionFromABrkOff = false;
+ private requestMasterCautionFromAThrOff = false;
+
+ private requestSingleChimeFromAThrOff = false;
+
+ private requestMasterWarningFromFaults = false;
+ private requestMasterWarningFromApOff = false;
+
private auralCrcKeys: string[] = [];
private auralScKeys: string[] = [];
@@ -127,8 +156,6 @@ export class PseudoFWC {
private readonly auralSingleChimeInhibitTimer = new DebounceTimer();
- private readonly auralSingleChimePlayingTimer = new DebounceTimer();
-
private readonly masterWarning = Subject.create(false);
private readonly masterCaution = Subject.create(false);
@@ -285,6 +312,76 @@ export class PseudoFWC {
private toSpeedsNotInsertedWarning = Subject.create(false);
+ /** If multiple AP discs are triggered between FWS cycles, memorize the amount */
+ public autoPilotInstinctiveDiscCountSinceLastFwsCycle = 0;
+
+ public readonly autoPilotDisengagedInstantPulse = new NXLogicPulseNode(false);
+
+ /** 1.8s according to references, but was raised to 1.9s to allow for triple click to finish */
+ public readonly autoPilotInstinctiveDiscPressedInLast1p5Sec = new NXLogicTriggeredMonostableNode(1.5, true);
+
+ /** 1.8s according to references, but was raised to 1.9s to allow for triple click to finish */
+ public readonly autoPilotInstinctiveDiscPressedTwiceInLast1p5Sec = new NXLogicTriggeredMonostableNode(1.5, true);
+
+ public readonly autoPilotInstinctiveDiscPressedPulse = new NXLogicPulseNode(true);
+
+ /** Stay in first warning stage for 1.8s. Raised to 1.9s to allow for triple click to finish */
+ public readonly autoPilotOffVoluntaryEndAfter1p5s = new NXLogicTriggeredMonostableNode(1.5, true);
+
+ public readonly autoPilotOffVoluntaryFirstCavalryChargeActive = new NXLogicTriggeredMonostableNode(0.8, true);
+
+ public readonly autoPilotOffVoluntaryFirstCavalryChargeActivePulse = new NXLogicPulseNode(false);
+
+ public readonly autoPilotOffVoluntaryDiscPulse = new NXLogicPulseNode(true);
+
+ public readonly autoPilotOffVoluntaryMemory = new NXLogicMemoryNode(true);
+
+ public readonly autoPilotOffInvoluntaryMemory = new NXLogicMemoryNode(false);
+
+ public readonly autoPilotOffAlertShown = Subject.create(false);
+
+ public readonly autoPilotOffInvoluntary = Subject.create(false);
+
+ public readonly autoPilotOffAlertShownMemory = new NXLogicMemoryNode(false);
+
+ public readonly autoPilotOffUnacknowledged = new NXLogicMemoryNode(false);
+
+ public readonly autoPilotOffShowMemo = Subject.create(false);
+
+ public readonly fmgc1DiscreteWord3 = Arinc429LocalVarConsumerSubject.create(this.sub.on('fmgc1DiscreteWord3'));
+
+ public readonly fmgc2DiscreteWord3 = Arinc429LocalVarConsumerSubject.create(this.sub.on('fmgc2DiscreteWord3'));
+
+ public readonly fmgc1DiscreteWord4 = Arinc429LocalVarConsumerSubject.create(this.sub.on('fmgc1DiscreteWord4'));
+
+ public readonly fmgc2DiscreteWord4 = Arinc429LocalVarConsumerSubject.create(this.sub.on('fmgc2DiscreteWord4'));
+
+ public readonly fmgcApproachCapability = Subject.create(0);
+
+ public readonly approachCapabilityDowngradeDebounce = new NXLogicTriggeredMonostableNode(0.5, true);
+
+ public readonly approachCapabilityDowngradeSuppress = new NXLogicTriggeredMonostableNode(3, true);
+
+ public readonly approachCapabilityDowngradeDebouncePulse = new NXLogicPulseNode(false);
+
+ public readonly autoThrustDisengagedInstantPulse = new NXLogicPulseNode(false);
+
+ public readonly autoThrustInstinctiveDiscPressed = new NXLogicTriggeredMonostableNode(1.5, true); // Save event for 1.5 sec
+
+ public readonly autoThrustOffVoluntaryMemoNode = new NXLogicTriggeredMonostableNode(9, false); // Emit memo for max. 9 sec
+
+ public readonly autoThrustOffVoluntaryCautionNode = new NXLogicTriggeredMonostableNode(3, false); // Emit master caution for max. 3 sec
+
+ public readonly autoThrustOffInvoluntaryNode = new NXLogicMemoryNode(false);
+
+ public autoThrustInhibitCaution = false; // Inhibit for 10 sec
+
+ public readonly autoThrustOffVoluntary = Subject.create(false);
+
+ public readonly autoThrustOffInvoluntary = Subject.create(false);
+
+ public autoThrustOffVoluntaryMemoInhibited = false;
+
/** TO CONF pressed in phase 2 or 3 SR */
private toConfigCheckedInPhase2Or3 = false;
@@ -678,6 +775,14 @@ export class PseudoFWC {
private onGroundImmediate = false;
+ public readonly autoBrakeDeactivatedNode = new NXLogicTriggeredMonostableNode(9, false); // When ABRK deactivated, emit this for 9 sec
+
+ public readonly autoBrakeOffAuralConfirmNode = new NXLogicConfirmNode(1, true);
+
+ public readonly autoBrakeOff = Subject.create(false);
+
+ public autoBrakeOffAuralTriggered = false;
+
/* NAVIGATION */
private readonly adirsRemainingAlignTime = Subject.create(0);
@@ -798,12 +903,22 @@ export class PseudoFWC {
private readonly throttle2Position = Subject.create(0);
+ public readonly allThrottleIdle = Subject.create(false);
+
private readonly engine1ValueSwitch = ConsumerValue.create(null, false);
private readonly engine2ValueSwitch = ConsumerValue.create(null, false);
private readonly autoThrustStatus = Subject.create(0);
+ private readonly atsDiscreteWord = Arinc429Register.empty();
+
+ private readonly ecu1MaintenanceWord6 = Arinc429Register.empty();
+
+ private readonly ecu2MaintenanceWord6 = Arinc429Register.empty();
+
+ private readonly thrLocked = Subject.create(false);
+
private readonly autothrustLeverWarningFlex = Subject.create(false);
private readonly autothrustLeverWarningToga = Subject.create(false);
@@ -946,10 +1061,34 @@ export class PseudoFWC {
}
init(): void {
+ Promise.all([
+ KeyEventManager.getManager(this.bus),
+ Wait.awaitSubscribable(GameStateProvider.get(), (state) => state === GameState.ingame, true),
+ ]).then(([keyEventManager]) => {
+ this.keyEventManager = keyEventManager;
+ this.registerKeyEvents();
+ });
+
+ this.sub
+ .on('key_intercept')
+ .atFrequency(50)
+ .handle((keyData) => {
+ switch (keyData.key) {
+ case 'A32NX.AUTO_THROTTLE_DISCONNECT':
+ this.autoThrottleInstinctiveDisconnect();
+ break;
+ case 'A32NX.FCU_AP_DISCONNECT_PUSH':
+ case 'A32NX.AUTOPILOT_DISENGAGE':
+ case 'AUTOPILOT_OFF':
+ this.autoPilotInstinctiveDisconnect();
+ break;
+ }
+ });
+
this.toConfigNormal.sub((normal) => SimVar.SetSimVarValue('L:A32NX_TO_CONFIG_NORMAL', 'bool', normal));
this.fwcFlightPhase.sub(() => this.flightPhaseEndedPulseNode.write(true, 0));
- this.auralCrcOutput.sub((crc) => SimVar.SetSimVarValue('L:A32NX_FWC_CRC', 'bool', crc));
+ this.auralCrcOutput.sub((crc) => this.soundManager.handleSoundCondition('continuousRepetitiveChime', crc), true);
this.masterCaution.sub((caution) => {
// Inhibit master warning/cautions until FWC startup has been completed
@@ -1017,6 +1156,14 @@ export class PseudoFWC {
});
}
+ private registerKeyEvents() {
+ this.keyEventManager.interceptKey('A32NX.AUTO_THROTTLE_DISCONNECT', true);
+ this.keyEventManager.interceptKey('A32NX.FCU_AP_DISCONNECT_PUSH', true);
+ this.keyEventManager.interceptKey('A32NX.AUTOPILOT_DISENGAGE', false); // internal event, for FWS only
+ this.keyEventManager.interceptKey('AUTOPILOT_OFF', true);
+ this.keyEventManager.interceptKey('AUTO_THROTTLE_ARM', true);
+ }
+
mapOrder(array, order): [] {
array.sort((a, b) => {
if (order.indexOf(a) > order.indexOf(b)) {
@@ -1094,8 +1241,43 @@ export class PseudoFWC {
/**
* Periodic update
*/
- onUpdate() {
- const deltaTime = this.instrument.deltaTime;
+ update(_deltaTime: number) {
+ const deltaTime = this.fwsUpdateThrottler.canUpdate(_deltaTime);
+
+ // Acquire discrete inputs at a higher frequency, buffer them until the next FWS cycle.
+ // T.O CONFIG button
+ if (SimVar.GetSimVarValue('L:A32NX_BTN_TOCONFIG', 'bool')) {
+ this.toConfigInputBuffer.write(true, false);
+ }
+
+ // CLR buttons
+ const clearButtonLeft = SimVar.GetSimVarValue('L:A32NX_BTN_CLR', 'bool');
+ const clearButtonRight = SimVar.GetSimVarValue('L:A32NX_BTN_CLR2', 'bool');
+ if (clearButtonLeft || clearButtonRight) {
+ this.clearButtonInputBuffer.write(true, false);
+ }
+
+ // RCL button
+ const recallButton = SimVar.GetSimVarValue('L:A32NX_BTN_RCL', 'bool');
+ if (recallButton) {
+ this.recallButtonInputBuffer.write(true, false);
+ }
+
+ // Enforce cycle time for the logic computation (otherwise pulse nodes would be broken)
+ if (deltaTime === -1 || _deltaTime === 0) {
+ return;
+ }
+
+ // Play sounds
+ this.soundManager.onUpdate(deltaTime);
+
+ // Write pulse nodes for buffered inputs
+ this.toConfigPulseNode.write(this.toConfigInputBuffer.read(), deltaTime);
+ this.clr1PulseNode.write(this.clearButtonInputBuffer.read(), deltaTime);
+ this.clr2PulseNode.write(this.clearButtonInputBuffer.read(), deltaTime);
+ this.rclUpPulseNode.write(this.recallButtonInputBuffer.read(), deltaTime);
+ this.autoThrustInstinctiveDiscPressed.write(this.aThrDiscInputBuffer.read(), deltaTime);
+ this.autoPilotInstinctiveDiscPressedPulse.write(this.apDiscInputBuffer.read(), deltaTime);
// Inputs update
this.flightPhaseEndedPulseNode.write(false, deltaTime);
@@ -1111,10 +1293,11 @@ export class PseudoFWC {
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());
+ const flightPhase167 = [1, 6, 7].includes(this.fwcFlightPhase.get());
// TO CONFIG button
this.toConfigTestRaw = SimVar.GetSimVarValue('L:A32NX_BTN_TOCONFIG', 'bool') > 0;
- this.toConfigPulseNode.write(this.toConfigTestRaw, deltaTime);
+ this.toConfigPulseNode.write(this.toConfigTestRaw, _deltaTime);
const toConfigTest = this.toConfigTriggerNode.write(this.toConfigPulseNode.read(), deltaTime);
if (toConfigTest !== this.toConfigTest) {
// temporary var for the old FWC stuff
@@ -1126,17 +1309,11 @@ export class PseudoFWC {
);
// CLR buttons
- const clearButtonLeft = SimVar.GetSimVarValue('L:A32NX_BTN_CLR', 'bool');
- const clearButtonRight = SimVar.GetSimVarValue('L:A32NX_BTN_CLR2', 'bool');
- this.clr1PulseNode.write(clearButtonLeft, deltaTime);
- this.clr2PulseNode.write(clearButtonRight, deltaTime);
const previousClrPulseUpTrigger = this.clrPulseUpTrigger.read();
this.clrPulseUpTrigger.write(this.clr1PulseNode.read() || this.clr2PulseNode.read(), deltaTime);
this.clrTriggerRisingEdge = !previousClrPulseUpTrigger && this.clrPulseUpTrigger.read();
// RCL button
- const recallButton = SimVar.GetSimVarValue('L:A32NX_BTN_RCL', 'bool');
- this.rclUpPulseNode.write(recallButton, deltaTime);
const previousRclUpTriggerNode = this.rclUpTriggerNode.read();
this.rclUpTriggerNode.write(recallButton, deltaTime);
@@ -1208,8 +1385,17 @@ export class PseudoFWC {
this.throttle1Position.set(SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:1', 'number'));
this.throttle2Position.set(SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:2', 'number'));
this.autoThrustStatus.set(SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_STATUS', 'enum'));
+ this.atsDiscreteWord.setFromSimVar('L:A32NX_FCU_ATS_DISCRETE_WORD');
+ this.ecu1MaintenanceWord6.setFromSimVar('L:A32NX_ECU_1_MAINTENANCE_WORD_6');
+ this.ecu2MaintenanceWord6.setFromSimVar('L:A32NX_ECU_2_MAINTENANCE_WORD_6');
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.allThrottleIdle.set(this.throttle1Position.get() < 1 && this.throttle2Position.get() < 1);
+
+ const masterCautionButtonLeft = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERCAUT_L', 'bool');
+ const masterCautionButtonRight = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERCAUT_R', 'bool');
+ const masterWarningButtonLeft = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_L', 'bool');
+ const masterWarningButtonRight = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_R', 'bool');
/* HYDRAULICS acquisition */
@@ -1341,6 +1527,201 @@ export class PseudoFWC {
(onGroundCount > 1 && raInvalid);
this.aircraftOnGround.set(this.onGroundConf.write(this.onGroundImmediate, deltaTime));
+ // AP OFF
+ const apEngaged: boolean =
+ SimVar.GetSimVarValue('L:A32NX_FMGC_1_AP_ENGAGED', SimVarValueType.Bool) ||
+ SimVar.GetSimVarValue('L:A32NX_FMGC_2_AP_ENGAGED', SimVarValueType.Bool);
+ this.autoPilotDisengagedInstantPulse.write(apEngaged, deltaTime);
+
+ const apDiscPressedInLast1p8SecBeforeThisCycle = this.autoPilotInstinctiveDiscPressedInLast1p5Sec.read();
+ this.autoPilotInstinctiveDiscPressedInLast1p5Sec.write(this.autoPilotInstinctiveDiscPressedPulse.read(), deltaTime);
+
+ const voluntaryApDisc =
+ this.autoPilotDisengagedInstantPulse.read() && this.autoPilotInstinctiveDiscPressedInLast1p5Sec.read();
+ this.autoPilotOffVoluntaryEndAfter1p5s.write(voluntaryApDisc, deltaTime);
+ this.autoPilotOffVoluntaryDiscPulse.write(voluntaryApDisc, deltaTime);
+
+ this.autoPilotOffVoluntaryFirstCavalryChargeActive.write(this.autoPilotOffVoluntaryDiscPulse.read(), deltaTime);
+ this.autoPilotOffVoluntaryFirstCavalryChargeActivePulse.write(
+ this.autoPilotOffVoluntaryFirstCavalryChargeActive.read(),
+ deltaTime,
+ );
+
+ this.autoPilotInstinctiveDiscPressedTwiceInLast1p5Sec.write(
+ this.autoPilotInstinctiveDiscPressedPulse.read() &&
+ (this.autoPilotInstinctiveDiscCountSinceLastFwsCycle > 1 || apDiscPressedInLast1p8SecBeforeThisCycle),
+ deltaTime,
+ );
+
+ this.autoPilotOffVoluntaryMemory.write(
+ this.autoPilotOffVoluntaryDiscPulse.read(),
+ apEngaged ||
+ this.autoPilotInstinctiveDiscPressedTwiceInLast1p5Sec.read() ||
+ !this.autoPilotOffVoluntaryEndAfter1p5s.read(),
+ );
+
+ const discPbPressedAfterDisconnection =
+ !this.autoPilotDisengagedInstantPulse.read() &&
+ (this.autoPilotInstinctiveDiscPressedPulse.read() || masterWarningButtonLeft || masterWarningButtonRight);
+
+ this.autoPilotOffUnacknowledged.write(
+ this.autoPilotDisengagedInstantPulse.read(),
+ apEngaged || this.autoPilotInstinctiveDiscPressedTwiceInLast1p5Sec.read() || discPbPressedAfterDisconnection,
+ );
+
+ this.autoPilotOffInvoluntaryMemory.write(
+ !apEngaged && !this.autoPilotOffVoluntaryMemory.read() && this.autoPilotOffUnacknowledged.read(),
+ !this.autoPilotOffUnacknowledged.read(),
+ );
+
+ this.autoPilotOffAlertShownMemory.write(
+ this.autoPilotOffInvoluntaryMemory.read(),
+ this.fwcFlightPhase.get() === 1 || apEngaged,
+ );
+ this.autoPilotOffAlertShown.set(this.autoPilotOffAlertShownMemory.read());
+ this.autoPilotOffInvoluntary.set(this.autoPilotOffInvoluntaryMemory.read());
+ this.autoPilotOffShowMemo.set(this.autoPilotOffVoluntaryMemory.read() || this.autoPilotOffInvoluntaryMemory.read());
+
+ if (this.autoPilotDisengagedInstantPulse.read()) {
+ // Request quiet CRC one time
+ this.requestMasterWarningFromApOff = true;
+ this.soundManager.enqueueSound('cavalryChargeOnce'); // On the A320, play first cav charge completely no matter what
+ }
+ if (this.autoPilotOffVoluntaryFirstCavalryChargeActivePulse.read()) {
+ this.soundManager.dequeueSound('cavalryChargeOnce');
+ }
+ if (!this.autoPilotOffVoluntaryMemory.read() && !this.autoPilotOffInvoluntaryMemory.read()) {
+ this.requestMasterWarningFromApOff = false;
+ this.soundManager.dequeueSound('cavalryChargeOnce');
+ this.soundManager.dequeueSound('cavalryChargeCont');
+ }
+
+ this.autoPilotInstinctiveDiscPressedPulse.write(false, deltaTime);
+
+ // approach capability downgrade. Debounce first, then suppress for a certain amount of time
+ // (to avoid multiple triple clicks, and a delay which is too long)
+ let fmgcApproachCapability = 0;
+ const getApproachCapability = (dw3: Arinc429WordData, dw4: Arinc429WordData): number => {
+ let appCap = 0;
+ const landModeActive = dw4.bitValueOr(14, false);
+ const landModeArmed = dw3.bitValueOr(20, false);
+ const land2Capacity = dw4.bitValueOr(23, false);
+ const land3FailPassiveCapacity = dw4.bitValueOr(24, false);
+ const land3FailOperationalCapacity = dw4.bitValueOr(25, false);
+
+ if (land2Capacity) {
+ appCap = 2;
+ } else if (land3FailPassiveCapacity) {
+ appCap = 3;
+ } else if (land3FailOperationalCapacity) {
+ appCap = 4;
+ } else if (landModeActive || landModeArmed) {
+ appCap = 1;
+ } else {
+ appCap = 0;
+ }
+ return appCap;
+ };
+
+ if (this.fmgc1DiscreteWord3.get().isNormalOperation() && this.fmgc1DiscreteWord4.get().isNormalOperation()) {
+ fmgcApproachCapability = getApproachCapability(this.fmgc1DiscreteWord3.get(), this.fmgc1DiscreteWord4.get());
+ } else if (this.fmgc2DiscreteWord3.get().isNormalOperation() && this.fmgc2DiscreteWord4.get().isNormalOperation()) {
+ fmgcApproachCapability = getApproachCapability(this.fmgc2DiscreteWord3.get(), this.fmgc2DiscreteWord4.get());
+ }
+
+ const capabilityDowngrade =
+ fmgcApproachCapability < this.fmgcApproachCapability.get() && fmgcApproachCapability > 0;
+ this.approachCapabilityDowngradeDebounce.write(
+ capabilityDowngrade && flightPhase167 && !this.approachCapabilityDowngradeSuppress.read(),
+ deltaTime,
+ );
+ this.approachCapabilityDowngradeDebouncePulse.write(this.approachCapabilityDowngradeDebounce.read(), deltaTime);
+ this.approachCapabilityDowngradeSuppress.write(this.approachCapabilityDowngradeDebouncePulse.read(), deltaTime);
+ // Capability downgrade after debounce --> triple click
+ if (this.approachCapabilityDowngradeDebouncePulse.read()) {
+ this.soundManager.enqueueSound('pause0p8s');
+ this.soundManager.enqueueSound('tripleClick');
+ }
+ this.fmgcApproachCapability.set(fmgcApproachCapability);
+
+ // A/THR OFF
+ const aThrEngaged = this.atsDiscreteWord.bitValueOr(13, false);
+ this.autoThrustDisengagedInstantPulse.write(aThrEngaged, deltaTime);
+ this.autoThrustInstinctiveDiscPressed.write(false, deltaTime);
+
+ const below50ft = this.radioHeight1.valueOr(2500) < 50 && this.radioHeight2.valueOr(2500) < 50;
+
+ if (below50ft && this.allThrottleIdle.get()) {
+ this.autoThrustInhibitCaution = true;
+ }
+
+ const voluntaryAThrDisc =
+ !this.aircraftOnGround.get() &&
+ this.autoThrustDisengagedInstantPulse.read() &&
+ (this.autoThrustInstinctiveDiscPressed.read() || this.allThrottleIdle.get()) &&
+ !this.autoThrustInhibitCaution;
+
+ // Voluntary A/THR disconnect
+ this.autoThrustOffVoluntaryMemoNode.write(voluntaryAThrDisc && !aThrEngaged, deltaTime);
+ this.autoThrustOffVoluntaryCautionNode.write(voluntaryAThrDisc && !aThrEngaged, deltaTime);
+ this.thrLocked.set(
+ this.ecu1MaintenanceWord6.bitValueOr(12, false) || this.ecu2MaintenanceWord6.bitValueOr(12, false),
+ );
+
+ if (!this.autoThrustOffVoluntaryMemoNode.read()) {
+ this.autoThrustInhibitCaution = false;
+ }
+
+ if (
+ this.autoThrustOffVoluntaryCautionNode.read() &&
+ !this.autoThrustOffVoluntary.get() &&
+ !this.autoThrustInhibitCaution
+ ) {
+ // First triggered in this cycle, request master caution
+ this.requestMasterCautionFromAThrOff = true;
+ this.requestSingleChimeFromAThrOff = true;
+ } else if (!this.autoThrustOffVoluntaryCautionNode.read() || this.autoThrustInhibitCaution) {
+ this.requestMasterCautionFromAThrOff = false;
+ this.requestSingleChimeFromAThrOff = false;
+ }
+ this.autoThrustOffVoluntary.set(
+ this.autoThrustOffVoluntaryMemoNode.read() && !this.autoThrustInhibitCaution && !aThrEngaged,
+ );
+
+ // Involuntary A/THR disconnect
+ const involuntaryAThrDisc =
+ !this.aircraftOnGround.get() &&
+ this.autoThrustDisengagedInstantPulse.read() &&
+ !(this.autoThrustInstinctiveDiscPressed.read() || (below50ft && this.allThrottleIdle.get()));
+
+ this.autoThrustOffInvoluntaryNode.write(involuntaryAThrDisc, aThrEngaged || voluntaryAThrDisc);
+ this.autoThrustOffInvoluntary.set(this.autoThrustOffInvoluntaryNode.read());
+
+ // AUTO BRAKE OFF
+ this.autoBrakeDeactivatedNode.write(!!SimVar.GetSimVarValue('L:A32NX_AUTOBRAKES_ACTIVE', 'boolean'), deltaTime);
+
+ if (!this.autoBrakeDeactivatedNode.read()) {
+ this.requestMasterCautionFromABrkOff = false;
+ this.autoBrakeOffAuralTriggered = false;
+ }
+
+ this.autoBrakeOffAuralConfirmNode.write(this.autoBrakeDeactivatedNode.read(), deltaTime);
+
+ const autoBrakeOffShouldTrigger =
+ this.aircraftOnGround.get() && this.computedAirSpeedToNearest2.get() > 33 && this.autoBrakeDeactivatedNode.read();
+
+ if (autoBrakeOffShouldTrigger && !this.autoBrakeOff.get()) {
+ // Triggered in this cycle -> request master caution
+ this.requestMasterCautionFromABrkOff = true;
+ }
+
+ // FIXME double callout if ABRK fails
+ this.autoBrakeOff.set(autoBrakeOffShouldTrigger);
+ if (autoBrakeOffShouldTrigger && this.autoBrakeOffAuralConfirmNode.read() && !this.autoBrakeOffAuralTriggered) {
+ this.soundManager.enqueueSound('autoBrakeOff');
+ this.autoBrakeOffAuralTriggered = true;
+ }
+
// 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
@@ -2178,18 +2559,17 @@ export class PseudoFWC {
}
/* MASTER CAUT/WARN BUTTONS */
-
- const masterCautionButtonLeft = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERCAUT_L', 'bool');
- const masterCautionButtonRight = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERCAUT_R', 'bool');
- const masterWarningButtonLeft = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_L', 'bool');
- const masterWarningButtonRight = SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_R', 'bool');
if (masterCautionButtonLeft || masterCautionButtonRight) {
- this.masterCaution.set(false);
this.auralSingleChimePending = false;
+ this.requestMasterCautionFromFaults = false;
+ this.requestMasterCautionFromABrkOff = false;
+ this.requestMasterCautionFromAThrOff = false;
+ this.autoThrustInhibitCaution = true;
}
if ((masterWarningButtonLeft || masterWarningButtonRight) && this.nonCancellableWarningCount === 0) {
- this.masterWarning.set(false);
- this.auralCrcActive.set(false);
+ this.requestMasterWarningFromFaults = this.nonCancellableWarningCount > 0;
+ this.requestMasterWarningFromApOff = false;
+ this.auralCrcActive.set(this.nonCancellableWarningCount > 0);
}
/* T.O. CONFIG CHECK */
@@ -2309,10 +2689,10 @@ export class PseudoFWC {
}
if (value.failure === 3) {
- this.masterWarning.set(true);
+ this.requestMasterWarningFromFaults = true;
}
if (value.failure === 2) {
- this.masterCaution.set(true);
+ this.requestMasterCautionFromFaults = true;
}
}
@@ -2378,6 +2758,10 @@ export class PseudoFWC {
}
auralScKeys.push(key);
}
+
+ if (value.auralWarning?.get() === FwcAuralWarning.CavalryCharge) {
+ this.soundManager.enqueueSound('cavalryChargeCont');
+ }
}
this.auralCrcKeys = auralCrcKeys;
@@ -2465,13 +2849,21 @@ export class PseudoFWC {
this.ewdMessageLinesLeft.forEach((l, i) => l.set(orderedMemoArrayLeft[i]));
if (orderedFailureArrayRight.length === 0) {
- this.masterCaution.set(false);
+ this.requestMasterCautionFromFaults = false;
if (this.nonCancellableWarningCount === 0) {
- this.masterWarning.set(false);
+ this.requestMasterWarningFromFaults = false;
}
}
}
+ this.masterCaution.set(
+ this.requestMasterCautionFromFaults ||
+ this.requestMasterCautionFromABrkOff ||
+ this.requestMasterCautionFromAThrOff,
+ );
+
+ this.masterWarning.set(this.requestMasterWarningFromFaults || this.requestMasterWarningFromApOff);
+
if (leftFailureSystemCount + rightFailureSystemCount === 0) {
SimVar.SetSimVarValue('L:A32NX_ECAM_SFAIL', 'number', -1);
}
@@ -2491,30 +2883,35 @@ export class PseudoFWC {
this.ewdMessageLinesRight.forEach((l, i) => l.set(orderedMemoArrayRight[i]));
- // This does not consider interrupting c-chord, priority of synthetic voice etc.
- // We shall wait for the rust FWC for those nice things!
- if (this.auralSingleChimePending && !this.auralCrcActive.get() && !this.auralSingleChimeInhibitTimer.isPending()) {
+ const chimeRequested =
+ (this.auralSingleChimePending || this.requestSingleChimeFromAThrOff) && !this.auralCrcActive.get();
+ if (chimeRequested && !this.auralSingleChimeInhibitTimer.isPending()) {
this.auralSingleChimePending = false;
- SimVar.SetSimVarValue('L:A32NX_FWC_SC', 'bool', true);
+ this.requestSingleChimeFromAThrOff = false;
+ this.soundManager.enqueueSound('singleChime');
// there can only be one SC per 2 seconds, non-cumulative, so clear any pending ones at the end of that inhibit period
this.auralSingleChimeInhibitTimer.schedule(
() => (this.auralSingleChimePending = false),
PseudoFWC.AURAL_SC_INHIBIT_TIME,
);
- this.auralSingleChimePlayingTimer.schedule(
- () => SimVar.SetSimVarValue('L:A32NX_FWC_SC', 'bool', false),
- PseudoFWC.AURAL_SC_PLAY_TIME,
- );
}
this.updateRowRopWarnings();
+
+ // Reset all buffered inputs
+ this.toConfigInputBuffer.write(false, true);
+ this.clearButtonInputBuffer.write(false, true);
+ this.recallButtonInputBuffer.write(false, true);
+ this.aThrDiscInputBuffer.write(false, true);
+ this.apDiscInputBuffer.write(false, true);
+ this.autoPilotInstinctiveDiscCountSinceLastFwsCycle = 0;
}
updateRowRopWarnings() {
const w = Arinc429Word.fromSimVarValue('L:A32NX_ROW_ROP_WORD_1');
// ROW
- SimVar.SetSimVarValue('L:A32NX_AUDIO_ROW_RWY_TOO_SHORT', 'bool', w.bitValueOr(15, false));
+ this.soundManager.handleSoundCondition('runwayTooShort', w.bitValueOr(15, false));
// ROP
// MAX BRAKING, only for manual braking, if maximum pedal braking is not applied
@@ -2522,25 +2919,71 @@ export class PseudoFWC {
SimVar.GetSimVarValue('L:A32NX_LEFT_BRAKE_PEDAL_INPUT', 'number') > 90 ||
SimVar.GetSimVarValue('L:A32NX_RIGHT_BRAKE_PEDAL_INPUT', 'number') > 90;
const maxBraking = w.bitValueOr(13, false) && !maxBrakingSet;
- SimVar.SetSimVarValue('L:A32NX_AUDIO_ROP_MAX_BRAKING', 'bool', maxBraking);
+ this.soundManager.handleSoundCondition('brakeMaxBraking', 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.bitValueOr(12, false) || w.bitValueOr(13, false)) && !maxReverseSet;
- SimVar.SetSimVarValue('L:A32NX_AUDIO_ROW_SET_MAX_REVERSE', 'bool', !maxBraking && maxReverse);
+ this.soundManager.handleSoundCondition('setMaxReverse', !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',
+ this.soundManager.handleSoundCondition(
+ 'keepMaxReverse',
ias <= 80 && ias > 4 && (w.bitValueOr(12, false) || w.bitValueOr(13, false)),
);
}
+ autoThrottleInstinctiveDisconnect() {
+ // When instinctive A/THR disc. p/b is pressed after ABRK deactivation, inhibit audio+memo, don't request master caution
+ // Unclear refs, whether this has to happen within the audio confirm node time (1s)
+ if (this.autoBrakeDeactivatedNode.read()) {
+ this.requestMasterCautionFromABrkOff = false;
+ }
+
+ this.aThrDiscInputBuffer.write(true, false);
+
+ if (this.autoThrustOffVoluntary.get()) {
+ // Pressed a second time -> silence
+ this.autoThrustInhibitCaution = true;
+ this.requestMasterCautionFromAThrOff = false;
+ }
+ }
+
+ autoPilotInstinctiveDisconnect() {
+ this.apDiscInputBuffer.write(true, false);
+ if (this.apDiscInputBuffer.read()) {
+ this.autoPilotInstinctiveDiscCountSinceLastFwsCycle++;
+ }
+ }
+
ewdMessageFailures: EWDMessageDict = {
+ // 22 - FLIGHT GUIDANCE
+ 220800001: {
+ // AP OFF involuntary
+ flightPhaseInhib: [],
+ simVarIsActive: this.autoPilotOffAlertShown,
+ auralWarning: this.autoPilotOffInvoluntary.map((a) => (a ? FwcAuralWarning.CavalryCharge : FwcAuralWarning.None)),
+ whichCodeToReturn: () => [0],
+ codesToReturn: ['220800001'],
+ memoInhibit: () => false,
+ failure: 3,
+ sysPage: -1,
+ side: 'LEFT',
+ },
+ 220800004: {
+ // A/THR OFF involuntary
+ flightPhaseInhib: [3, 4, 8],
+ simVarIsActive: this.autoThrustOffInvoluntary,
+ whichCodeToReturn: () => [0, this.thrLocked.get() ? 1 : null],
+ codesToReturn: ['220800004', '220800005'],
+ memoInhibit: () => false,
+ failure: 2,
+ sysPage: -1,
+ side: 'LEFT',
+ },
// 22 - AUTOFLIGHT
2200202: {
// FCU 1+2 FAULT
@@ -4332,6 +4775,18 @@ export class PseudoFWC {
sysPage: -1,
side: 'RIGHT',
},
+ // 32 LANDING GEAR
+ 320000001: {
+ // AUTO BRK OFF
+ flightPhaseInhib: [1, 2, 3, 4, 5, 6, 7, 10],
+ simVarIsActive: this.autoBrakeOff,
+ whichCodeToReturn: () => [0],
+ codesToReturn: ['320000001'],
+ memoInhibit: () => false,
+ failure: 0,
+ sysPage: -1,
+ side: 'RIGHT',
+ },
'0000040': {
// NW STRG DISC
flightPhaseInhib: [],
@@ -4630,5 +5085,28 @@ export class PseudoFWC {
sysPage: -1,
side: 'RIGHT',
},
+ // 22 - Flight guidance
+ 220000001: {
+ // A/THR OFF
+ flightPhaseInhib: [],
+ simVarIsActive: this.autoPilotOffShowMemo,
+ whichCodeToReturn: () => [0],
+ codesToReturn: ['220000001'],
+ memoInhibit: () => false,
+ failure: 0,
+ sysPage: -1,
+ side: 'RIGHT',
+ },
+ 220000002: {
+ // A/THR OFF
+ flightPhaseInhib: [],
+ simVarIsActive: this.autoThrustOffVoluntary,
+ whichCodeToReturn: () => [0],
+ codesToReturn: ['220000002'],
+ memoInhibit: () => false,
+ failure: 0,
+ sysPage: -1,
+ side: 'RIGHT',
+ },
};
}
diff --git a/fbw-a32nx/src/systems/systems-host/systems/atsu.ts b/fbw-a32nx/src/systems/systems-host/systems/atsu.ts
index 256c359a796..f129b08fe98 100644
--- a/fbw-a32nx/src/systems/systems-host/systems/atsu.ts
+++ b/fbw-a32nx/src/systems/systems-host/systems/atsu.ts
@@ -6,10 +6,10 @@ import { Atc } from '@datalink/atc';
import { Aoc } from '@datalink/aoc';
import { SimVarHandling } from '@datalink/common';
import { Router } from '@datalink/router';
-import { EventBus, EventSubscriber } from '@microsoft/msfs-sdk';
+import { EventBus, EventSubscriber, Instrument } from '@microsoft/msfs-sdk';
import { PowerSupplyBusTypes } from 'systems-host/systems/powersupply';
-export class AtsuSystem {
+export class AtsuSystem implements Instrument {
private readonly simVarHandling: SimVarHandling;
private readonly powerSupply: EventSubscriber;
@@ -40,18 +40,15 @@ export class AtsuSystem {
});
}
- public connectedCallback(): void {
+ public init(): void {
this.simVarHandling.initialize();
+ this.simVarHandling.startPublish();
this.router.initialize();
this.atc.initialize();
this.aoc.initialize();
}
- public startPublish(): void {
- this.simVarHandling.startPublish();
- }
-
- public update(): void {
+ public onUpdate(): void {
this.simVarHandling.update();
this.router.update();
}
diff --git a/fbw-a32nx/src/systems/systems-host/systems/powersupply.ts b/fbw-a32nx/src/systems/systems-host/systems/powersupply.ts
index 0fc4ca65137..796f174d292 100644
--- a/fbw-a32nx/src/systems/systems-host/systems/powersupply.ts
+++ b/fbw-a32nx/src/systems/systems-host/systems/powersupply.ts
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0
import {
+ BackplanePublisher,
EventBus,
EventSubscriber,
Publisher,
@@ -53,7 +54,7 @@ export interface PowerSupplyBusTypes {
dcBusEss: boolean;
}
-export class PowerSupplyBusses {
+export class PowerSupplyBusses implements BackplanePublisher {
private simVarPublisher: PowerSupplySimvarPublisher = null;
private subscriber: EventSubscriber = null;
@@ -94,22 +95,12 @@ export class PowerSupplyBusses {
.handle((powered: number) => this.publisher.pub('dcBusEss', powered !== 0, false, false));
}
- public connectedCallback(): void {
- this.initialize();
-
- this.simVarPublisher.subscribe('msfsAcBus1');
- this.simVarPublisher.subscribe('msfsAcBus2');
- this.simVarPublisher.subscribe('msfsAcBusEss');
- this.simVarPublisher.subscribe('msfsDcBus1');
- this.simVarPublisher.subscribe('msfsDcBus2');
- this.simVarPublisher.subscribe('msfsDcBusEss');
- }
-
public startPublish(): void {
+ this.initialize();
this.simVarPublisher.startPublish();
}
- public update(): void {
+ public onUpdate(): void {
this.simVarPublisher.onUpdate();
}
}
diff --git a/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.cpp b/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.cpp
index 68c39bf1722..6789785aa13 100644
--- a/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.cpp
+++ b/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.cpp
@@ -291,6 +291,7 @@ bool SimConnectInterface::prepareSimInputSimConnectDataDefinitions() {
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTOPILOT_DISENGAGE_SET, "AUTOPILOT_DISENGAGE_SET", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTOPILOT_DISENGAGE_TOGGLE, "AUTOPILOT_DISENGAGE_TOGGLE", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::TOGGLE_FLIGHT_DIRECTOR, "TOGGLE_FLIGHT_DIRECTOR", true);
+ result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_AUTOPILOT_DISENGAGE, "A32NX.AUTOPILOT_DISENGAGE", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_FCU_AP_1_PUSH, "A32NX.FCU_AP_1_PUSH", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_FCU_AP_2_PUSH, "A32NX.FCU_AP_2_PUSH", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_FCU_AP_DISCONNECT_PUSH, "A32NX.FCU_AP_DISCONNECT_PUSH", false);
@@ -397,6 +398,7 @@ bool SimConnectInterface::prepareSimInputSimConnectDataDefinitions() {
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTO_THROTTLE_ARM, "AUTO_THROTTLE_ARM", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTO_THROTTLE_DISCONNECT, "AUTO_THROTTLE_DISCONNECT", true);
+ result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_AUTO_THROTTLE_DISCONNECT, "A32NX.AUTO_THROTTLE_DISCONNECT", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTO_THROTTLE_TO_GA, "AUTO_THROTTLE_TO_GA", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_ATHR_RESET_DISABLE, "A32NX.ATHR_RESET_DISABLE", false);
@@ -1810,6 +1812,9 @@ void SimConnectInterface::processEvent(const DWORD eventId, const DWORD data0, c
if (static_cast(data0) == 1) {
simInputAutopilot.AP_disconnect = 1;
std::cout << "WASM: event triggered: AUTOPILOT_DISENGAGE_SET" << std::endl;
+
+ // Re emitting masked event for autopilot disconnection
+ sendEvent(SimConnectInterface::Events::A32NX_AUTOPILOT_DISENGAGE, 0, SIMCONNECT_GROUP_PRIORITY_STANDARD);
}
break;
}
@@ -2436,6 +2441,9 @@ void SimConnectInterface::processEvent(const DWORD eventId, const DWORD data0, c
case Events::AUTO_THROTTLE_DISCONNECT: {
simInputThrottles.ATHR_disconnect = 1;
std::cout << "WASM: event triggered: AUTO_THROTTLE_DISCONNECT" << std::endl;
+
+ // Re emitting masked event
+ sendEvent(Events::A32NX_AUTO_THROTTLE_DISCONNECT, 0, SIMCONNECT_GROUP_PRIORITY_STANDARD);
break;
}
diff --git a/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.h b/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.h
index fe4f980c487..f644c9cfef3 100644
--- a/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.h
+++ b/fbw-a32nx/src/wasm/fbw_a320/src/interface/SimConnectInterface.h
@@ -47,6 +47,7 @@ class SimConnectInterface {
AUTOPILOT_DISENGAGE_SET,
AUTOPILOT_DISENGAGE_TOGGLE,
TOGGLE_FLIGHT_DIRECTOR,
+ A32NX_AUTOPILOT_DISENGAGE,
A32NX_FCU_AP_1_PUSH,
A32NX_FCU_AP_2_PUSH,
A32NX_FCU_AP_DISCONNECT_PUSH,
@@ -148,6 +149,7 @@ class SimConnectInterface {
BAROMETRIC,
AUTO_THROTTLE_ARM,
AUTO_THROTTLE_DISCONNECT,
+ A32NX_AUTO_THROTTLE_DISCONNECT,
AUTO_THROTTLE_TO_GA,
A32NX_ATHR_RESET_DISABLE,
A32NX_THROTTLE_MAPPING_SET_DEFAULTS,
diff --git a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx
index 6d001810ac1..47527dc1641 100644
--- a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx
+++ b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher.tsx
@@ -5,201 +5,18 @@
import { EventBus, SimVarDefinition, SimVarValueType, SimVarPublisher } from '@microsoft/msfs-sdk';
export type PseudoFwcSimvars = {
- acEssBus: boolean;
- ewdPotentiometer: number;
- autoThrustCommand1: number;
- autoThrustCommand2: number;
- autoThrustLimit: number;
- autoThrustLimitToga: number;
- thrustLimitType: number;
- autoThrustMode: number;
- autoThrustStatus: number;
- autoThrustTLA1: number;
- autoThrustTLA2: number;
- autoThrustWarningToga: boolean;
- packs1Supplying: boolean;
- packs2Supplying: boolean;
- engine1AntiIce: boolean;
- engine1EGT: number;
- engine1Fadec: boolean;
- engine1FF: number;
engine1Master: number;
- engine1N1: number;
- engine1N2: number;
- engine1ReverserTransit: boolean;
- engine1ReverserDeployed: boolean;
- engine1State: number;
- engine2AntiIce: boolean;
- engine2EGT: number;
- engine2Fadec: boolean;
- engine2FF: number;
engine2Master: number;
- engine2N1: number;
- engine2N2: number;
- engine2ReverserTransit: boolean;
- engine2ReverserDeployed: boolean;
- engine2State: number;
engine3Master: number;
engine4Master: number;
- wingAntiIce: boolean;
- apuBleedPressure: number;
- left1LandingGear: boolean;
- right1LandingGear: boolean;
- throttle1Position: number;
- throttle2Position: number;
- fwcFlightPhase: number;
- idleN1: number;
- flexTemp: number;
- satRaw: number;
- totalFuel: number;
- slatsFlapsStatusRaw: number;
- slatsPositionRaw: number;
- flapsPositionRaw: number;
- ewdLowerLeft1: number;
- ewdLowerLeft2: number;
- ewdLowerLeft3: number;
- ewdLowerLeft4: number;
- ewdLowerLeft5: number;
- ewdLowerLeft6: number;
- ewdLowerLeft7: number;
- ewdLowerRight1: number;
- ewdLowerRight2: number;
- ewdLowerRight3: number;
- ewdLowerRight4: number;
- ewdLowerRight5: number;
- ewdLowerRight6: number;
- ewdLowerRight7: number;
};
-export enum PseudoFwcVars {
- acEssBus = 'L:A32NX_ELEC_AC_ESS_BUS_IS_POWERED',
- ewdPotentiometer = 'LIGHT POTENTIOMETER:92',
- autoThrustCommand1 = 'L:A32NX_AUTOTHRUST_N1_COMMANDED:1',
- autoThrustCommand2 = 'L:A32NX_AUTOTHRUST_N1_COMMANDED:2',
- autoThrustLimit = 'L:A32NX_AUTOTHRUST_THRUST_LIMIT',
- autoThrustLimitToga = 'L:A32NX_AUTOTHRUST_THRUST_LIMIT_TOGA',
- thrustLimitType = 'L:A32NX_AUTOTHRUST_THRUST_LIMIT_TYPE',
- autoThrustMode = 'L:A32NX_AUTOTHRUST_MODE',
- autoThrustStatus = 'L:A32NX_AUTOTHRUST_STATUS',
- autoThrustTLA1 = 'L:A32NX_AUTOTHRUST_TLA_N1:1',
- autoThrustTLA2 = 'L:A32NX_AUTOTHRUST_TLA_N1:2',
- autoThrustWarningToga = 'L:A32NX_AUTOTHRUST_THRUST_LEVER_WARNING_TOGA',
- packs1Supplying = 'L:A32NX_COND_PACK_FLOW_VALVE_1_IS_OPEN',
- packs2Supplying = 'L:A32NX_COND_PACK_FLOW_VALVE_2_IS_OPEN',
- engine1AntiIce = 'L:XMLVAR_Momentary_PUSH_OVHD_ANTIICE_ENG1_Pressed',
- engine1EGT = 'L:A32NX_ENGINE_EGT:1',
- engine1Fadec = 'L:A32NX_FADEC_POWERED_ENG1',
- engine1FF = 'L:A32NX_ENGINE_FF:1',
- engine1N1 = 'L:A32NX_ENGINE_N1:1',
- engine1N2 = 'L:A32NX_ENGINE_N2:1',
- engine1ReverserTransit = 'L:A32NX_REVERSER_1_DEPLOYING',
- engine1ReverserDeployed = 'L:A32NX_REVERSER_1_DEPLOYED',
- engine1State = 'L:A32NX_ENGINE_STATE:1',
- engine2AntiIce = 'L:XMLVAR_Momentary_PUSH_OVHD_ANTIICE_ENG2_Pressed',
- engine2EGT = 'L:A32NX_ENGINE_EGT:2',
- engine2Fadec = 'L:A32NX_FADEC_POWERED_ENG2',
- engine2FF = 'L:A32NX_ENGINE_FF:2',
- engine2N1 = 'L:A32NX_ENGINE_N1:2',
- engine2N2 = 'L:A32NX_ENGINE_N2:2',
- engine2ReverserTransit = 'L:A32NX_REVERSER_2_DEPLOYING',
- engine2ReverserDeployed = 'L:A32NX_REVERSER_2_DEPLOYED',
- engine2State = 'L:A32NX_ENGINE_STATE:2',
- wingAntiIce = 'L:A32NX_PNEU_WING_ANTI_ICE_SYSTEM_SELECTED',
- apuBleedPressure = 'L:APU_BLEED_PRESSURE',
- left1LandingGear = 'L:A32NX_LGCIU_1_LEFT_GEAR_COMPRESSED',
- right1LandingGear = 'L:A32NX_LGCIU_1_RIGHT_GEAR_COMPRESSED',
- throttle1Position = 'L:XMLVAR_Throttle1Position',
- throttle2Position = 'L:XMLVAR_Throttle2Position',
- fwcFlightPhase = 'L:A32NX_FWC_FLIGHT_PHASE',
- idleN1 = 'L:A32NX_ENGINE_IDLE_N1',
- flexTemp = 'L:AIRLINER_TO_FLEX_TEMP',
- satRaw = 'L:A32NX_ADIRS_ADR_1_STATIC_AIR_TEMPERATURE',
- totalFuel = 'FUEL TOTAL QUANTITY WEIGHT',
- slatsFlapsStatusRaw = 'L:A32NX_SFCC_SLAT_FLAP_SYSTEM_STATUS_WORD',
- slatsPositionRaw = 'L:A32NX_SFCC_SLAT_ACTUAL_POSITION_WORD',
- flapsPositionRaw = 'L:A32NX_SFCC_FLAP_ACTUAL_POSITION_WORD',
- ewdLowerLeft1 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_1',
- ewdLowerLeft2 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_2',
- ewdLowerLeft3 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_3',
- ewdLowerLeft4 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_4',
- ewdLowerLeft5 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_5',
- ewdLowerLeft6 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_6',
- ewdLowerLeft7 = 'L:A32NX_Ewd_LOWER_LEFT_LINE_7',
- ewdLowerRight1 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_1',
- ewdLowerRight2 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_2',
- ewdLowerRight3 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_3',
- ewdLowerRight4 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_4',
- ewdLowerRight5 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_5',
- ewdLowerRight6 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_6',
- ewdLowerRight7 = 'L:A32NX_Ewd_LOWER_RIGHT_LINE_7',
-}
-
export class PseudoFwcSimvarPublisher extends SimVarPublisher {
private static simvars = new Map([
- ['acEssBus', { name: PseudoFwcVars.acEssBus, type: SimVarValueType.Bool }],
- ['ewdPotentiometer', { name: PseudoFwcVars.ewdPotentiometer, type: SimVarValueType.Number }],
- ['autoThrustCommand1', { name: PseudoFwcVars.autoThrustCommand1, type: SimVarValueType.Number }],
- ['autoThrustCommand2', { name: PseudoFwcVars.autoThrustCommand2, type: SimVarValueType.Number }],
- ['autoThrustLimit', { name: PseudoFwcVars.autoThrustLimit, type: SimVarValueType.Number }],
- ['autoThrustLimitToga', { name: PseudoFwcVars.autoThrustLimitToga, type: SimVarValueType.Number }],
- ['thrustLimitType', { name: PseudoFwcVars.thrustLimitType, type: SimVarValueType.Enum }],
- ['autoThrustMode', { name: PseudoFwcVars.autoThrustMode, type: SimVarValueType.Enum }],
- ['autoThrustStatus', { name: PseudoFwcVars.autoThrustStatus, type: SimVarValueType.Enum }],
- ['autoThrustTLA1', { name: PseudoFwcVars.autoThrustTLA1, type: SimVarValueType.Number }],
- ['autoThrustTLA2', { name: PseudoFwcVars.autoThrustTLA2, type: SimVarValueType.Number }],
- ['autoThrustWarningToga', { name: PseudoFwcVars.autoThrustWarningToga, type: SimVarValueType.Bool }],
- ['packs1Supplying', { name: PseudoFwcVars.packs1Supplying, type: SimVarValueType.Bool }],
- ['packs2Supplying', { name: PseudoFwcVars.packs2Supplying, type: SimVarValueType.Bool }],
- ['engine1AntiIce', { name: PseudoFwcVars.engine1AntiIce, type: SimVarValueType.Bool }],
- ['engine1EGT', { name: PseudoFwcVars.engine1EGT, type: SimVarValueType.Number }],
- ['engine1Fadec', { name: PseudoFwcVars.engine1Fadec, type: SimVarValueType.Bool }],
['engine1Master', { name: 'A:FUELSYSTEM VALVE SWITCH:1', type: SimVarValueType.Bool }],
- ['engine1FF', { name: PseudoFwcVars.engine1FF, type: SimVarValueType.Number }],
- ['engine1N1', { name: PseudoFwcVars.engine1N1, type: SimVarValueType.Number }],
- ['engine1N2', { name: PseudoFwcVars.engine1N2, type: SimVarValueType.Number }],
- ['engine1ReverserTransit', { name: PseudoFwcVars.engine1ReverserTransit, type: SimVarValueType.Bool }],
- ['engine1ReverserDeployed', { name: PseudoFwcVars.engine1ReverserDeployed, type: SimVarValueType.Bool }],
- ['engine1State', { name: PseudoFwcVars.engine1State, type: SimVarValueType.Enum }],
- ['engine2AntiIce', { name: PseudoFwcVars.engine2AntiIce, type: SimVarValueType.Bool }],
- ['engine2EGT', { name: PseudoFwcVars.engine2EGT, type: SimVarValueType.Number }],
- ['engine2Fadec', { name: PseudoFwcVars.engine2Fadec, type: SimVarValueType.Bool }],
- ['engine2FF', { name: PseudoFwcVars.engine2FF, type: SimVarValueType.Number }],
['engine2Master', { name: 'A:FUELSYSTEM VALVE SWITCH:2', type: SimVarValueType.Bool }],
- ['engine2N1', { name: PseudoFwcVars.engine2N1, type: SimVarValueType.Number }],
- ['engine2N2', { name: PseudoFwcVars.engine2N2, type: SimVarValueType.Number }],
- ['engine2ReverserTransit', { name: PseudoFwcVars.engine2ReverserTransit, type: SimVarValueType.Bool }],
- ['engine2ReverserDeployed', { name: PseudoFwcVars.engine2ReverserDeployed, type: SimVarValueType.Bool }],
- ['engine2State', { name: PseudoFwcVars.engine2State, type: SimVarValueType.Enum }],
['engine3Master', { name: 'A:FUELSYSTEM VALVE SWITCH:3', type: SimVarValueType.Bool }],
['engine4Master', { name: 'A:FUELSYSTEM VALVE SWITCH:4', type: SimVarValueType.Bool }],
- ['wingAntiIce', { name: PseudoFwcVars.wingAntiIce, type: SimVarValueType.Bool }],
- ['apuBleedPressure', { name: PseudoFwcVars.apuBleedPressure, type: SimVarValueType.PSI }],
- ['left1LandingGear', { name: PseudoFwcVars.left1LandingGear, type: SimVarValueType.Bool }],
- ['right1LandingGear', { name: PseudoFwcVars.right1LandingGear, type: SimVarValueType.Bool }],
- ['throttle1Position', { name: PseudoFwcVars.throttle1Position, type: SimVarValueType.Number }],
- ['throttle2Position', { name: PseudoFwcVars.throttle2Position, type: SimVarValueType.Number }],
- ['fwcFlightPhase', { name: PseudoFwcVars.fwcFlightPhase, type: SimVarValueType.Enum }],
- ['idleN1', { name: PseudoFwcVars.idleN1, type: SimVarValueType.Number }],
- ['flexTemp', { name: PseudoFwcVars.flexTemp, type: SimVarValueType.Number }],
- ['satRaw', { name: PseudoFwcVars.satRaw, type: SimVarValueType.Number }],
- ['totalFuel', { name: PseudoFwcVars.totalFuel, type: SimVarValueType.Number }],
- ['slatsFlapsStatusRaw', { name: PseudoFwcVars.slatsFlapsStatusRaw, type: SimVarValueType.Number }],
- ['slatsPositionRaw', { name: PseudoFwcVars.slatsPositionRaw, type: SimVarValueType.Number }],
- ['flapsPositionRaw', { name: PseudoFwcVars.flapsPositionRaw, type: SimVarValueType.Number }],
- ['ewdLowerLeft1', { name: PseudoFwcVars.ewdLowerLeft1, type: SimVarValueType.Number }],
- ['ewdLowerLeft2', { name: PseudoFwcVars.ewdLowerLeft2, type: SimVarValueType.Number }],
- ['ewdLowerLeft3', { name: PseudoFwcVars.ewdLowerLeft3, type: SimVarValueType.Number }],
- ['ewdLowerLeft4', { name: PseudoFwcVars.ewdLowerLeft4, type: SimVarValueType.Number }],
- ['ewdLowerLeft5', { name: PseudoFwcVars.ewdLowerLeft5, type: SimVarValueType.Number }],
- ['ewdLowerLeft6', { name: PseudoFwcVars.ewdLowerLeft6, type: SimVarValueType.Number }],
- ['ewdLowerLeft7', { name: PseudoFwcVars.ewdLowerLeft7, type: SimVarValueType.Number }],
- ['ewdLowerRight1', { name: PseudoFwcVars.ewdLowerRight1, type: SimVarValueType.Number }],
- ['ewdLowerRight2', { name: PseudoFwcVars.ewdLowerRight2, type: SimVarValueType.Number }],
- ['ewdLowerRight3', { name: PseudoFwcVars.ewdLowerRight3, type: SimVarValueType.Number }],
- ['ewdLowerRight4', { name: PseudoFwcVars.ewdLowerRight4, type: SimVarValueType.Number }],
- ['ewdLowerRight5', { name: PseudoFwcVars.ewdLowerRight5, type: SimVarValueType.Number }],
- ['ewdLowerRight6', { name: PseudoFwcVars.ewdLowerRight6, type: SimVarValueType.Number }],
- ['ewdLowerRight7', { name: PseudoFwcVars.ewdLowerRight7, type: SimVarValueType.Number }],
]);
public constructor(bus: EventBus) {
diff --git a/fbw-a380x/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx b/fbw-a380x/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx
index b289f68de72..de69cc28db8 100644
--- a/fbw-a380x/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx
+++ b/fbw-a380x/src/systems/instruments/src/PFD/shared/PFDSimvarPublisher.tsx
@@ -194,7 +194,7 @@ export enum PFDVars {
fmaSpeedProtection = 'L:A32NX_FMA_SPEED_PROTECTION_MODE',
AThrMode = 'L:A32NX_AUTOTHRUST_MODE',
apVsSelected = 'L:A32NX_AUTOPILOT_VS_SELECTED',
- approachCapability = 'L:A32NX_ApproachCapability',
+ approachCapability = 'L:A32NX_APPROACH_CAPABILITY',
ap1Active = 'L:A32NX_AUTOPILOT_1_ACTIVE',
ap2Active = 'L:A32NX_AUTOPILOT_2_ACTIVE',
fmaVerticalArmed = 'L:A32NX_FMA_VERTICAL_ARMED',
diff --git a/fbw-a380x/src/systems/systems-host/index.ts b/fbw-a380x/src/systems/systems-host/index.ts
index 1d93a2a0f3d..b8ebac5a4de 100644
--- a/fbw-a380x/src/systems/systems-host/index.ts
+++ b/fbw-a380x/src/systems/systems-host/index.ts
@@ -11,9 +11,9 @@ import {
MappedSubject,
SubscribableMapFunctions,
WeightBalanceSimvarPublisher,
+ StallWarningPublisher,
} from '@microsoft/msfs-sdk';
import { LegacyGpws } from 'systems-host/systems/LegacyGpws';
-import { LegacyFwc } from 'systems-host/systems/LegacyFwc';
import { LegacyFuel } from 'systems-host/systems/LegacyFuel';
import { LegacySoundManager } from 'systems-host/systems/LegacySoundManager';
import { LegacyTcasComputer } from 'systems-host/systems/tcas/components/LegacyTcasComputer';
@@ -34,6 +34,7 @@ import { AtsuSystem } from 'systems-host/systems/atsu';
import { FwsCore } from 'systems-host/systems/FlightWarningSystem/FwsCore';
import { FuelSystemPublisher } from 'systems-host/systems/FuelSystemPublisher';
import { BrakeToVacateDistanceUpdater } from 'systems-host/systems/BrakeToVacateDistanceUpdater';
+import { PseudoFwcSimvarPublisher } from 'instruments/src/MsfsAvionicsCommon/providers/PseudoFwcPublisher';
class SystemsHost extends BaseInstrument {
private readonly bus = new ArincEventBus();
@@ -49,9 +50,6 @@ class SystemsHost extends BaseInstrument {
private readonly failuresConsumer = new FailuresConsumer('A32NX');
// TODO: Migrate PowerSupplyBusses, if needed
-
- private fwc: LegacyFwc;
-
private gpws: LegacyGpws;
private soundManager: LegacySoundManager;
@@ -97,6 +95,10 @@ class SystemsHost extends BaseInstrument {
private readonly fuelSystemPublisher = new FuelSystemPublisher(this.bus);
+ private readonly stallWarningPublisher = new StallWarningPublisher(this.bus, 0.9);
+
+ private readonly pseudoFwcPublisher = new PseudoFwcSimvarPublisher(this.bus);
+
private readonly fwsCore = new FwsCore(1, this.bus);
//FIXME add some deltatime functionality to backplane instruments so we dont have to pass SystemHost
@@ -129,12 +131,13 @@ class SystemsHost extends BaseInstrument {
this.backplane.addPublisher('BtvPublisher', this.btvPublisher);
this.backplane.addPublisher('Weightpublisher', this.weightAndBalancePublisher);
this.backplane.addPublisher('FuelPublisher', this.fuelSystemPublisher);
+ this.backplane.addPublisher('StallWarning', this.stallWarningPublisher);
+ this.backplane.addPublisher('PseudoFwc', this.pseudoFwcPublisher);
this.backplane.addInstrument('LegacyFuel', this.legacyFuel);
this.hEventPublisher = new HEventPublisher(this.bus);
- this.fwc = new LegacyFwc();
this.soundManager = new LegacySoundManager();
- this.gpws = new LegacyGpws(this.soundManager);
+ this.gpws = new LegacyGpws(this.bus, this.soundManager);
this.gpws.init();
this.fwsCore.init();
@@ -149,7 +152,6 @@ class SystemsHost extends BaseInstrument {
const dt = lastUpdateTime === undefined ? 0 : now - lastUpdateTime;
lastUpdateTime = now;
- this.fwc.update(dt);
this.soundManager.update(dt);
this.gpws.update(dt);
this.fwsCore.update(dt);
diff --git a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsAbnormalSensed.ts b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsAbnormalSensed.ts
index 9e3583db849..f1b7f4b83ab 100644
--- a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsAbnormalSensed.ts
+++ b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsAbnormalSensed.ts
@@ -1456,7 +1456,7 @@ export class FwsAbnormalSensed {
flightPhaseInhib: [3, 4, 5, 10],
simVarIsActive: this.fws.autoThrustOffInvoluntary,
notActiveWhenFaults: [],
- whichItemsToShow: () => [true],
+ whichItemsToShow: () => [false],
whichItemsChecked: () => [SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_MODE_MESSAGE', 'number') !== 1],
failure: 2,
sysPage: -1,
diff --git a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsCore.ts b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsCore.ts
index 276c4089590..80e943068d8 100644
--- a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsCore.ts
+++ b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsCore.ts
@@ -49,6 +49,8 @@ import { EwdAbnormalItem, FwsAbnormalSensed } from 'systems-host/systems/FlightW
import { FwsAbnormalNonSensed } from 'systems-host/systems/FlightWarningSystem/FwsAbnormalNonSensed';
import { MfdSurvEvents } from 'instruments/src/MsfsAvionicsCommon/providers/MfdSurvPublisher';
import { Mle, Mmo, VfeF1, VfeF1F, VfeF2, VfeF3, VfeFF, Vle, Vmo } from '@shared/PerformanceConstants';
+import { FwsAuralVolume, FwsSoundManager } from 'systems-host/systems/FlightWarningSystem/FwsSoundManager';
+import { FwcFlightPhase, FwsFlightPhases } from 'systems-host/systems/FlightWarningSystem/FwsFlightPhases';
export function xor(a: boolean, b: boolean): boolean {
return !!((a ? 1 : 0) ^ (b ? 1 : 0));
@@ -70,30 +72,29 @@ export enum FwcAuralWarning {
CavalryCharge,
}
-export enum FwcAuralVolume {
- Full, // 0 dB
- Attenuated, // -6dB
- Silent, // -200 dB
-}
-
export class FwsCore {
public readonly sub = this.bus.getSubscriber<
PseudoFwcSimvars & StallWarningEvents & MfdSurvEvents & FuelSystemEvents & KeyEvents
>();
public readonly vhfSub = this.bus.getSubscriber();
- private fwsUpdateThrottler = new UpdateThrottler(125); // has to be > 100 due to pulse nodes
+ private readonly fwsUpdateThrottler = new UpdateThrottler(125); // has to be > 100 due to pulse nodes
private keyEventManager: KeyEventManager;
+ private readonly startupTimer = new DebounceTimer();
+
+ private readonly startupCompleted = Subject.create(false);
+
+ public readonly soundManager = new FwsSoundManager(this.bus, this.startupCompleted);
+
+ private readonly flightPhases = new FwsFlightPhases(this);
+
/** 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 = 5000;
-
- /** The time to play the single chime sound in ms */
- private static readonly AURAL_SC_PLAY_TIME = 500;
+ private static readonly AURAL_SC_INHIBIT_TIME = 2000;
private static readonly EWD_MESSAGE_LINES = 10;
@@ -207,9 +208,6 @@ export class FwsCore {
public readonly apDiscInputBuffer = new NXLogicMemoryNode(false);
/* PSEUDO FWC VARIABLES */
- private readonly startupTimer = new DebounceTimer();
-
- private readonly startupCompleted = Subject.create(false);
/** Keys/IDs of all failures currently active, irrespective they are already cleared or not */
public readonly allCurrentFailures: string[] = [];
@@ -243,8 +241,6 @@ export class FwsCore {
public readonly auralSingleChimePlayingTimer = new DebounceTimer();
- public readonly auralCavalryChargeActive = Subject.create(false);
-
public readonly masterWarning = Subject.create(false);
public readonly masterCaution = Subject.create(false);
@@ -266,21 +262,6 @@ export class FwsCore {
this.startupCompleted,
);
- public readonly auralCrcOutput = MappedSubject.create(
- ([auralCrc, startup]) => auralCrc && startup,
- this.auralCrcActive,
- this.startupCompleted,
- );
-
- public readonly auralCavalryChargeOutput = MappedSubject.create(
- ([auralCavCharge, auralCrc, startup]) => auralCavCharge && !auralCrc && startup,
- this.auralCavalryChargeActive,
- this.auralCrcOutput,
- this.startupCompleted,
- );
-
- public readonly fwsAuralVolume = Subject.create(FwcAuralVolume.Full);
-
public readonly ecamStsNormal = Subject.create(true);
public readonly fwcOut126 = Arinc429RegisterSubject.createEmpty();
@@ -486,14 +467,17 @@ export class FwsCore {
/** 1.8s according to references, but was raised to 1.9s to allow for triple click to finish */
public readonly autoPilotInstinctiveDiscPressedTwiceInLast1p9Sec = new NXLogicTriggeredMonostableNode(1.9, true);
+ /** Prohibits that CRC can be cancelled before even hearing it */
+ public readonly autoPilotFirstCavalryStillWithinFirst0p3s = new NXLogicTriggeredMonostableNode(0.3, true);
+
public readonly autoPilotInstinctiveDiscPressedPulse = new NXLogicPulseNode(true);
/** Stay in first warning stage for 1.8s. Raised to 1.9s to allow for triple click to finish */
public readonly autoPilotOffVoluntaryEndAfter1p9s = new NXLogicTriggeredMonostableNode(1.9, true);
- public readonly autoPilotOffVoluntaryFirstCavalryChargeActive = new NXLogicTriggeredMonostableNode(0.8, true);
+ public readonly autoPilotOffVoluntaryFirstCavalryChargeActive = new NXLogicTriggeredMonostableNode(0.9, true);
- public readonly autoPilotOffSendTripleClickAfterFirstCavalryCharge = new NXLogicPulseNode(false);
+ public readonly autoPilotOffVoluntaryFirstCavalryChargeActivePulse = new NXLogicPulseNode(false);
public readonly autoPilotOffVoluntaryDiscPulse = new NXLogicPulseNode(true);
@@ -507,6 +491,14 @@ export class FwsCore {
public readonly autoPilotOffShowMemo = Subject.create(false);
+ public readonly approachCapability = Subject.create(0);
+
+ public readonly approachCapabilityDowngradeDebounce = new NXLogicTriggeredMonostableNode(1, true);
+
+ public readonly approachCapabilityDowngradeSuppress = new NXLogicTriggeredMonostableNode(3, true);
+
+ public readonly approachCapabilityDowngradeDebouncePulse = new NXLogicPulseNode(false);
+
public readonly autoThrustDisengagedInstantPulse = new NXLogicPulseNode(false);
public readonly autoThrustInstinctiveDiscPressed = new NXLogicTriggeredMonostableNode(1.5, true); // Save event for 1.5 sec
@@ -1035,7 +1027,7 @@ export class FwsCore {
/* 31 - FWS */
- public readonly fwcFlightPhase = Subject.create(-1);
+ public readonly flightPhase = Subject.create(null);
public readonly flightPhase128 = Subject.create(false);
@@ -1093,8 +1085,6 @@ export class FwsCore {
/** If one of the ADR's CAS is above V1 - 4kts, confirm for 0.3s */
public readonly v1SpeedConfirmNode = new NXLogicConfirmNode(0.3);
- public readonly v1CalloutOutput = Subject.create(false);
-
/* LANDING GEAR AND LIGHTS */
public readonly aircraftOnGround = Subject.create(false);
@@ -1175,6 +1165,8 @@ export class FwsCore {
public readonly autoBrakeOff = Subject.create(false);
+ public autoBrakeOffAuralTriggered = false;
+
public autoBrakeOffMemoInhibited = false;
/* NAVIGATION */
@@ -1223,6 +1215,8 @@ export class FwsCore {
this.fwsNumber === 2 ? this.airDataFoOn3 : this.airDataCaptOn3,
);
+ public readonly adrPressureAltitude = Subject.create(0);
+
public readonly ir1MaintWord = Arinc429Register.empty();
public readonly ir2MaintWord = Arinc429Register.empty();
public readonly ir3MaintWord = Arinc429Register.empty();
@@ -1579,22 +1573,14 @@ export class FwsCore {
});
this.toConfigNormal.sub((normal) => SimVar.SetSimVarValue('L:A32NX_TO_CONFIG_NORMAL', 'bool', normal));
- this.fwcFlightPhase.sub(() => this.flightPhaseEndedPulseNode.write(true, 0));
+ this.flightPhase.sub((fp) => {
+ SimVar.SetSimVarValue('L:A32NX_FWC_FLIGHT_PHASE', 'Enum', fp || 0);
+ if (fp !== null) {
+ this.flightPhaseEndedPulseNode.write(true, 0);
+ }
+ });
- this.auralCrcOutput.sub(
- (crc) => SimVar.SetSimVarValue('L:A32NX_FWC_CRC', 'bool', this.startupCompleted.get() ? crc : false),
- true,
- );
-
- this.auralCavalryChargeOutput.sub(
- (cc) =>
- SimVar.SetSimVarValue(
- 'L:A32NX_FWC_CAVALRY_CHARGE',
- SimVarValueType.Bool,
- this.startupCompleted.get() ? cc : false,
- ),
- true,
- );
+ this.auralCrcActive.sub((crc) => this.soundManager.handleSoundCondition('continuousRepetitiveChime', crc), true);
this.masterCautionOutput.sub((caution) => {
// Inhibit master warning/cautions until FWC startup has been completed
@@ -1606,13 +1592,6 @@ export class FwsCore {
SimVar.SetSimVarValue('L:A32NX_MASTER_WARNING', 'bool', warning);
}, true);
- this.fwsAuralVolume.sub((volume) => {
- // Inhibit master warning/cautions until FWC startup has been completed
- SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, volume);
- }, true);
-
- this.v1CalloutOutput.sub((c) => SimVar.SetSimVarValue('L:A32NX_AUDIO_V1_CALLOUT', SimVarValueType.Bool, c), true);
-
// L/G lever red arrow sinking outputs
this.lgLeverRedArrow.sub((on) => {
// TODO FWCs need to be powered...
@@ -1623,7 +1602,7 @@ export class FwsCore {
this.stallWarning.sub((v) => {
this.fwcOut126.setBitValue(17, v);
// set the sound on/off
- SimVar.SetSimVarValue('L:A32NX_AUDIO_STALL_WARNING', 'bool', v);
+ this.soundManager.handleSoundCondition('stall', v);
}, true);
this.aircraftOnGround.sub((v) => this.fwcOut126.setBitValue(28, v));
@@ -1832,6 +1811,12 @@ export class FwsCore {
// A380X hack: Inject healthy messages for some systems which are not yet implemented
this.healthInjector();
+ // Update flight phases
+ this.flightPhases.update(deltaTime);
+
+ // Play sounds
+ this.soundManager.onUpdate(deltaTime);
+
// Write pulse nodes for buffered inputs
this.toConfigPulseNode.write(this.toConfigInputBuffer.read(), deltaTime);
this.clrPulseNode.write(this.clearButtonInputBuffer.read(), deltaTime);
@@ -1845,24 +1830,24 @@ export class FwsCore {
// Inputs update
this.flightPhaseEndedPulseNode.write(false, deltaTime);
- this.fwcFlightPhase.set(SimVar.GetSimVarValue('L:A32NX_FWC_FLIGHT_PHASE', 'Enum'));
- const phase3 = this.fwcFlightPhase.get() === 3;
- const phase6 = this.fwcFlightPhase.get() === 6;
+ const phase3 = this.flightPhase.get() === 3;
+ const phase6 = this.flightPhase.get() === 6;
this.flightPhase3PulseNode.write(phase3, deltaTime);
// flight phase convenience vars
- this.flightPhase128.set([1, 2, 8].includes(this.fwcFlightPhase.get()));
- this.flightPhase23.set([2, 3].includes(this.fwcFlightPhase.get()));
- this.flightPhase345.set([3, 4, 5].includes(this.fwcFlightPhase.get()));
+ this.flightPhase128.set([1, 2, 8].includes(this.flightPhase.get()));
+ this.flightPhase23.set([2, 3].includes(this.flightPhase.get()));
+ this.flightPhase345.set([3, 4, 5].includes(this.flightPhase.get()));
this.flightPhase34567.set(
- this.flightPhase345.get() || this.fwcFlightPhase.get() === 6 || this.fwcFlightPhase.get() === 7,
+ this.flightPhase345.get() || this.flightPhase.get() === 6 || this.flightPhase.get() === 7,
);
- this.flightPhase1211.set([1, 2, 11].includes(this.fwcFlightPhase.get()));
- this.flightPhase89.set([8, 9].includes(this.fwcFlightPhase.get()));
- this.flightPhase910.set([9, 10].includes(this.fwcFlightPhase.get()));
- const flightPhase6789 = [6, 7, 8, 9].includes(this.fwcFlightPhase.get());
- const flightPhase112 = [1, 12].includes(this.fwcFlightPhase.get());
+ this.flightPhase1211.set([1, 2, 11].includes(this.flightPhase.get()));
+ this.flightPhase89.set([8, 9].includes(this.flightPhase.get()));
+ this.flightPhase910.set([9, 10].includes(this.flightPhase.get()));
+ const flightPhase6789 = [6, 7, 8, 9].includes(this.flightPhase.get());
+ const flightPhase112 = [1, 12].includes(this.flightPhase.get());
+ const flightPhase189 = [1, 8, 9].includes(this.flightPhase.get());
- this.phase815MinConfNode.write(this.fwcFlightPhase.get() === 8, deltaTime);
+ this.phase815MinConfNode.write(this.flightPhase.get() === 8, deltaTime);
this.phase112.set(flightPhase112);
// TO CONFIG button
@@ -2011,12 +1996,12 @@ export class FwsCore {
const yLoPressure = !yellowSysPressurised;
this.eng1Or2RunningAndPhaseConfirmationNode.write(
- this.engine1Running.get() || this.engine2Running.get() || ![1, 2, 11, 12].includes(this.fwcFlightPhase.get()),
+ this.engine1Running.get() || this.engine2Running.get() || ![1, 2, 11, 12].includes(this.flightPhase.get()),
deltaTime,
);
this.eng3Or4RunningAndPhaseConfirmationNode.write(
- this.engine3Running.get() || this.engine4Running.get() || ![1, 2, 11, 12].includes(this.fwcFlightPhase.get()),
+ this.engine3Running.get() || this.engine4Running.get() || ![1, 2, 11, 12].includes(this.flightPhase.get()),
deltaTime,
);
@@ -2097,7 +2082,7 @@ export class FwsCore {
!this.greenRsvLoAirPressure.get() &&
!this.greenRsvOverheat.get() &&
!this.greenAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng1APumpAuto.get(),
deltaTime,
);
@@ -2114,7 +2099,7 @@ export class FwsCore {
!this.greenRsvLoAirPressure.get() &&
!this.greenRsvOverheat.get() &&
!this.greenAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng1BPumpAuto.get(),
deltaTime,
);
@@ -2135,7 +2120,7 @@ export class FwsCore {
!this.greenRsvLoAirPressure.get() &&
!this.greenRsvOverheat.get() &&
!this.greenAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng2APumpAuto.get(),
deltaTime,
);
@@ -2153,7 +2138,7 @@ export class FwsCore {
!this.greenRsvLoAirPressure.get() &&
!this.greenRsvOverheat.get() &&
!this.greenAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng2BPumpAuto.get(),
deltaTime,
);
@@ -2173,7 +2158,7 @@ export class FwsCore {
!this.yellowRsvLoAirPressure.get() &&
!this.yellowRsvOverheat.get() &&
!this.yellowAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng3APumpAuto.get(),
deltaTime,
);
@@ -2194,7 +2179,7 @@ export class FwsCore {
!this.yellowRsvLoAirPressure.get() &&
!this.yellowRsvOverheat.get() &&
!this.yellowAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng3BPumpAuto.get(),
deltaTime,
);
@@ -2216,7 +2201,7 @@ export class FwsCore {
!this.yellowRsvLoAirPressure.get() &&
!this.yellowRsvOverheat.get() &&
!this.yellowAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng4APumpAuto,
deltaTime,
);
@@ -2235,7 +2220,7 @@ export class FwsCore {
!this.yellowRsvLoAirPressure.get() &&
!this.yellowRsvOverheat.get() &&
!this.yellowAbnormLoPressure.get() &&
- this.fwcFlightPhase.get() === 2 &&
+ this.flightPhase.get() === 2 &&
!this.eng4BPumpAuto.get(),
deltaTime,
);
@@ -2284,8 +2269,9 @@ export class FwsCore {
this.adirsRemainingAlignTime.set(SimVar.GetSimVarValue('L:A32NX_ADIRS_REMAINING_IR_ALIGNMENT_TIME', 'Seconds'));
// TODO use GPS alt if ADRs not available
- const pressureAltitude =
- adr1PressureAltitude.valueOr(null) ?? adr2PressureAltitude.valueOr(null) ?? adr3PressureAltitude.valueOr(null);
+ this.adrPressureAltitude.set(
+ adr1PressureAltitude.valueOr(null) ?? adr2PressureAltitude.valueOr(null) ?? adr3PressureAltitude.valueOr(null),
+ );
this.ir1Align.set(
this.ir1MaintWord.bitValueOr(16, false) ||
this.ir1MaintWord.bitValueOr(17, false) ||
@@ -2319,6 +2305,7 @@ export class FwsCore {
/* V1 callout */
const v1 = SimVar.GetSimVarValue('L:AIRLINER_V1_SPEED', SimVarValueType.Knots);
const v1Threshold = v1 - 4;
+ const v1ConfirmNodeStatus = this.v1SpeedConfirmNode.read();
this.v1SpeedConfirmNode.write(
v1 &&
(this.adr1Cas.get().valueOr(0) > v1Threshold ||
@@ -2326,7 +2313,14 @@ export class FwsCore {
this.adr3Cas.get().valueOr(0) > v1Threshold),
deltaTime,
);
- this.v1CalloutOutput.set(this.fwcFlightPhase.get() === 4 && this.v1SpeedConfirmNode.read());
+ if (
+ this.flightPhase.get() === 4 &&
+ this.v1SpeedConfirmNode.read() &&
+ !v1ConfirmNodeStatus &&
+ this.v1SpeedConfirmNode.read()
+ ) {
+ this.soundManager.enqueueSound('v1');
+ }
/* LANDING GEAR AND LIGHTS acquisition */
@@ -2447,14 +2441,14 @@ export class FwsCore {
this.autoPilotOffVoluntaryDiscPulse.write(voluntaryApDisc, deltaTime);
this.autoPilotOffVoluntaryFirstCavalryChargeActive.write(this.autoPilotOffVoluntaryDiscPulse.read(), deltaTime);
- this.autoPilotOffSendTripleClickAfterFirstCavalryCharge.write(
+ this.autoPilotOffVoluntaryFirstCavalryChargeActivePulse.write(
this.autoPilotOffVoluntaryFirstCavalryChargeActive.read(),
deltaTime,
);
- SimVar.SetSimVarValue(
- 'L:A32NX_FMA_TRIPLE_CLICK',
- 'Bool',
- this.autoPilotOffSendTripleClickAfterFirstCavalryCharge.read(),
+
+ this.autoPilotFirstCavalryStillWithinFirst0p3s.write(
+ this.autoPilotOffVoluntaryFirstCavalryChargeActive.read(),
+ deltaTime,
);
this.autoPilotInstinctiveDiscPressedTwiceInLast1p9Sec.write(
@@ -2466,13 +2460,14 @@ export class FwsCore {
this.autoPilotOffVoluntaryMemory.write(
this.autoPilotOffVoluntaryDiscPulse.read(),
apEngaged ||
- this.autoPilotInstinctiveDiscPressedTwiceInLast1p9Sec.read() ||
+ (this.autoPilotInstinctiveDiscPressedTwiceInLast1p9Sec.read() &&
+ !this.autoPilotFirstCavalryStillWithinFirst0p3s.read()) ||
!this.autoPilotOffVoluntaryEndAfter1p9s.read(),
);
const discPbPressedAfterDisconnection =
!this.autoPilotDisengagedInstantPulse.read() &&
- (this.autoPilotInstinctiveDiscPressedPulse.read() || masterWarningButtonLeft || masterCautionButtonRight);
+ (this.autoPilotInstinctiveDiscPressedPulse.read() || masterWarningButtonLeft || masterWarningButtonRight);
this.autoPilotOffUnacknowledged.write(
this.autoPilotDisengagedInstantPulse.read(),
@@ -2489,21 +2484,37 @@ export class FwsCore {
if (this.autoPilotDisengagedInstantPulse.read()) {
// Request quiet CRC one time
this.requestMasterWarningFromApOff = true;
- this.auralCavalryChargeActive.set(true);
- this.fwsAuralVolume.set(FwcAuralVolume.Attenuated);
+ this.soundManager.setVolume(FwsAuralVolume.Attenuated);
+ this.soundManager.enqueueSound('cavalryChargeCont'); // On the A380, first cav charge can be cancelled early
}
- if (!this.autoPilotOffVoluntaryFirstCavalryChargeActive.read()) {
- this.auralCavalryChargeActive.set(false);
- this.fwsAuralVolume.set(FwcAuralVolume.Full);
+ if (this.autoPilotOffVoluntaryFirstCavalryChargeActivePulse.read()) {
+ this.soundManager.dequeueSound('cavalryChargeCont');
+ this.soundManager.setVolume(FwsAuralVolume.Full);
}
if (!this.autoPilotOffVoluntaryMemory.read() && !this.autoPilotOffInvoluntaryMemory.read()) {
this.requestMasterWarningFromApOff = false;
- this.auralCavalryChargeActive.set(false);
- this.fwsAuralVolume.set(FwcAuralVolume.Full);
+ this.soundManager.dequeueSound('cavalryChargeCont');
+ this.soundManager.setVolume(FwsAuralVolume.Full);
}
this.autoPilotInstinctiveDiscPressedPulse.write(false, deltaTime);
+ // approach capability downgrade. Debounce first, then suppress for a certain amount of time
+ // (to avoid multiple triple clicks, and a delay which is too long)
+ const newCapability = SimVar.GetSimVarValue('L:A32NX_APPROACH_CAPABILITY', SimVarValueType.Number);
+ const capabilityDowngrade = newCapability < this.approachCapability.get() && newCapability > 0;
+ this.approachCapabilityDowngradeDebounce.write(
+ capabilityDowngrade && flightPhase189 && !this.approachCapabilityDowngradeSuppress.read(),
+ deltaTime,
+ );
+ this.approachCapabilityDowngradeDebouncePulse.write(this.approachCapabilityDowngradeDebounce.read(), deltaTime);
+ this.approachCapabilityDowngradeSuppress.write(this.approachCapabilityDowngradeDebouncePulse.read(), deltaTime);
+ // Capability downgrade after debounce --> triple click
+ if (this.approachCapabilityDowngradeDebouncePulse.read()) {
+ this.soundManager.enqueueSound('tripleClick');
+ }
+ this.approachCapability.set(newCapability);
+
// A/THR OFF
const aThrEngaged = this.autoThrustStatus.get() === 2 || this.autoThrustMode.get() !== 0;
this.autoThrustDisengagedInstantPulse.write(aThrEngaged, deltaTime);
@@ -2563,6 +2574,7 @@ export class FwsCore {
if (!this.autoBrakeDeactivatedNode.read()) {
this.autoBrakeOffMemoInhibited = false;
this.requestMasterCautionFromABrkOff = false;
+ this.autoBrakeOffAuralTriggered = false;
}
this.autoBrakeOffAuralConfirmNode.write(
@@ -2583,11 +2595,10 @@ export class FwsCore {
// FIXME double callout if ABRK fails
this.autoBrakeOff.set(autoBrakeOffShouldTrigger);
- SimVar.SetSimVarValue(
- 'L:A32NX_AUDIO_AUTOBRAKE_OFF',
- SimVarValueType.Bool,
- autoBrakeOffShouldTrigger && this.autoBrakeOffAuralConfirmNode.read(),
- );
+ if (autoBrakeOffShouldTrigger && this.autoBrakeOffAuralConfirmNode.read() && !this.autoBrakeOffAuralTriggered) {
+ this.soundManager.enqueueSound('autoBrakeOff');
+ this.autoBrakeOffAuralTriggered = true;
+ }
// Engine Logic
this.thrustLeverNotSet.set(this.autothrustLeverWarningFlex.get() || this.autothrustLeverWarningToga.get());
@@ -2717,11 +2728,11 @@ export class FwsCore {
/* 21 - AIR CONDITIONING AND PRESSURIZATION */
- this.flightLevel.set(Math.round(pressureAltitude / 100));
+ this.flightLevel.set(Math.round(this.adrPressureAltitude.get() / 100));
- this.phase8ConfirmationNode60.write(this.fwcFlightPhase.get() === 8, deltaTime);
+ this.phase8ConfirmationNode60.write(this.flightPhase.get() === 8, deltaTime);
- this.phase8ConfirmationNode180.write(this.fwcFlightPhase.get() === 8, deltaTime);
+ this.phase8ConfirmationNode180.write(this.flightPhase.get() === 8, deltaTime);
this.fdac1Channel1Failure.set(SimVar.GetSimVarValue('L:A32NX_COND_FDAC_1_CHANNEL_1_FAILURE', 'bool'));
this.fdac1Channel2Failure.set(SimVar.GetSimVarValue('L:A32NX_COND_FDAC_1_CHANNEL_2_FAILURE', 'bool'));
@@ -3225,7 +3236,7 @@ export class FwsCore {
const flapsNotInToPos = this.flapsSuperiorTo26Deg.get() || this.flapsInferiorTo8Deg.get();
this.flapConfigSr.write(
this.flightPhase345.get() && flapsNotInToPos,
- !flapsNotInToPos || phase6 || this.fwcFlightPhase.get() === 7,
+ !flapsNotInToPos || phase6 || this.flightPhase.get() === 7,
);
this.flapsNotTo.set(this.flightPhase1211.get() && flapsNotInToPos);
this.flapsNotToMemo.set(this.flapConfigSr.read() || this.flapsNotTo.get());
@@ -3240,7 +3251,7 @@ export class FwsCore {
const slatsNotInToPos = this.slatsInferiorTo20Deg.get();
this.slatConfigSr.write(
this.flightPhase345.get() && slatsNotInToPos,
- !slatsNotInToPos || phase6 || this.fwcFlightPhase.get() === 7,
+ !slatsNotInToPos || phase6 || this.flightPhase.get() === 7,
);
this.slatsNotTo.set(this.flightPhase1211.get() && slatsNotInToPos);
this.slatConfigAural.set(
@@ -3254,7 +3265,7 @@ export class FwsCore {
const speedbrakesNotInToPos = fcdc1DiscreteWord4.bitValueOr(28, false) || fcdc2DiscreteWord4.bitValueOr(28, false);
this.speedbrakesConfigSr.write(
this.flightPhase345.get() && speedbrakesNotInToPos,
- !speedbrakesNotInToPos || phase6 || this.fwcFlightPhase.get() === 7,
+ !speedbrakesNotInToPos || phase6 || this.flightPhase.get() === 7,
);
this.speedbrakesNotTo.set(this.flightPhase1211.get() && speedbrakesNotInToPos);
this.speedbrakesConfigAural.set(
@@ -3283,7 +3294,7 @@ export class FwsCore {
);
// taxi in flap 0 one minute check
- this.taxiInFlap0Check.write(this.slatFlapSelectionS0F0 && this.fwcFlightPhase.get() == 11, deltaTime);
+ this.taxiInFlap0Check.write(this.slatFlapSelectionS0F0 && this.flightPhase.get() == 11, deltaTime);
this.flapsMcduDisagree.set(
(flapsMcduPos1Disagree || flapsMcduPos2Disagree || flapsMcduPos3Disagree) &&
@@ -3303,7 +3314,7 @@ export class FwsCore {
const pitchConfigInPhase3or4or5 = this.flightPhase345.get() && pitchConfig;
this.pitchConfigInPhase3or4or5Sr.write(
pitchConfigInPhase3or4or5,
- phase6 || this.fwcFlightPhase.get() === 7 || !pitchConfig,
+ phase6 || this.flightPhase.get() === 7 || !pitchConfig,
);
this.pitchTrimNotToAudio.set(pitchConfigTestInPhase1211 || pitchConfigInPhase3or4or5);
this.pitchTrimNotToWarning.set(pitchConfigTestInPhase1211 || this.pitchConfigInPhase3or4or5Sr.read());
@@ -3349,7 +3360,7 @@ export class FwsCore {
(adr1PressureAltitude.valueOr(0) >= 22000 ||
adr2PressureAltitude.valueOr(0) >= 22000 ||
adr3PressureAltitude.valueOr(0) >= 22000) &&
- this.fwcFlightPhase.get() === 8 &&
+ this.flightPhase.get() === 8 &&
!this.slatFlapSelectionS0F0,
);
@@ -3361,13 +3372,13 @@ export class FwsCore {
deltaTime,
);
this.speedBrakeCaution1Confirm.write(
- this.fwcFlightPhase.get() === 8 &&
+ this.flightPhase.get() === 8 &&
this.speedBrakeCommand50sConfirm.read() &&
!this.engAboveIdleWithSpeedBrakeConfirm.read(),
deltaTime,
);
const speedBrakeCaution1 = this.speedBrakeCaution1Confirm.read();
- const speedBrakeCaution2 = this.fwcFlightPhase.get() === 9 && this.speedBrakeCommand5sConfirm.read();
+ const speedBrakeCaution2 = this.flightPhase.get() === 9 && this.speedBrakeCommand5sConfirm.read();
// FIXME FCU does not provide the bit, so we synthesize it
const apVerticalMode = SimVar.GetSimVarValue('L:A32NX_FMA_VERTICAL_MODE', 'number');
const apTcasRaNoseUp =
@@ -3377,7 +3388,7 @@ export class FwsCore {
this.apTcasRaNoseUpConfirm.write(apTcasRaNoseUp, deltaTime);
this.speedBrakeCaution3Confirm.write(
this.speedBrakeCommand.get() &&
- this.fwcFlightPhase.get() === 8 &&
+ this.flightPhase.get() === 8 &&
oneEngineAboveMinPower &&
this.apTcasRaNoseUpConfirm.read(),
deltaTime,
@@ -3410,7 +3421,7 @@ export class FwsCore {
const lgDown =
this.lgciu1DiscreteWord1.bitValueOr(29, false) ||
(this.lgciu2DiscreteWord1.bitValueOr(29, false) && mainGearDownlocked);
- this.phase104s5Trigger.write(this.fwcFlightPhase.get() === 10, deltaTime);
+ this.phase104s5Trigger.write(this.flightPhase.get() === 10, deltaTime);
this.groundSpoiler5sDelayed.write(
fcdc1DiscreteWord4.bitValueOr(27, false) || fcdc2DiscreteWord4.bitValueOr(27, false),
deltaTime,
@@ -3433,7 +3444,7 @@ export class FwsCore {
);
// l/g gear not down
- const fwcFlightPhase = this.fwcFlightPhase.get();
+ const fwcFlightPhase = this.flightPhase.get();
const flightPhase4567 =
fwcFlightPhase === 4 || fwcFlightPhase === 5 || fwcFlightPhase === 6 || fwcFlightPhase === 7;
const flightPhase8 = fwcFlightPhase === 8;
@@ -3444,7 +3455,7 @@ export class FwsCore {
this.radioHeight3.valueOr(Infinity),
) < 750;
const altInhibit =
- (pressureAltitude ?? 0) > 18500 &&
+ (this.adrPressureAltitude.get() ?? 0) > 18500 &&
!this.radioHeight1.isNoComputedData() &&
!this.radioHeight1.isNormalOperation() &&
!this.radioHeight2.isNoComputedData() &&
@@ -3753,7 +3764,7 @@ export class FwsCore {
(this.engine1State.get() === 0 && this.engine2State.get() === 0)),
);
- const flightPhase = this.fwcFlightPhase.get();
+ const flightPhase = this.flightPhase.get();
let tempMemoArrayLeft: string[] = [];
let tempMemoArrayRight: string[] = [];
const allFailureKeys: string[] = [];
@@ -3938,7 +3949,7 @@ export class FwsCore {
}
if (value.auralWarning?.get() === FwcAuralWarning.CavalryCharge) {
- this.auralCavalryChargeActive.set(true);
+ this.soundManager.enqueueSound('cavalryChargeCont');
}
}
@@ -4083,26 +4094,17 @@ export class FwsCore {
!ewdLimitationsApprLdgKeys.length,
);
- // This does not consider interrupting c-chord, priority of synthetic voice etc.
- const chimeRequested = this.auralSingleChimePending || this.requestSingleChimeFromAThrOff;
- if (
- chimeRequested &&
- !this.auralCrcActive.get() &&
- !this.auralCavalryChargeActive.get() &&
- !this.auralSingleChimeInhibitTimer.isPending()
- ) {
+ const chimeRequested =
+ (this.auralSingleChimePending || this.requestSingleChimeFromAThrOff) && !this.auralCrcActive.get();
+ if (chimeRequested && !this.auralSingleChimeInhibitTimer.isPending()) {
this.auralSingleChimePending = false;
this.requestSingleChimeFromAThrOff = false;
- SimVar.SetSimVarValue('L:A32NX_FWC_SC', 'bool', true);
+ this.soundManager.enqueueSound('singleChime');
// there can only be one SC per 2 seconds, non-cumulative, so clear any pending ones at the end of that inhibit period
this.auralSingleChimeInhibitTimer.schedule(
() => (this.auralSingleChimePending = false),
FwsCore.AURAL_SC_INHIBIT_TIME,
);
- this.auralSingleChimePlayingTimer.schedule(
- () => SimVar.SetSimVarValue('L:A32NX_FWC_SC', 'bool', false),
- FwsCore.AURAL_SC_PLAY_TIME,
- );
}
this.normalChecklists.update();
@@ -4126,7 +4128,7 @@ export class FwsCore {
const w = Arinc429Word.fromSimVarValue('L:A32NX_ROW_ROP_WORD_1');
// ROW
- SimVar.SetSimVarValue('L:A32NX_AUDIO_ROW_RWY_TOO_SHORT', 'bool', w.bitValueOr(15, false));
+ this.soundManager.handleSoundCondition('runwayTooShort', w.bitValueOr(15, false));
// ROP
// MAX BRAKING, only for manual braking, if maximum pedal braking is not applied
@@ -4134,20 +4136,19 @@ export class FwsCore {
SimVar.GetSimVarValue('L:A32NX_LEFT_BRAKE_PEDAL_INPUT', 'number') > 90 ||
SimVar.GetSimVarValue('L:A32NX_RIGHT_BRAKE_PEDAL_INPUT', 'number') > 90;
const maxBraking = w.bitValueOr(13, false) && !maxBrakingSet;
- SimVar.SetSimVarValue('L:A32NX_AUDIO_ROP_MAX_BRAKING', 'bool', maxBraking);
+ this.soundManager.handleSoundCondition('brakeMaxBraking', 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.bitValueOr(12, false) || w.bitValueOr(13, false)) && !maxReverseSet;
- SimVar.SetSimVarValue('L:A32NX_AUDIO_ROW_SET_MAX_REVERSE', 'bool', !maxBraking && maxReverse);
+ this.soundManager.handleSoundCondition('setMaxReverse', !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',
+ this.soundManager.handleSoundCondition(
+ 'keepMaxReverse',
ias <= 80 && ias > 4 && (w.bitValueOr(12, false) || w.bitValueOr(13, false)),
);
}
diff --git a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsFlightPhases.ts b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsFlightPhases.ts
new file mode 100644
index 00000000000..ef8066cbcd8
--- /dev/null
+++ b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsFlightPhases.ts
@@ -0,0 +1,534 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable camelcase */
+import {
+ Arinc429Register,
+ NXLogicConfirmNode,
+ NXLogicMemoryNode,
+ NXLogicTriggeredMonostableNode,
+} from '@flybywiresim/fbw-sdk';
+import { FwsCore } from 'systems-host/systems/FlightWarningSystem/FwsCore';
+
+export enum FwcFlightPhase {
+ ElecPwr = 1,
+ FirstEngineStarted = 2,
+ SecondEngineTakeOffPower = 3,
+ AtOrAboveEightyKnots = 4,
+ AtOrAboveV1 = 5,
+ LiftOff = 6,
+ AtOrAbove400Feet = 7,
+ AtOrAbove1500FeetTo800Feet = 8,
+ AtOrBelow800Feet = 9,
+ TouchDown = 10,
+ AtOrBelowEightyKnots = 11,
+ EnginesShutdown = 12,
+}
+
+// FIXME use subjects from FwsCore
+/**
+ * This nearly a 1:1 port from the A32NX's FWC serves as temporary replacement, until a more sophisticated system simulation is in place.
+ */
+export class FwsFlightPhases {
+ toConfigTest: boolean;
+
+ ldgMemo: boolean;
+
+ toMemo: boolean;
+
+ gndMemo: NXLogicConfirmNode;
+
+ oneEngineRunningConf: NXLogicConfirmNode;
+
+ speedAbove80KtsMemo: NXLogicMemoryNode;
+
+ speedAboveV1Memo: NXLogicMemoryNode;
+
+ mctMemo: NXLogicConfirmNode;
+
+ firePBOutConf: NXLogicConfirmNode;
+
+ firePBOutMemo: NXLogicTriggeredMonostableNode;
+
+ firePBClear12: NXLogicMemoryNode;
+
+ phase112Memo: NXLogicTriggeredMonostableNode;
+
+ phase10GroundMemo: NXLogicTriggeredMonostableNode;
+
+ ac80KtsMemo: NXLogicTriggeredMonostableNode;
+
+ prevPhase11InvertMemo: NXLogicTriggeredMonostableNode;
+
+ twoEnginesTOPowerInvertMemo: NXLogicTriggeredMonostableNode;
+
+ phase9Nvm: NXLogicMemoryNode;
+
+ prevPhase11: boolean;
+
+ groundImmediateMemo: NXLogicTriggeredMonostableNode;
+
+ phase6Memo: NXLogicTriggeredMonostableNode;
+
+ phase7Memo: NXLogicTriggeredMonostableNode;
+
+ phase89Memo: NXLogicTriggeredMonostableNode;
+
+ memoTo_conf01: NXLogicConfirmNode;
+
+ memoTo_memo: NXLogicMemoryNode;
+
+ memoLdgMemo_conf01: NXLogicConfirmNode;
+
+ memoLdgMemo_inhibit: NXLogicMemoryNode;
+
+ memoLdgMemo_conf02: NXLogicConfirmNode;
+
+ memoLdgMemo_below2000ft: NXLogicMemoryNode;
+
+ memoToInhibit_conf01: NXLogicConfirmNode;
+
+ memoLdgInhibit_conf01: NXLogicConfirmNode;
+
+ previousTargetAltitude: number;
+
+ _wasBelowThreshold: boolean;
+
+ _wasAboveThreshold: boolean;
+
+ _wasInRange: boolean;
+
+ _wasReach200ft: boolean;
+
+ _cChordShortWasTriggered: boolean;
+
+ aircraft: Aircraft;
+
+ private readonly adrAltitude = Arinc429Register.empty();
+
+ constructor(private fws: FwsCore) {
+ // momentary
+ this.toConfigTest = null;
+
+ // persistent
+ this.ldgMemo = null;
+ this.toMemo = null;
+
+ this.gndMemo = new NXLogicConfirmNode(1);
+
+ this.oneEngineRunningConf = new NXLogicConfirmNode(30);
+
+ this.speedAbove80KtsMemo = new NXLogicMemoryNode(true);
+
+ this.speedAboveV1Memo = new NXLogicMemoryNode();
+
+ this.mctMemo = new NXLogicConfirmNode(60, false);
+
+ this.firePBOutConf = new NXLogicConfirmNode(0.2);
+ this.firePBOutMemo = new NXLogicTriggeredMonostableNode(2);
+ this.firePBClear12 = new NXLogicMemoryNode(false);
+ this.phase112Memo = new NXLogicTriggeredMonostableNode(300);
+ this.phase10GroundMemo = new NXLogicTriggeredMonostableNode(2);
+ this.ac80KtsMemo = new NXLogicTriggeredMonostableNode(2);
+ this.prevPhase11InvertMemo = new NXLogicTriggeredMonostableNode(3, false);
+ this.twoEnginesTOPowerInvertMemo = new NXLogicTriggeredMonostableNode(1, false);
+ this.phase9Nvm = new NXLogicMemoryNode(true, true);
+ this.prevPhase11 = false;
+
+ this.groundImmediateMemo = new NXLogicTriggeredMonostableNode(2);
+ this.phase6Memo = new NXLogicTriggeredMonostableNode(15);
+ this.phase7Memo = new NXLogicTriggeredMonostableNode(120);
+ this.phase89Memo = new NXLogicTriggeredMonostableNode(180);
+
+ this.memoTo_conf01 = new NXLogicConfirmNode(120, true);
+ this.memoTo_memo = new NXLogicMemoryNode(false);
+
+ this.memoLdgMemo_conf01 = new NXLogicConfirmNode(1, true);
+ this.memoLdgMemo_inhibit = new NXLogicMemoryNode(false);
+ this.memoLdgMemo_conf02 = new NXLogicConfirmNode(10, true);
+ this.memoLdgMemo_below2000ft = new NXLogicMemoryNode(true);
+
+ this.memoToInhibit_conf01 = new NXLogicConfirmNode(3, true);
+
+ this.memoLdgInhibit_conf01 = new NXLogicConfirmNode(3, true);
+
+ // altitude warning
+ this.previousTargetAltitude = NaN;
+ this._wasBelowThreshold = false;
+ this._wasAboveThreshold = false;
+ this._wasInRange = false;
+ this._wasReach200ft = false;
+ }
+
+ update(deltaTime: number) {
+ this._updateFlightPhase(deltaTime);
+ this._updateTakeoffMemo(deltaTime);
+ this._updateLandingMemo(deltaTime);
+ this._updateAltitudeWarning();
+ }
+
+ _updateFlightPhase(_deltaTime: number) {
+ const raHeight1Invalid = this.fws.radioHeight1.isFailureWarning() || this.fws.radioHeight1.isNoComputedData();
+ const raHeight2Invalid = this.fws.radioHeight2.isFailureWarning() || this.fws.radioHeight2.isNoComputedData();
+ let radioHeight;
+ if (raHeight1Invalid) {
+ if (raHeight2Invalid) {
+ radioHeight = this.fws.radioHeight3;
+ } else {
+ radioHeight = this.fws.radioHeight2;
+ }
+ } else {
+ radioHeight = this.fws.radioHeight1;
+ }
+ // TODO find a better source for the following value ("core speed at or above idle")
+ // Note that N1 starts below idle on spawn on the runway, so this should be below 16 to not jump back to phase 1
+ const oneEngRunning =
+ this.fws.N1Eng1.get() > 15 ||
+ this.fws.N1Eng2.get() > 15 ||
+ this.fws.N1Eng3.get() > 15 ||
+ this.fws.N1Eng4.get() > 15;
+ const oneEngineRunning = this.oneEngineRunningConf.write(oneEngRunning, _deltaTime);
+ const noEngineRunning = !oneEngineRunning;
+ const hFail =
+ this.fws.radioHeight1.isFailureWarning() &&
+ this.fws.radioHeight2.isFailureWarning() &&
+ this.fws.radioHeight3.isFailureWarning();
+ const adcTestInhib = false;
+
+ const groundImmediate = Simplane.getIsGrounded();
+ const ground = this.gndMemo.write(groundImmediate, _deltaTime);
+
+ const ias = this.fws.computedAirSpeedToNearest2.get();
+ const acSpeedAbove80kts = this.speedAbove80KtsMemo.write(ias > 83, ias < 77);
+
+ const v1 = SimVar.GetSimVarValue('L:AIRLINER_V1_SPEED', 'knots');
+ let acAboveV1: boolean;
+ if (v1) {
+ acAboveV1 = this.speedAboveV1Memo.write(ias > v1 + 3, ias < v1 - 3);
+ } else {
+ acAboveV1 = false;
+ }
+
+ const hAbv1500 = radioHeight.isNoComputedData() || radioHeight.value > 1500;
+ const hAbv800 = radioHeight.isNoComputedData() || radioHeight.value > 800;
+ const hAbv400 = radioHeight.isNoComputedData() || radioHeight.value > 400;
+
+ const eng1TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:1', 'number');
+ const eng1TLAFTO = SimVar.GetSimVarValue('L:A32NX_AIRLINER_TO_FLEX_TEMP', 'number') !== 0; // is a flex temp is set?
+ const eng1MCT = eng1TLA > 33.3 && eng1TLA < 36.7;
+ const eng1TLAFullPwr = eng1TLA > 43.3;
+ const eng1MCL = eng1TLA > 22.9;
+ const eng1SupMCT = !(eng1TLA < 36.7);
+
+ const eng2TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:2', 'number');
+ const eng2TLAFTO = eng1TLAFTO; // until we have proper FADECs
+ const eng2MCT = eng2TLA > 33.3 && eng2TLA < 36.7;
+ const eng2TLAFullPwr = eng2TLA > 43.3;
+ const eng2MCL = eng2TLA > 22.9;
+ const eng2SupMCT = !(eng2TLA < 36.7);
+
+ const eng3TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:3', 'number');
+ const eng3TLAFTO = eng1TLAFTO; // until we have proper FADECs
+ const eng3MCT = eng3TLA > 33.3 && eng3TLA < 36.7;
+ const eng3TLAFullPwr = eng3TLA > 43.3;
+ const eng3MCL = eng3TLA > 22.9;
+ const eng3SupMCT = !(eng3TLA < 36.7);
+
+ const eng4TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:4', 'number');
+ const eng4TLAFTO = eng1TLAFTO; // until we have proper FADECs
+ const eng4MCT = eng4TLA > 33.3 && eng4TLA < 36.7;
+ const eng4TLAFullPwr = eng3TLA > 43.3;
+ const eng4MCL = eng4TLA > 22.9;
+ const eng4SupMCT = !(eng4TLA < 36.7);
+
+ const twoEnginesMcl = [eng1MCL, eng2MCL, eng3MCL, eng4MCL].filter(Boolean).length >= 2;
+ const eng1TOPowerSignal = (eng1TLAFTO && eng1MCT) || eng1TLAFullPwr || eng1SupMCT;
+ const eng2TOPowerSignal = (eng2TLAFTO && eng2MCT) || eng2TLAFullPwr || eng2SupMCT;
+ const eng3TOPowerSignal = (eng3TLAFTO && eng3MCT) || eng3TLAFullPwr || eng3SupMCT;
+ const eng4TOPowerSignal = (eng4TLAFTO && eng4MCT) || eng4TLAFullPwr || eng4SupMCT;
+
+ const twoEnginesTOPowerSignal =
+ [eng1TOPowerSignal, eng2TOPowerSignal, eng3TOPowerSignal, eng4TOPowerSignal].filter(Boolean).length >= 2;
+
+ const twoEnginesTOPower =
+ twoEnginesTOPowerSignal ||
+ (this.mctMemo.write(twoEnginesTOPowerSignal, _deltaTime) && !hAbv1500 && twoEnginesMcl);
+
+ const eng1FirePbMemo = this.firePBOutMemo.write(
+ this.firePBOutConf.write(this.fws.fireButtonEng1.get(), _deltaTime),
+ _deltaTime,
+ );
+ const resetFirePbClear12 = eng1FirePbMemo && ground;
+
+ const phase10 =
+ (this.phase10GroundMemo.write(groundImmediate, _deltaTime) || groundImmediate) &&
+ !twoEnginesTOPower &&
+ acSpeedAbove80kts;
+
+ const phase345Cond = ground && twoEnginesTOPower;
+ const phase3 = !acSpeedAbove80kts && oneEngRunning && phase345Cond;
+ const phase4 = acSpeedAbove80kts && phase345Cond && !acAboveV1;
+ const phase5 = acSpeedAbove80kts && phase345Cond && acAboveV1;
+
+ const setPhase11Nvm = phase3 || phase10;
+ const resetPhase11Nvm =
+ (!this.ac80KtsMemo.write(!acSpeedAbove80kts, _deltaTime) &&
+ ((ground && this.prevPhase11InvertMemo.write(this.prevPhase11, _deltaTime)) ||
+ resetFirePbClear12 ||
+ (ground && this.twoEnginesTOPowerInvertMemo.write(twoEnginesTOPower, _deltaTime))) &&
+ !this.prevPhase11) ||
+ adcTestInhib;
+ const phase11Nvm = this.phase9Nvm.write(setPhase11Nvm, resetPhase11Nvm); // S* / R (NVM)
+ const phase211Cond = ground && !twoEnginesTOPower && !acSpeedAbove80kts;
+ const phase11 = oneEngRunning && phase11Nvm && phase211Cond;
+ const phase2 = phase211Cond && !phase11Nvm && oneEngRunning;
+
+ const phase112MemoA = this.firePBClear12.write(phase11, resetFirePbClear12); // S / R*
+ const phase112Cond = !phase11 && noEngineRunning && groundImmediate;
+ const phase112Memo = this.phase112Memo.write(phase112MemoA && phase112Cond, _deltaTime);
+ const phase1 = phase112Cond && !phase112Memo;
+ const phase12 = phase112Cond && phase112Memo;
+
+ this.prevPhase11 = phase11;
+
+ const ground2sMemorized = this.groundImmediateMemo.write(groundImmediate, _deltaTime) || groundImmediate;
+
+ const phase6Cond = !hAbv400 && twoEnginesTOPower && !hFail && !ground2sMemorized;
+ const phase6 = this.phase6Memo.write(phase6Cond, _deltaTime) && phase6Cond;
+
+ const phase7Cond = !phase6 && !hAbv1500 && twoEnginesTOPower && !hFail && !ground2sMemorized;
+ const phase7 = this.phase7Memo.write(phase7Cond, _deltaTime) && phase7Cond;
+
+ const phase89Cond = !ground2sMemorized && !hFail && !twoEnginesTOPower && !hAbv1500 && !hAbv800;
+ const phase89Memo = this.phase89Memo.write(phase89Cond, _deltaTime) && phase89Cond;
+
+ const phase8 = !phase7 && !ground2sMemorized && !phase89Memo;
+ const phase9 = phase89Memo && !phase10;
+
+ // consolidate into single variable (just to be safe)
+ const phases = [phase1, phase2, phase3, phase4, phase5, phase6, phase7, phase8, phase9, phase10, phase11, phase12];
+
+ if (this.fws.flightPhase.get() === null && phases.indexOf(true) !== -1) {
+ // if we aren't initialized, just grab the first one that is valid
+ this.fws.flightPhase.set(phases.indexOf(true) + 1);
+ console.log(`FWC flight phase: ${this.fws.flightPhase.get()}`);
+ return;
+ }
+
+ const activePhases = phases
+ .map((x, i) => [x ? 1 : 0, i + 1])
+ .filter((y) => y[0] === 1)
+ .map((z) => z[1]);
+
+ // the usual and easy case: only one flight phase is valid
+ if (activePhases.length === 1) {
+ if (activePhases[0] !== this.fws.flightPhase.get()) {
+ console.log(`FWC flight phase: ${this.fws.flightPhase.get()} => ${activePhases[0]}`);
+ this.fws.flightPhase.set(activePhases[0]);
+ }
+ return;
+ }
+
+ // the mixed case => warn
+ if (activePhases.length > 1) {
+ if (activePhases.indexOf(this.fws.flightPhase.get()) !== -1) {
+ // if the currently active one is present, keep it
+ return;
+ }
+ // pick the earliest one
+ this.fws.flightPhase.set(activePhases[0]);
+ return;
+ }
+
+ // otherwise, no flight phase is valid => warn
+ if (this.fws.flightPhase.get() === null) {
+ this.fws.flightPhase.set(null);
+ }
+ }
+
+ _updateTakeoffMemo(_deltaTime: number) {
+ const setFlightPhaseMemo = this.fws.flightPhase.get() === FwcFlightPhase.FirstEngineStarted && this.toConfigTest;
+ const resetFlightPhaseMemo =
+ this.fws.flightPhase.get() === FwcFlightPhase.EnginesShutdown ||
+ this.fws.flightPhase.get() === FwcFlightPhase.SecondEngineTakeOffPower ||
+ this.fws.flightPhase.get() === FwcFlightPhase.ElecPwr ||
+ this.fws.flightPhase.get() === FwcFlightPhase.AtOrAbove1500FeetTo800Feet;
+ const flightPhaseMemo = this.memoTo_memo.write(setFlightPhaseMemo, resetFlightPhaseMemo);
+
+ this.fws.engine1Running.get();
+ const toTimerElapsed = this.memoTo_conf01.write(
+ this.fws.engine1Running.get() &&
+ this.fws.engine2Running.get() &&
+ this.fws.engine3Running.get() &&
+ this.fws.engine4Running.get(),
+ _deltaTime,
+ );
+
+ this.toMemo =
+ flightPhaseMemo || (this.fws.flightPhase.get() === FwcFlightPhase.FirstEngineStarted && toTimerElapsed);
+ SimVar.SetSimVarValue('L:A32NX_FWC_TOMEMO', 'Bool', this.toMemo);
+ }
+
+ _updateLandingMemo(_deltaTime: number) {
+ const radioHeight1Invalid = this.fws.radioHeight1.isFailureWarning() || this.fws.radioHeight1.isNoComputedData();
+ const radioHeight2Invalid = this.fws.radioHeight2.isFailureWarning() || this.fws.radioHeight2.isNoComputedData();
+ const radioHeight3Invalid = this.fws.radioHeight3.isFailureWarning() || this.fws.radioHeight3.isNoComputedData();
+ const gearDownlocked = SimVar.GetSimVarValue('GEAR TOTAL PCT EXTENDED', 'percent') > 0.95;
+
+ const setBelow2000ft =
+ (this.fws.radioHeight1.value < 2000 && !radioHeight1Invalid) ||
+ (this.fws.radioHeight2.value < 2000 && !radioHeight2Invalid) ||
+ (this.fws.radioHeight3.value < 2000 && !radioHeight3Invalid);
+ const resetBelow2000ft =
+ (this.fws.radioHeight1.value > 2200 || radioHeight1Invalid) &&
+ (this.fws.radioHeight2.value > 2200 || radioHeight2Invalid) &&
+ (this.fws.radioHeight3.value > 2200 || radioHeight3Invalid);
+ const memo2 = this.memoLdgMemo_below2000ft.write(setBelow2000ft, resetBelow2000ft);
+
+ const setInhibitMemo = this.memoLdgMemo_conf01.write(
+ resetBelow2000ft && !radioHeight1Invalid && !radioHeight2Invalid && !radioHeight3Invalid,
+ _deltaTime,
+ );
+ const resetInhibitMemo = !(
+ this.fws.flightPhase.get() === FwcFlightPhase.AtOrBelow800Feet ||
+ this.fws.flightPhase.get() === FwcFlightPhase.TouchDown ||
+ this.fws.flightPhase.get() === FwcFlightPhase.AtOrAbove1500FeetTo800Feet
+ );
+ const memo1 = this.memoLdgMemo_inhibit.write(setInhibitMemo, resetInhibitMemo);
+
+ const showInApproach = memo1 && memo2 && this.fws.flightPhase.get() === FwcFlightPhase.AtOrAbove1500FeetTo800Feet;
+
+ const invalidRadioMemo = this.memoLdgMemo_conf02.write(
+ radioHeight1Invalid &&
+ radioHeight2Invalid &&
+ radioHeight3Invalid &&
+ gearDownlocked &&
+ this.fws.flightPhase.get() === FwcFlightPhase.AtOrAbove1500FeetTo800Feet,
+ _deltaTime,
+ );
+
+ this.ldgMemo =
+ showInApproach ||
+ invalidRadioMemo ||
+ this.fws.flightPhase.get() === FwcFlightPhase.TouchDown ||
+ this.fws.flightPhase.get() === FwcFlightPhase.AtOrBelow800Feet;
+ SimVar.SetSimVarValue('L:A32NX_FWC_LDGMEMO', 'Bool', this.ldgMemo);
+ }
+
+ _updateAltitudeWarning() {
+ const warningPressed =
+ !!SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_L', 'Bool') ||
+ !!SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_R', 'Bool');
+ if (warningPressed === true) {
+ this._wasBelowThreshold = false;
+ this._wasAboveThreshold = false;
+ this._wasInRange = false;
+ this._cChordShortWasTriggered = false;
+ this.fws.soundManager.dequeueSound('cChordOnce');
+ this.fws.soundManager.dequeueSound('cChordCont');
+ return;
+ }
+
+ if (Simplane.getIsGrounded()) {
+ this.fws.soundManager.dequeueSound('cChordCont');
+ }
+
+ // Use FCU displayed value
+ const currentAltitudeConstraint = SimVar.GetSimVarValue('L:A32NX_FG_ALTITUDE_CONSTRAINT', 'feet');
+ const currentFCUAltitude = SimVar.GetSimVarValue('AUTOPILOT ALTITUDE LOCK VAR:3', 'feet');
+ const targetAltitude =
+ currentAltitudeConstraint && !this.hasAltitudeConstraint() ? currentAltitudeConstraint : currentFCUAltitude;
+
+ // Exit when selected altitude is being changed
+ if (this.previousTargetAltitude !== targetAltitude) {
+ this.previousTargetAltitude = targetAltitude;
+ this._wasBelowThreshold = false;
+ this._wasAboveThreshold = false;
+ this._wasInRange = false;
+ this._wasReach200ft = false;
+ this._cChordShortWasTriggered = false;
+ this.fws.soundManager.dequeueSound('cChordOnce');
+ this.fws.soundManager.dequeueSound('cChordCont');
+ return;
+ }
+
+ // Exit when:
+ // - Landing gear down & slats extended
+ // - Glide slope captured
+ // - Landing locked down
+
+ const landingGearIsDown =
+ SimVar.GetSimVarValue('L:A32NX_FLAPS_HANDLE_INDEX', 'Enum') >= 1 &&
+ SimVar.GetSimVarValue('L:A32NX_GEAR_HANDLE_POSITION', 'Percent over 100') > 0.5;
+ const verticalMode = SimVar.GetSimVarValue('L:A32NX_FMA_VERTICAL_MODE', 'Number');
+ const glideSlopeCaptured = verticalMode >= 30 && verticalMode <= 34;
+ const landingGearIsLockedDown = SimVar.GetSimVarValue('GEAR POSITION:0', 'Enum') > 0.9;
+ const isTcasResolutionAdvisoryActive = SimVar.GetSimVarValue('L:A32NX_TCAS_STATE', 'Enum') > 1;
+ if (landingGearIsDown || glideSlopeCaptured || landingGearIsLockedDown || isTcasResolutionAdvisoryActive) {
+ this._wasBelowThreshold = false;
+ this._wasAboveThreshold = false;
+ this._wasInRange = false;
+ this._wasReach200ft = false;
+ this._cChordShortWasTriggered = false;
+ this.fws.soundManager.dequeueSound('cChordOnce');
+ this.fws.soundManager.dequeueSound('cChordCont');
+ return;
+ }
+
+ // FIXME better altitude selection
+ this.adrAltitude.setFromSimVar(`L:A32NX_ADIRS_ADR_1_BARO_CORRECTED_ALTITUDE_${this.fws.fwsNumber}`);
+ if (!this.adrAltitude.isNormalOperation()) {
+ return;
+ }
+ const delta = Math.abs(this.adrAltitude.value - targetAltitude);
+
+ if (delta < 200) {
+ this._wasBelowThreshold = true;
+ this._wasAboveThreshold = false;
+ this._wasReach200ft = true;
+ }
+ if (delta > 750) {
+ this._wasAboveThreshold = true;
+ this._wasBelowThreshold = false;
+ this._cChordShortWasTriggered = false;
+ }
+ if (delta >= 200 && delta <= 750) {
+ this._wasInRange = true;
+ }
+
+ const apEngaged =
+ SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_1_ACTIVE', 'Bool') ||
+ SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_2_ACTIVE', 'Bool');
+ if (this._wasBelowThreshold && this._wasReach200ft) {
+ if (delta >= 200) {
+ this.fws.soundManager.enqueueSound('cChordCont');
+ } else {
+ this.fws.soundManager.dequeueSound('cChordCont');
+ }
+ } else if (
+ this._wasAboveThreshold &&
+ delta <= 750 &&
+ !this._wasReach200ft &&
+ !this._cChordShortWasTriggered &&
+ !apEngaged
+ ) {
+ this._cChordShortWasTriggered = true;
+ this.fws.soundManager.dequeueSound('cChordCont');
+ this.fws.soundManager.enqueueSound('cChordOnce');
+ } else if (delta > 750 && this._wasInRange && !this._wasReach200ft) {
+ if (delta > 750) {
+ this.fws.soundManager.enqueueSound('cChordCont');
+ } else {
+ this.fws.soundManager.dequeueSound('cChordCont');
+ }
+ }
+ }
+
+ hasAltitudeConstraint() {
+ if (
+ Simplane.getAutoPilotAltitudeManaged() &&
+ SimVar.GetSimVarValue('L:AP_CURRENT_TARGET_ALTITUDE_IS_CONSTRAINT', 'number') !== 0
+ ) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsMemos.ts b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsMemos.ts
index c4e104543f1..815d4d2bd8e 100644
--- a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsMemos.ts
+++ b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsMemos.ts
@@ -55,7 +55,7 @@ export class FwsMemos {
simVarIsActive: MappedSubject.create(
([speedBrakeCommand, fwcFlightPhase]) => speedBrakeCommand && ![1, 8, 9, 10].includes(fwcFlightPhase),
this.fws.speedBrakeCommand,
- this.fws.fwcFlightPhase,
+ this.fws.flightPhase,
),
whichCodeToReturn: () => [this.fws.amberSpeedBrake.get() ? 1 : 0],
codesToReturn: ['000006001', '000006002'],
@@ -353,7 +353,7 @@ export class FwsMemos {
// RAT OUT
flightPhaseInhib: [],
simVarIsActive: this.fws.ratDeployed.map((v) => v > 0),
- whichCodeToReturn: () => [[1, 2].includes(this.fws.fwcFlightPhase.get()) ? 0 : 1],
+ whichCodeToReturn: () => [[1, 2].includes(this.fws.flightPhase.get()) ? 0 : 1],
codesToReturn: ['242000001', '242000002'],
memoInhibit: () => false,
failure: 0,
diff --git a/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsSoundManager.ts b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsSoundManager.ts
new file mode 100644
index 00000000000..36696baec36
--- /dev/null
+++ b/fbw-a380x/src/systems/systems-host/systems/FlightWarningSystem/FwsSoundManager.ts
@@ -0,0 +1,453 @@
+// Copyright (c) 2021-2024 FlyByWire Simulations
+//
+// SPDX-License-Identifier: GPL-3.0
+
+import { EventBus, SimVarValueType, Subscribable } from '@microsoft/msfs-sdk';
+
+export interface FwsSoundManagerControlEvents {
+ enqueueSound: string;
+ dequeueSound: string;
+}
+
+// Synthetic voice has priority over everything, SC is least important
+enum FwsAuralWarningType {
+ SingleChime,
+ AuralWarning,
+ SyntheticVoice,
+}
+
+export enum FwsAuralVolume {
+ Full, // 0 dB
+ Attenuated, // -6dB
+ Silent, // -200 dB
+}
+
+interface FwsAural {
+ /** The LocalVar which triggers the playback. Not prefixed by L: here. Either localVarName or wwiseEventName has to be defined. */
+ localVarName?: string;
+ /** The Wwise event which triggers the playback. Either localVarName or wwiseEventName has to be defined. */
+ wwiseEventName?: string;
+ /** Sounds are queued based on type and priority (highest priority = gets queued first within same type) */
+ priority: number;
+ type: FwsAuralWarningType;
+ /** Length of audio in seconds, if non-repetitive */
+ length?: number;
+ /** If this is set, this sound is repeated periodically with the specified pause in seconds */
+ periodicWithPause?: number;
+ continuous?: boolean;
+}
+
+export const FwsAuralsList: Record = {
+ continuousRepetitiveChime: {
+ localVarName: 'A32NX_FWC_CRC',
+ priority: 5,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: true,
+ },
+ singleChime: {
+ localVarName: 'A32NX_FWC_SC',
+ length: 0.54,
+ priority: 0,
+ type: FwsAuralWarningType.SingleChime,
+ continuous: false,
+ },
+ cavalryChargeOnce: {
+ localVarName: 'A32NX_FWC_CAVALRY_CHARGE',
+ length: 0.9,
+ priority: 4,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ cavalryChargeCont: {
+ localVarName: 'A32NX_FWC_CAVALRY_CHARGE',
+ priority: 4,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: true,
+ },
+ tripleClick: {
+ localVarName: 'A32NX_FMA_TRIPLE_CLICK',
+ length: 0.62,
+ priority: 3,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ v1: {
+ localVarName: 'A32NX_AUDIO_V1_CALLOUT',
+ length: 1.3,
+ priority: 1,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ autoBrakeOff: {
+ localVarName: 'A32NX_AUDIO_AUTOBRAKE_OFF',
+ length: 1.5,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ runwayTooShort: {
+ localVarName: 'A32NX_AUDIO_ROW_RWY_TOO_SHORT',
+ length: 1.6,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: true,
+ },
+ keepMaxReverse: {
+ localVarName: 'A32NX_AUDIO_ROP_KEEP_MAX_REVERSE',
+ length: 1.4,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ setMaxReverse: {
+ localVarName: 'A32NX_AUDIO_ROW_SET_MAX_REVERSE',
+ length: 1.62,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: false,
+ },
+ brakeMaxBraking: {
+ localVarName: 'A32NX_AUDIO_ROP_MAX_BRAKING',
+ length: 3.1,
+ priority: 4,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: true,
+ },
+ stall: {
+ localVarName: 'A32NX_AUDIO_ROP_MAX_BRAKING',
+ length: 3.0,
+ priority: 5,
+ type: FwsAuralWarningType.SyntheticVoice,
+ continuous: true,
+ },
+ cChordOnce: {
+ localVarName: 'A32NX_ALT_DEVIATION',
+ length: 1.0,
+ priority: 3,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: false,
+ },
+ cChordCont: {
+ localVarName: 'A32NX_ALT_DEVIATION',
+ priority: 3,
+ type: FwsAuralWarningType.AuralWarning,
+ continuous: true,
+ },
+ // Altitude callouts
+ minimums: {
+ wwiseEventName: 'aural_minimumnew',
+ length: 0.67,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ hundred_above: {
+ wwiseEventName: 'aural_100above',
+ length: 0.72,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ retard: {
+ wwiseEventName: 'new_retard',
+ length: 0.9,
+ periodicWithPause: 0.2,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_2500: {
+ wwiseEventName: 'new_2500',
+ length: 1.1,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_2500b: {
+ wwiseEventName: 'new_2_500',
+ length: 1.047,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_2000: {
+ wwiseEventName: 'new_2000',
+ length: 0.72,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_1000: {
+ wwiseEventName: 'new_1000',
+ length: 0.9,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_500: {
+ wwiseEventName: 'new_500',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_400: {
+ wwiseEventName: 'new_400',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_300: {
+ wwiseEventName: 'new_300',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_200: {
+ wwiseEventName: 'new_200',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_100: {
+ wwiseEventName: 'new_100',
+ length: 0.6,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_90: {
+ wwiseEventName: '90_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_80: {
+ wwiseEventName: '80_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_70: {
+ wwiseEventName: '70_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_60: {
+ wwiseEventName: '60_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_50: {
+ wwiseEventName: '50_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_40: {
+ wwiseEventName: '40_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_30: {
+ wwiseEventName: '30_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_20: {
+ wwiseEventName: '20_380',
+ length: 0.4,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_10: {
+ wwiseEventName: '10_380',
+ length: 0.3,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+ alt_5: {
+ wwiseEventName: '5_380',
+ length: 0.3,
+ priority: 2,
+ type: FwsAuralWarningType.SyntheticVoice,
+ },
+};
+
+// FIXME Not all sounds are added to this yet (e.g. CIDS chimes), consider adding them in the future
+// Also, single chimes are not filtered (in RL only once every two seconds)
+export class FwsSoundManager {
+ private readonly soundQueue = new Set();
+
+ private singleChimesPending = 0;
+
+ private currentSoundPlaying: keyof typeof FwsAuralsList | null = null;
+
+ /** in seconds */
+ private currentSoundPlayTimeRemaining = 0;
+
+ constructor(
+ private bus: EventBus,
+ private startupCompleted: Subscribable,
+ ) {
+ // Stop all sounds
+ Object.values(FwsAuralsList).forEach((a) => {
+ if (a.localVarName) {
+ SimVar.SetSimVarValue(`L:${a.localVarName}`, SimVarValueType.Bool, false);
+ }
+ });
+
+ const sub = this.bus.getSubscriber();
+ sub.on('enqueueSound').handle((s) => this.enqueueSound(s));
+ sub.on('dequeueSound').handle((s) => this.dequeueSound(s));
+ }
+
+ /** Add sound to queue. Don't add if already playing */
+ enqueueSound(soundKey: keyof typeof FwsAuralsList) {
+ const sound = FwsAuralsList[soundKey];
+ if (!sound || this.currentSoundPlaying === soundKey) {
+ return;
+ }
+
+ if (sound.type === FwsAuralWarningType.SyntheticVoice || sound.type === FwsAuralWarningType.AuralWarning) {
+ this.soundQueue.add(soundKey);
+ } else if (sound.type === FwsAuralWarningType.SingleChime) {
+ this.singleChimesPending++;
+ }
+ }
+
+ /** Remove sound from queue, e.g. when condition doesn't apply anymore. If sound is currently playing, stops sound immediately */
+ dequeueSound(soundKey: keyof typeof FwsAuralsList) {
+ // Check if this sound is currently playing
+ if (this.currentSoundPlaying === soundKey && FwsAuralsList[this.currentSoundPlaying]?.continuous) {
+ this.stopCurrentSound();
+ }
+ this.soundQueue.delete(soundKey);
+ }
+
+ private stopCurrentSound() {
+ // Only LVar sounds which are continuous can be stopped
+ if (
+ this.currentSoundPlaying &&
+ FwsAuralsList[this.currentSoundPlaying].localVarName &&
+ FwsAuralsList[this.currentSoundPlaying]?.continuous
+ ) {
+ SimVar.SetSimVarValue(`L:${FwsAuralsList[this.currentSoundPlaying].localVarName}`, SimVarValueType.Bool, false);
+ this.currentSoundPlaying = null;
+ this.currentSoundPlayTimeRemaining = 0;
+ }
+ }
+
+ /**
+ * Convenience function for FWS: If condition true and sound not already playing, add to queue. If not, dequeue sound
+ * */
+ handleSoundCondition(soundKey: keyof typeof FwsAuralsList, condition: boolean) {
+ if (condition && this.currentSoundPlaying !== soundKey) {
+ this.enqueueSound(soundKey);
+ } else if (!condition) {
+ this.dequeueSound(soundKey);
+ }
+ }
+
+ /** This only has an effect on sounds defining WwiseRTPC behavior/var for volume */
+ setVolume(volume: FwsAuralVolume) {
+ SimVar.SetSimVarValue('L:A32NX_FWS_AUDIO_VOLUME', SimVarValueType.Enum, volume);
+ }
+
+ /** Play now, not to be called from the outside */
+ private playSound(soundKey: keyof typeof FwsAuralsList) {
+ const sound = FwsAuralsList[soundKey];
+ if (!sound) {
+ return;
+ }
+
+ if (sound.localVarName) {
+ SimVar.SetSimVarValue(`L:${sound.localVarName}`, SimVarValueType.Bool, true);
+ } else if (sound.wwiseEventName) {
+ Coherent.call('PLAY_INSTRUMENT_SOUND', sound.wwiseEventName);
+ }
+ this.currentSoundPlaying = soundKey;
+ this.currentSoundPlayTimeRemaining = sound.continuous ? Infinity : sound.length;
+ this.soundQueue.delete(soundKey);
+ }
+
+ /** Find most important sound from soundQueue and play */
+ private selectAndPlayMostImportantSound(): keyof typeof FwsAuralsList | null {
+ if (!this.startupCompleted.get()) {
+ return;
+ }
+
+ // Logic for scheduling new sounds: Take sound from soundQueue of most important type
+ // (SyntheticVoice > AuralWarning > SingleChime) with highest priority, and play it
+ let selectedSoundKey: keyof typeof FwsAuralsList | null = null;
+ this.soundQueue.forEach((sk) => {
+ const s = FwsAuralsList[sk];
+ if (
+ selectedSoundKey === null ||
+ s.type > FwsAuralsList[selectedSoundKey].type ||
+ (s.type === FwsAuralsList[selectedSoundKey].type && s.priority > FwsAuralsList[selectedSoundKey].priority)
+ ) {
+ selectedSoundKey = sk;
+ }
+ });
+
+ if (selectedSoundKey) {
+ this.playSound(selectedSoundKey);
+ return selectedSoundKey;
+ }
+
+ // See if single chimes are left
+ if (this.singleChimesPending) {
+ this.playSound('singleChime');
+ this.singleChimesPending--;
+ return 'singleChime';
+ }
+
+ // Ok, nothing to play
+ return null;
+ }
+
+ onUpdate(deltaTime: number) {
+ // Either wait for the current sound to finish, or schedule the next sound
+ if (this.currentSoundPlaying && this.currentSoundPlayTimeRemaining > 0) {
+ if (this.currentSoundPlayTimeRemaining - deltaTime / 1_000 > 0) {
+ // Wait for sound to be finished
+ this.currentSoundPlayTimeRemaining -= deltaTime / 1_000;
+ } else {
+ // Sound finishes in this cycle
+ if (FwsAuralsList[this.currentSoundPlaying].localVarName) {
+ SimVar.SetSimVarValue(
+ `L:${FwsAuralsList[this.currentSoundPlaying].localVarName}`,
+ SimVarValueType.Bool,
+ false,
+ );
+ }
+ this.currentSoundPlaying = null;
+ this.currentSoundPlayTimeRemaining = 0;
+ }
+
+ // Interrupt if sound with higher category is present in queue and current sound is continuous
+ let shouldInterrupt = false;
+ let rescheduleSound: keyof typeof FwsAuralsList | null = null;
+ this.soundQueue.forEach((sk) => {
+ const s = FwsAuralsList[sk];
+ if (
+ s &&
+ this.currentSoundPlaying &&
+ FwsAuralsList[this.currentSoundPlaying]?.continuous &&
+ s.type > FwsAuralsList[this.currentSoundPlaying].type
+ ) {
+ shouldInterrupt = true;
+ }
+ });
+
+ if (shouldInterrupt) {
+ if (this.currentSoundPlaying && FwsAuralsList[this.currentSoundPlaying]?.continuous) {
+ rescheduleSound = this.currentSoundPlaying;
+ this.stopCurrentSound();
+ if (rescheduleSound) {
+ this.enqueueSound(rescheduleSound);
+ }
+ }
+ }
+ } else {
+ // Play next sound
+ this.selectAndPlayMostImportantSound();
+ }
+ }
+}
diff --git a/fbw-a380x/src/systems/systems-host/systems/LegacyFwc.ts b/fbw-a380x/src/systems/systems-host/systems/LegacyFwc.ts
deleted file mode 100644
index 9ee2596a8f5..00000000000
--- a/fbw-a380x/src/systems/systems-host/systems/LegacyFwc.ts
+++ /dev/null
@@ -1,727 +0,0 @@
-/* eslint-disable no-underscore-dangle */
-/* eslint-disable camelcase */
-// TODO remove this once Rust implementation is up and running
-import { Arinc429Word, UpdateThrottler } from '@flybywiresim/fbw-sdk';
-
-enum FwcFlightPhase {
- ElecPwr = 1,
- FirstEngineStarted = 2,
- SecondEngineTakeOffPower = 3,
- AtOrAboveEightyKnots = 4,
- AtOrAboveV1 = 5,
- LiftOff = 6,
- AtOrAbove400Feet = 7,
- AtOrAbove1500FeetTo800Feet = 8,
- AtOrBelow800Feet = 9,
- TouchDown = 10,
- AtOrBelowEightyKnots = 11,
- EnginesShutdown = 12,
-}
-
-/**
- * This 1:1 port from the A32NX's FWC serves as temporary replacement, until a more sophisticated system simulation is in place.
- * After merge of PR #4872 (https://github.com/flybywiresim/aircraft/pull/4872) (intended for A32NX), the FWS architecture has to
- * be ported to the A380X, then this class can be removed.
- */
-export class LegacyFwc {
- private updateThrottler = new UpdateThrottler(125); // has to be > 100 due to pulse nodes
-
- toConfigTest: boolean;
-
- flightPhase: FwcFlightPhase;
-
- ldgMemo: boolean;
-
- toMemo: boolean;
-
- gndMemo: NXLogic_ConfirmNode;
-
- oneEngineRunningConf: NXLogic_ConfirmNode;
-
- speedAbove80KtsMemo: NXLogic_MemoryNode;
-
- speedAboveV1Memo: NXLogic_MemoryNode;
-
- mctMemo: NXLogic_ConfirmNode;
-
- firePBOutConf: NXLogic_ConfirmNode;
-
- firePBOutMemo: NXLogic_TriggeredMonostableNode;
-
- firePBClear12: NXLogic_MemoryNode;
-
- phase112Memo: NXLogic_TriggeredMonostableNode;
-
- phase10GroundMemo: NXLogic_TriggeredMonostableNode;
-
- ac80KtsMemo: NXLogic_TriggeredMonostableNode;
-
- prevPhase11InvertMemo: NXLogic_TriggeredMonostableNode;
-
- twoEnginesTOPowerInvertMemo: NXLogic_TriggeredMonostableNode;
-
- phase9Nvm: NXLogic_MemoryNode;
-
- prevPhase11: boolean;
-
- groundImmediateMemo: NXLogic_TriggeredMonostableNode;
-
- phase6Memo: NXLogic_TriggeredMonostableNode;
-
- phase7Memo: NXLogic_TriggeredMonostableNode;
-
- phase89Memo: NXLogic_TriggeredMonostableNode;
-
- memoTo_conf01: NXLogic_ConfirmNode;
-
- memoTo_memo: NXLogic_MemoryNode;
-
- memoLdgMemo_conf01: NXLogic_ConfirmNode;
-
- memoLdgMemo_inhibit: NXLogic_MemoryNode;
-
- memoLdgMemo_conf02: NXLogic_ConfirmNode;
-
- memoLdgMemo_below2000ft: NXLogic_MemoryNode;
-
- memoToInhibit_conf01: NXLogic_ConfirmNode;
-
- memoLdgInhibit_conf01: NXLogic_ConfirmNode;
-
- previousTargetAltitude: number;
-
- _wasBellowThreshold: boolean;
-
- _wasAboveThreshold: boolean;
-
- _wasInRange: boolean;
-
- _wasReach200ft: boolean;
-
- aircraft: Aircraft;
-
- constructor() {
- // momentary
- this.toConfigTest = null; // WTOCT
-
- // persistent
- this.flightPhase = null;
- this.ldgMemo = null;
- this.toMemo = null;
-
- // ESDL 1. 0. 60
- this.gndMemo = new NXLogic_ConfirmNode(1); // outptuts ZGND
-
- // ESDL 1. 0. 60
- this.oneEngineRunningConf = new NXLogic_ConfirmNode(30);
-
- // ESDL 1. 0. 73
- this.speedAbove80KtsMemo = new NXLogic_MemoryNode(true);
-
- this.speedAboveV1Memo = new NXLogic_MemoryNode();
-
- // ESDL 1. 0. 79 / ESDL 1. 0. 80
- this.mctMemo = new NXLogic_ConfirmNode(60, false);
-
- // ESDL 1. 0.100
- this.firePBOutConf = new NXLogic_ConfirmNode(0.2); // CONF01
- this.firePBOutMemo = new NXLogic_TriggeredMonostableNode(2); // MTRIG 05
- this.firePBClear12 = new NXLogic_MemoryNode(false);
- this.phase112Memo = new NXLogic_TriggeredMonostableNode(300); // MTRIG 03
- this.phase10GroundMemo = new NXLogic_TriggeredMonostableNode(2); // MTRIG 06
- this.ac80KtsMemo = new NXLogic_TriggeredMonostableNode(2); // MTRIG 04
- this.prevPhase11InvertMemo = new NXLogic_TriggeredMonostableNode(3, false); // MTRIG 02
- this.twoEnginesTOPowerInvertMemo = new NXLogic_TriggeredMonostableNode(1, false); // MTRIG 01
- this.phase9Nvm = new NXLogic_MemoryNode(true, true);
- this.prevPhase11 = false;
-
- // ESDL 1. 0.110
- this.groundImmediateMemo = new NXLogic_TriggeredMonostableNode(2); // MTRIG 03
- this.phase6Memo = new NXLogic_TriggeredMonostableNode(15);
- this.phase7Memo = new NXLogic_TriggeredMonostableNode(120);
- this.phase89Memo = new NXLogic_TriggeredMonostableNode(180); // MTRIG 02
-
- // ESDL 1. 0.180
- this.memoTo_conf01 = new NXLogic_ConfirmNode(120, true); // CONF 01
- this.memoTo_memo = new NXLogic_MemoryNode(false);
-
- // ESDL 1. 0.190
- this.memoLdgMemo_conf01 = new NXLogic_ConfirmNode(1, true); // CONF 01
- this.memoLdgMemo_inhibit = new NXLogic_MemoryNode(false);
- this.memoLdgMemo_conf02 = new NXLogic_ConfirmNode(10, true); // CONF 01
- this.memoLdgMemo_below2000ft = new NXLogic_MemoryNode(true);
-
- // ESDL 1. 0.310
- this.memoToInhibit_conf01 = new NXLogic_ConfirmNode(3, true); // CONF 01
-
- // ESDL 1. 0.320
- this.memoLdgInhibit_conf01 = new NXLogic_ConfirmNode(3, true); // CONF 01
-
- // altitude warning
- this.previousTargetAltitude = NaN;
- this._wasBellowThreshold = false;
- this._wasAboveThreshold = false;
- this._wasInRange = false;
- this._wasReach200ft = false;
- }
-
- update(_deltaTime: number) {
- const throttledT = this.updateThrottler.canUpdate(_deltaTime);
-
- if (throttledT > 0) {
- this._updateFlightPhase(throttledT);
- this._updateTakeoffMemo(throttledT);
- this._updateLandingMemo(throttledT);
- this._updateAltitudeWarning();
- }
- }
-
- _updateFlightPhase(_deltaTime: number) {
- const radioHeight1 = Arinc429Word.fromSimVarValue('L:A32NX_RA_1_RADIO_ALTITUDE');
- const radioHeight2 = Arinc429Word.fromSimVarValue('L:A32NX_RA_2_RADIO_ALTITUDE');
- const radioHeight3 = Arinc429Word.fromSimVarValue('L:A32NX_RA_3_RADIO_ALTITUDE');
-
- const raHeight1InValid = radioHeight1.isFailureWarning() || radioHeight1.isNoComputedData();
- const raHeight2InValid = radioHeight2.isFailureWarning() || radioHeight2.isNoComputedData();
- let radioHeight;
- if (raHeight1InValid) {
- if (raHeight2InValid) {
- radioHeight = radioHeight3;
- } else {
- radioHeight = radioHeight2;
- }
- } else {
- radioHeight = radioHeight1;
- }
- const eng1N1 = SimVar.GetSimVarValue('ENG N1 RPM:1', 'Percent');
- const eng2N1 = SimVar.GetSimVarValue('ENG N1 RPM:2', 'Percent');
- const eng3N1 = SimVar.GetSimVarValue('ENG N1 RPM:3', 'Percent');
- const eng4N1 = SimVar.GetSimVarValue('ENG N1 RPM:4', 'Percent');
- // TODO find a better source for the following value ("core speed at or above idle")
- // Note that N1 starts below idle on spawn on the runway, so this should be below 16 to not jump back to phase 1
- const oneEngRunning = eng1N1 > 15 || eng2N1 > 15 || eng3N1 > 15 || eng4N1 > 15;
- const oneEngineRunning = this.oneEngineRunningConf.write(oneEngRunning, _deltaTime);
- const noEngineRunning = !oneEngineRunning;
- const hFail = radioHeight1.isFailureWarning() && radioHeight2.isFailureWarning() && radioHeight3.isFailureWarning();
- const adcTestInhib = false;
-
- // ESLD 1.0.60
- const groundImmediate = Simplane.getIsGrounded();
- const ground = this.gndMemo.write(groundImmediate, _deltaTime);
-
- // ESLD 1.0.73
- const ias = SimVar.GetSimVarValue('AIRSPEED INDICATED', 'knots');
- const acSpeedAbove80kts = this.speedAbove80KtsMemo.write(ias > 83, ias < 77);
-
- const v1 = SimVar.GetSimVarValue('L:AIRLINER_V1_SPEED', 'knots');
- let acAboveV1;
- if (v1) {
- acAboveV1 = this.speedAboveV1Memo.write(ias > v1 + 3, ias < v1 - 3);
- } else {
- acAboveV1 = false;
- }
-
- // ESLD 1.0.90
- const hAbv1500 = radioHeight.isNoComputedData() || radioHeight.value > 1500;
- const hAbv800 = radioHeight.isNoComputedData() || radioHeight.value > 800;
- const hAbv400 = radioHeight.isNoComputedData() || radioHeight.value > 400;
-
- // ESLD 1.0.79 + 1.0.80
- const eng1TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:1', 'number');
- const eng1TLAFTO = SimVar.GetSimVarValue('L:A32NX_AIRLINER_TO_FLEX_TEMP', 'number') !== 0; // is a flex temp is set?
- const eng1MCT = eng1TLA > 33.3 && eng1TLA < 36.7;
- const eng1TLAFullPwr = eng1TLA > 43.3;
- const eng1MCL = eng1TLA > 22.9;
- const eng1SupMCT = !(eng1TLA < 36.7);
-
- const eng2TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:2', 'number');
- const eng2TLAFTO = eng1TLAFTO; // until we have proper FADECs
- const eng2MCT = eng2TLA > 33.3 && eng2TLA < 36.7;
- const eng2TLAFullPwr = eng2TLA > 43.3;
- const eng2MCL = eng2TLA > 22.9;
- const eng2SupMCT = !(eng2TLA < 36.7);
-
- const eng3TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:3', 'number');
- const eng3TLAFTO = eng1TLAFTO; // until we have proper FADECs
- const eng3MCT = eng3TLA > 33.3 && eng3TLA < 36.7;
- const eng3TLAFullPwr = eng3TLA > 43.3;
- const eng3MCL = eng3TLA > 22.9;
- const eng3SupMCT = !(eng3TLA < 36.7);
-
- const eng4TLA = SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:4', 'number');
- const eng4TLAFTO = eng1TLAFTO; // until we have proper FADECs
- const eng4MCT = eng3TLA > 33.3 && eng3TLA < 36.7;
- const eng4TLAFullPwr = eng3TLA > 43.3;
- const eng4MCL = eng3TLA > 22.9;
- const eng4SupMCT = !(eng4TLA < 36.7);
-
- const twoEnginesMcl = [eng1MCL, eng2MCL, eng3MCL, eng4MCL].filter(Boolean).length >= 2;
- const eng1TOPowerSignal = (eng1TLAFTO && eng1MCT) || eng1TLAFullPwr || eng1SupMCT;
- const eng2TOPowerSignal = (eng2TLAFTO && eng2MCT) || eng2TLAFullPwr || eng2SupMCT;
- const eng3TOPowerSignal = (eng3TLAFTO && eng3MCT) || eng3TLAFullPwr || eng3SupMCT;
- const eng4TOPowerSignal = (eng4TLAFTO && eng4MCT) || eng4TLAFullPwr || eng4SupMCT;
-
- const twoEnginesTOPowerSignal =
- [eng1TOPowerSignal, eng2TOPowerSignal, eng3TOPowerSignal, eng4TOPowerSignal].filter(Boolean).length >= 2;
-
- const twoEnginesTOPower =
- twoEnginesTOPowerSignal ||
- (this.mctMemo.write(twoEnginesTOPowerSignal, _deltaTime) && !hAbv1500 && twoEnginesMcl);
-
- // ESLD 1.0.100
- const eng1FirePbOut = SimVar.GetSimVarValue('L:A32NX_FIRE_BUTTON_ENG1', 'Bool');
- const eng1FirePbMemo = this.firePBOutMemo.write(this.firePBOutConf.write(eng1FirePbOut, _deltaTime), _deltaTime);
- const resetFirePbClear12 = eng1FirePbMemo && ground;
-
- const phase10 =
- (this.phase10GroundMemo.write(groundImmediate, _deltaTime) || groundImmediate) &&
- !twoEnginesTOPower &&
- acSpeedAbove80kts;
-
- const phase345Cond = ground && twoEnginesTOPower;
- const phase3 = !acSpeedAbove80kts && oneEngRunning && phase345Cond;
- const phase4 = acSpeedAbove80kts && phase345Cond && !acAboveV1;
- const phase5 = acSpeedAbove80kts && phase345Cond && acAboveV1;
-
- const setPhase11Nvm = phase3 || phase10;
- const resetPhase11Nvm =
- (!this.ac80KtsMemo.write(!acSpeedAbove80kts, _deltaTime) &&
- ((ground && this.prevPhase11InvertMemo.write(this.prevPhase11, _deltaTime)) ||
- resetFirePbClear12 ||
- (ground && this.twoEnginesTOPowerInvertMemo.write(twoEnginesTOPower, _deltaTime))) &&
- !this.prevPhase11) ||
- adcTestInhib;
- const phase11Nvm = this.phase9Nvm.write(setPhase11Nvm, resetPhase11Nvm); // S* / R (NVM)
- const phase211Cond = ground && !twoEnginesTOPower && !acSpeedAbove80kts;
- const phase11 = oneEngRunning && phase11Nvm && phase211Cond;
- const phase2 = phase211Cond && !phase11Nvm && oneEngRunning;
-
- const phase112MemoA = this.firePBClear12.write(phase11, resetFirePbClear12); // S / R*
- const phase112Cond = !phase11 && noEngineRunning && groundImmediate;
- const phase112Memo = this.phase112Memo.write(phase112MemoA && phase112Cond, _deltaTime); // MTRIG 03
- const phase1 = phase112Cond && !phase112Memo;
- const phase12 = phase112Cond && phase112Memo;
-
- this.prevPhase11 = phase11;
-
- // ESLD 1.0.110
- const ground2sMemorized = this.groundImmediateMemo.write(groundImmediate, _deltaTime) || groundImmediate;
-
- const phase6Cond = !hAbv400 && twoEnginesTOPower && !hFail && !ground2sMemorized;
- const phase6 = this.phase6Memo.write(phase6Cond, _deltaTime) && phase6Cond;
-
- const phase7Cond = !phase6 && !hAbv1500 && twoEnginesTOPower && !hFail && !ground2sMemorized;
- const phase7 = this.phase7Memo.write(phase7Cond, _deltaTime) && phase7Cond;
-
- const phase89Cond = !ground2sMemorized && !hFail && !twoEnginesTOPower && !hAbv1500 && !hAbv800;
- const phase89Memo = this.phase89Memo.write(phase89Cond, _deltaTime) && phase89Cond;
-
- const phase8 = !phase7 && !ground2sMemorized && !phase89Memo;
- const phase9 = phase89Memo && !phase10;
-
- /** * End of ESLD logic ** */
-
- // consolidate into single variable (just to be safe)
- const phases = [phase1, phase2, phase3, phase4, phase5, phase6, phase7, phase8, phase9, phase10, phase11, phase12];
-
- if (this.flightPhase === null && phases.indexOf(true) !== -1) {
- // if we aren't initialized, just grab the first one that is valid
- this._setFlightPhase(phases.indexOf(true) + 1);
- console.log(`FWC flight phase: ${this.flightPhase}`);
- return;
- }
-
- const activePhases = phases
- .map((x, i) => [x ? 1 : 0, i + 1])
- .filter((y) => y[0] === 1)
- .map((z) => z[1]);
-
- // the usual and easy case: only one flight phase is valid
- if (activePhases.length === 1) {
- if (activePhases[0] !== this.flightPhase) {
- console.log(`FWC flight phase: ${this.flightPhase} => ${activePhases[0]}`);
- this._setFlightPhase(activePhases[0]);
- }
- return;
- }
-
- // the mixed case => warn
- if (activePhases.length > 1) {
- if (activePhases.indexOf(this.flightPhase) !== -1) {
- // if the currently active one is present, keep it
- return;
- }
- // pick the earliest one
- this._setFlightPhase(activePhases[0]);
- return;
- }
-
- // otherwise, no flight phase is valid => warn
- if (this.flightPhase === null) {
- this._setFlightPhase(null);
- }
- }
-
- _setFlightPhase(flightPhase: FwcFlightPhase) {
- if (flightPhase === this.flightPhase) {
- return;
- }
-
- // update flight phase
- this.flightPhase = flightPhase;
- SimVar.SetSimVarValue('L:A32NX_FWC_FLIGHT_PHASE', 'Enum', this.flightPhase || 0);
- }
-
- _updateTakeoffMemo(_deltaTime: number) {
- /// FWC ESLD 1.0.180
- const setFlightPhaseMemo = this.flightPhase === FwcFlightPhase.FirstEngineStarted && this.toConfigTest;
- const resetFlightPhaseMemo =
- this.flightPhase === FwcFlightPhase.EnginesShutdown ||
- this.flightPhase === FwcFlightPhase.SecondEngineTakeOffPower ||
- this.flightPhase === FwcFlightPhase.ElecPwr ||
- this.flightPhase === FwcFlightPhase.AtOrAbove1500FeetTo800Feet;
- const flightPhaseMemo = this.memoTo_memo.write(setFlightPhaseMemo, resetFlightPhaseMemo);
-
- const eng1NotRunning = SimVar.GetSimVarValue('ENG N1 RPM:1', 'Percent') < 15;
- const eng2NotRunning = SimVar.GetSimVarValue('ENG N1 RPM:2', 'Percent') < 15;
- const eng3NotRunning = SimVar.GetSimVarValue('ENG N1 RPM:3', 'Percent') < 15;
- const eng4NotRunning = SimVar.GetSimVarValue('ENG N1 RPM:4', 'Percent') < 15;
- const toTimerElapsed = this.memoTo_conf01.write(
- !eng1NotRunning && !eng2NotRunning && !eng3NotRunning && !eng4NotRunning,
- _deltaTime,
- );
-
- this.toMemo = flightPhaseMemo || (this.flightPhase === FwcFlightPhase.FirstEngineStarted && toTimerElapsed);
- SimVar.SetSimVarValue('L:A32NX_FWC_TOMEMO', 'Bool', this.toMemo);
- }
-
- _updateLandingMemo(_deltaTime: number) {
- const radioHeight1 = Arinc429Word.fromSimVarValue('L:A32NX_RA_1_RADIO_ALTITUDE');
- const radioHeight2 = Arinc429Word.fromSimVarValue('L:A32NX_RA_2_RADIO_ALTITUDE');
- const radioHeight3 = Arinc429Word.fromSimVarValue('L:A32NX_RA_3_RADIO_ALTITUDE');
- const radioHeight1Invalid = radioHeight1.isFailureWarning() || radioHeight1.isNoComputedData();
- const radioHeight2Invalid = radioHeight2.isFailureWarning() || radioHeight2.isNoComputedData();
- const radioHeight3Invalid = radioHeight3.isFailureWarning() || radioHeight3.isNoComputedData();
- const gearDownlocked = SimVar.GetSimVarValue('GEAR TOTAL PCT EXTENDED', 'percent') > 0.95;
-
- // FWC ESLD 1.0.190
- const setBelow2000ft =
- (radioHeight1.value < 2000 && !radioHeight1Invalid) ||
- (radioHeight2.value < 2000 && !radioHeight2Invalid) ||
- (radioHeight3.value < 2000 && !radioHeight3Invalid);
- const resetBelow2000ft =
- (radioHeight1.value > 2200 || radioHeight1Invalid) &&
- (radioHeight2.value > 2200 || radioHeight2Invalid) &&
- (radioHeight3.value > 2200 || radioHeight3Invalid);
- const memo2 = this.memoLdgMemo_below2000ft.write(setBelow2000ft, resetBelow2000ft);
-
- const setInhibitMemo = this.memoLdgMemo_conf01.write(
- resetBelow2000ft && !radioHeight1Invalid && !radioHeight2Invalid && !radioHeight3Invalid,
- _deltaTime,
- );
- const resetInhibitMemo = !(
- this.flightPhase === FwcFlightPhase.AtOrBelow800Feet ||
- this.flightPhase === FwcFlightPhase.TouchDown ||
- this.flightPhase === FwcFlightPhase.AtOrAbove1500FeetTo800Feet
- );
- const memo1 = this.memoLdgMemo_inhibit.write(setInhibitMemo, resetInhibitMemo);
-
- const showInApproach = memo1 && memo2 && this.flightPhase === FwcFlightPhase.AtOrAbove1500FeetTo800Feet;
-
- const invalidRadioMemo = this.memoLdgMemo_conf02.write(
- radioHeight1Invalid &&
- radioHeight2Invalid &&
- radioHeight3Invalid &&
- gearDownlocked &&
- this.flightPhase === FwcFlightPhase.AtOrAbove1500FeetTo800Feet,
- _deltaTime,
- );
-
- this.ldgMemo =
- showInApproach ||
- invalidRadioMemo ||
- this.flightPhase === FwcFlightPhase.TouchDown ||
- this.flightPhase === FwcFlightPhase.AtOrBelow800Feet;
- SimVar.SetSimVarValue('L:A32NX_FWC_LDGMEMO', 'Bool', this.ldgMemo);
- }
-
- _updateAltitudeWarning() {
- const indicatedAltitude = Simplane.getAltitude();
- const shortAlert = SimVar.GetSimVarValue('L:A32NX_ALT_DEVIATION_SHORT', 'Bool');
- if (shortAlert === 1) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION_SHORT', 'Bool', false);
- }
-
- const warningPressed =
- !!SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_L', 'Bool') ||
- !!SimVar.GetSimVarValue('L:PUSH_AUTOPILOT_MASTERAWARN_R', 'Bool');
- if (warningPressed === true) {
- this._wasBellowThreshold = false;
- this._wasAboveThreshold = false;
- this._wasInRange = false;
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- return;
- }
-
- if (Simplane.getIsGrounded()) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- }
-
- // Use FCU displayed value
- const currentAltitudeConstraint = SimVar.GetSimVarValue('L:A32NX_FG_ALTITUDE_CONSTRAINT', 'feet');
- const currentFCUAltitude = SimVar.GetSimVarValue('AUTOPILOT ALTITUDE LOCK VAR:3', 'feet');
- const targetAltitude =
- currentAltitudeConstraint && !this.hasAltitudeConstraint() ? currentAltitudeConstraint : currentFCUAltitude;
-
- // Exit when selected altitude is being changed
- if (this.previousTargetAltitude !== targetAltitude) {
- this.previousTargetAltitude = targetAltitude;
- this._wasBellowThreshold = false;
- this._wasAboveThreshold = false;
- this._wasInRange = false;
- this._wasReach200ft = false;
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION_SHORT', 'Bool', false);
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- return;
- }
-
- // Exit when:
- // - Landing gear down & slats extended
- // - Glide slope captured
- // - Landing locked down
-
- const landingGearIsDown =
- SimVar.GetSimVarValue('L:A32NX_FLAPS_HANDLE_INDEX', 'Enum') >= 1 &&
- SimVar.GetSimVarValue('L:A32NX_GEAR_HANDLE_POSITION', 'Percent over 100') > 0.5;
- const verticalMode = SimVar.GetSimVarValue('L:A32NX_FMA_VERTICAL_MODE', 'Number');
- const glideSlopeCaptured = verticalMode >= 30 && verticalMode <= 34;
- const landingGearIsLockedDown = SimVar.GetSimVarValue('GEAR POSITION:0', 'Enum') > 0.9;
- const isTcasResolutionAdvisoryActive = SimVar.GetSimVarValue('L:A32NX_TCAS_STATE', 'Enum') > 1;
- if (landingGearIsDown || glideSlopeCaptured || landingGearIsLockedDown || isTcasResolutionAdvisoryActive) {
- this._wasBellowThreshold = false;
- this._wasAboveThreshold = false;
- this._wasInRange = false;
- this._wasReach200ft = false;
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION_SHORT', 'Bool', false);
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- return;
- }
-
- const delta = Math.abs(indicatedAltitude - targetAltitude);
-
- if (delta < 200) {
- this._wasBellowThreshold = true;
- this._wasAboveThreshold = false;
- this._wasReach200ft = true;
- }
- if (delta > 750) {
- this._wasAboveThreshold = true;
- this._wasBellowThreshold = false;
- }
- if (delta >= 200 && delta <= 750) {
- this._wasInRange = true;
- }
-
- if (this._wasBellowThreshold && this._wasReach200ft) {
- if (delta >= 200) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', true);
- } else if (delta < 200) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- }
- } else if (this._wasAboveThreshold && delta <= 750 && !this._wasReach200ft) {
- if (
- !SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_1_ACTIVE', 'Bool') &&
- !SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_2_ACTIVE', 'Bool')
- ) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION_SHORT', 'Bool', true);
- }
- } else if (delta > 750 && this._wasInRange && !this._wasReach200ft) {
- if (delta > 750) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', true);
- } else if (delta >= 750) {
- SimVar.SetSimVarValue('L:A32NX_ALT_DEVIATION', 'Bool', false);
- }
- }
- }
-
- hasAltitudeConstraint() {
- if (
- Simplane.getAutoPilotAltitudeManaged() &&
- SimVar.GetSimVarValue('L:AP_CURRENT_TARGET_ALTITUDE_IS_CONSTRAINT', 'number') !== 0
- ) {
- return false;
- }
- return true;
- }
-}
-
-/*
- * This file contains various nodes that can be used for logical processing. Systems like the FWC may use them to
- * accurately implement their functionality.
- */
-
-/**
- * The following class represents a monostable circuit. It is inspired by the MTRIG nodes as described in the ESLD and
- * used by the FWC.
- * When it detects either a rising or a falling edge (depending on it's type) it will emit a signal for a certain time t
- * after the detection. It is not retriggerable, so a rising/falling edge within t will not reset the timer.
- */
-class NXLogic_TriggeredMonostableNode {
- t: number;
-
- risingEdge: boolean;
-
- _timer: number;
-
- _previousValue: boolean;
-
- constructor(t: number, risingEdge = true) {
- this.t = t;
- this.risingEdge = risingEdge;
- this._timer = 0;
- this._previousValue = null;
- }
-
- write(value: boolean, _deltaTime: number) {
- if (this._previousValue === null && SimVar.GetSimVarValue('L:A32NX_FWC_SKIP_STARTUP', 'Bool')) {
- this._previousValue = value;
- }
- if (this.risingEdge) {
- if (this._timer > 0) {
- this._timer = Math.max(this._timer - _deltaTime / 1000, 0);
- this._previousValue = value;
- return true;
- }
- if (!this._previousValue && value) {
- this._timer = this.t;
- this._previousValue = value;
- return true;
- }
- } else {
- if (this._timer > 0) {
- this._timer = Math.max(this._timer - _deltaTime / 1000, 0);
- this._previousValue = value;
- return true;
- }
- if (this._previousValue && !value) {
- this._timer = this.t;
- this._previousValue = value;
- return true;
- }
- }
- this._previousValue = value;
- return false;
- }
-}
-
-/**
- * The following class represents a "confirmation" circuit, which only passes a signal once it has been stable for a
- * certain amount of time. It is inspired by the CONF nodes as described in the ESLD and used by the FWC.
- * When it detects either a rising or falling edge (depending on it's type) it will wait for up to time t and emit the
- * incoming signal if it was stable throughout t. If at any point the signal reverts during t the state is fully reset,
- * and the original signal will be emitted again.
- */
-class NXLogic_ConfirmNode {
- t: number;
-
- risingEdge: boolean;
-
- _timer: number;
-
- _previousInput: boolean;
-
- _previousOutput: boolean;
-
- constructor(t: number, risingEdge = true) {
- this.t = t;
- this.risingEdge = risingEdge;
- this._timer = 0;
- this._previousInput = null;
- this._previousOutput = null;
- }
-
- write(value: boolean, _deltaTime: number) {
- if (this._previousInput === null && SimVar.GetSimVarValue('L:A32NX_FWC_SKIP_STARTUP', 'Bool')) {
- this._previousInput = value;
- this._previousOutput = value;
- }
- if (this.risingEdge) {
- if (!value) {
- this._timer = 0;
- } else if (this._timer > 0) {
- this._timer = Math.max(this._timer - _deltaTime / 1000, 0);
- this._previousInput = value;
- this._previousOutput = !value;
- return !value;
- } else if (!this._previousInput && value) {
- this._timer = this.t;
- this._previousInput = value;
- this._previousOutput = !value;
- return !value;
- }
- } else if (value) {
- this._timer = 0;
- } else if (this._timer > 0) {
- this._timer = Math.max(this._timer - _deltaTime / 1000, 0);
- this._previousInput = value;
- this._previousOutput = !value;
- return !value;
- } else if (this._previousInput && !value) {
- this._timer = this.t;
- this._previousInput = value;
- this._previousOutput = !value;
- return !value;
- }
- this._previousInput = value;
- this._previousOutput = value;
- return value;
- }
-
- read() {
- return this._previousOutput;
- }
-}
-
-/**
- * The following class represents a flip-flop or memory circuit that can be used to store a single bit. It is inspired
- * by the S+R nodes as described in the ESLD.
- * It has two inputs: Set and Reset. At first it will always emit a falsy value, until it receives a signal on the set
- * input, at which point it will start emitting a truthy value. This will continue until a signal is received on the
- * reset input, at which point it reverts to the original falsy output. It a signal is sent on both set and reset at the
- * same time, the input with a star will have precedence.
- * The NVM flag is not implemented right now but can be used to indicate non-volatile memory storage, which means the
- * value will persist even when power is lost and subsequently restored.
- */
-class NXLogic_MemoryNode {
- setStar: boolean;
-
- nvm: boolean;
-
- _value: boolean;
-
- /**
- * @param setStar Whether set has precedence over reset if both are applied simultaneously.
- * @param nvm Whether the is non-volatile and will be kept even when power is lost.
- */
- constructor(setStar = true, nvm = false) {
- this.setStar = setStar;
- this.nvm = nvm; // TODO in future, reset non-nvm on power cycle
- this._value = false;
- }
-
- write(set, reset) {
- if (set && reset) {
- this._value = this.setStar;
- } else if (set && !this._value) {
- this._value = true;
- } else if (reset && this._value) {
- this._value = false;
- }
- return this._value;
- }
-
- read() {
- return this._value;
- }
-}
diff --git a/fbw-a380x/src/systems/systems-host/systems/LegacyGpws.ts b/fbw-a380x/src/systems/systems-host/systems/LegacyGpws.ts
index 88b61713dbc..becf5e19fc1 100644
--- a/fbw-a380x/src/systems/systems-host/systems/LegacyGpws.ts
+++ b/fbw-a380x/src/systems/systems-host/systems/LegacyGpws.ts
@@ -2,6 +2,8 @@ import { Arinc429SignStatusMatrix, Arinc429Word, NXDataStore, UpdateThrottler }
import { FmgcFlightPhase } from '@shared/flightphase';
import { LegacySoundManager, soundList } from 'systems-host/systems/LegacySoundManager';
import { A380X_DEFAULT_RADIO_AUTO_CALL_OUTS, A380XRadioAutoCallOutFlags } from '../../shared/src/AutoCallOuts';
+import { EventBus } from '@microsoft/msfs-sdk';
+import { FwsSoundManagerControlEvents } from 'systems-host/systems/FlightWarningSystem/FwsSoundManager';
type ModesType = {
current: number;
@@ -12,12 +14,12 @@ type ModesType = {
/**
* This 1:1 port from the A32NX's GPWS+FWS serves as temporary replacement, until a more sophisticated system simulation is in place.
- * After merge of PR #4872 (https://github.com/flybywiresim/aircraft/pull/4872) (intended for A32NX), the FWS architecture has to
- * be ported to the A380X, then the FWS callout parts of this class can be removed.
*/
export class LegacyGpws {
private updateThrottler = new UpdateThrottler(125); // has to be > 100 due to pulse nodes
+ private pub = this.bus.getPublisher();
+
autoCallOutPins: number;
minimumsState = 0;
@@ -51,7 +53,10 @@ export class LegacyGpws {
egpwsAlertDiscreteWord2 = Arinc429Word.empty();
// eslint-disable-next-line camelcase
- constructor(private soundManager: LegacySoundManager) {
+ constructor(
+ private bus: EventBus,
+ private soundManager: LegacySoundManager,
+ ) {
this.autoCallOutPins = A380X_DEFAULT_RADIO_AUTO_CALL_OUTS;
this.minimumsState = 0;
@@ -331,10 +336,10 @@ export class LegacyGpws {
} else if (this.minimumsState === 1 && over100Above) {
this.minimumsState = 2;
} else if (this.minimumsState === 2 && !over100Above) {
- this.soundManager.tryPlaySound(soundList.hundred_above);
+ this.pub.pub('enqueueSound', 'hundred_above');
this.minimumsState = 1;
} else if (this.minimumsState === 1 && !overMinimums) {
- this.soundManager.tryPlaySound(soundList.minimums);
+ this.pub.pub('enqueueSound', 'minimums');
this.minimumsState = 0;
}
}
@@ -578,7 +583,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 6) {
if (this.RetardState.value !== 'retardPlaying' && this.autoCallOutPins & A380XRadioAutoCallOutFlags.Five) {
- this.soundManager.tryPlaySound(soundList.alt_5);
+ this.pub.pub('enqueueSound', 'alt_5');
}
this.AltCallState.action('down');
}
@@ -588,7 +593,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 12) {
if (this.RetardState.value !== 'retardPlaying' && this.autoCallOutPins & A380XRadioAutoCallOutFlags.Ten) {
- this.soundManager.tryPlaySound(soundList.alt_10);
+ this.pub.pub('enqueueSound', 'alt_10');
}
this.AltCallState.action('down');
}
@@ -598,7 +603,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 22) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Twenty) {
- this.soundManager.tryPlaySound(soundList.alt_20);
+ this.pub.pub('enqueueSound', 'alt_20');
}
this.AltCallState.action('down');
}
@@ -608,7 +613,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 32) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Thirty) {
- this.soundManager.tryPlaySound(soundList.alt_30);
+ this.pub.pub('enqueueSound', 'alt_30');
}
this.AltCallState.action('down');
}
@@ -618,7 +623,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 42) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Forty) {
- this.soundManager.tryPlaySound(soundList.alt_40);
+ this.pub.pub('enqueueSound', 'alt_40');
}
this.AltCallState.action('down');
}
@@ -628,7 +633,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 53) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Fifty) {
- this.soundManager.tryPlaySound(soundList.alt_50);
+ this.pub.pub('enqueueSound', 'alt_50');
}
this.AltCallState.action('down');
}
@@ -638,7 +643,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 63) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Sixty) {
- this.soundManager.tryPlaySound(soundList.alt_60);
+ this.pub.pub('enqueueSound', 'alt_60');
}
this.AltCallState.action('down');
}
@@ -648,7 +653,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 73) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Seventy) {
- this.soundManager.tryPlaySound(soundList.alt_70);
+ this.pub.pub('enqueueSound', 'alt_70');
}
this.AltCallState.action('down');
}
@@ -658,7 +663,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 83) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Eighty) {
- this.soundManager.tryPlaySound(soundList.alt_80);
+ this.pub.pub('enqueueSound', 'alt_80');
}
this.AltCallState.action('down');
}
@@ -668,7 +673,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 93) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.Ninety) {
- this.soundManager.tryPlaySound(soundList.alt_90);
+ this.pub.pub('enqueueSound', 'alt_90');
}
this.AltCallState.action('down');
}
@@ -678,7 +683,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 110) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.OneHundred) {
- this.soundManager.tryPlaySound(soundList.alt_100);
+ this.pub.pub('enqueueSound', 'alt_100');
}
this.AltCallState.action('down');
}
@@ -688,7 +693,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 210) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.TwoHundred) {
- this.soundManager.tryPlaySound(soundList.alt_200);
+ this.pub.pub('enqueueSound', 'alt_200');
}
this.AltCallState.action('down');
}
@@ -698,7 +703,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 310) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.ThreeHundred) {
- this.soundManager.tryPlaySound(soundList.alt_300);
+ this.pub.pub('enqueueSound', 'alt_300');
}
this.AltCallState.action('down');
}
@@ -708,7 +713,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 410) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.FourHundred) {
- this.soundManager.tryPlaySound(soundList.alt_400);
+ this.pub.pub('enqueueSound', 'alt_400');
}
this.AltCallState.action('down');
}
@@ -718,7 +723,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 513) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.FiveHundred) {
- this.soundManager.tryPlaySound(soundList.alt_500);
+ this.pub.pub('enqueueSound', 'alt_500');
}
this.AltCallState.action('down');
}
@@ -728,7 +733,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 1020) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.OneThousand) {
- this.soundManager.tryPlaySound(soundList.alt_1000);
+ this.pub.pub('enqueueSound', 'alt_1000');
}
this.AltCallState.action('down');
}
@@ -738,7 +743,7 @@ export class LegacyGpws {
this.AltCallState.action('up');
} else if (radioAlt <= 2020) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.TwoThousand) {
- this.soundManager.tryPlaySound(soundList.alt_2000);
+ this.pub.pub('enqueueSound', 'alt_2000');
}
this.AltCallState.action('down');
}
@@ -746,9 +751,9 @@ export class LegacyGpws {
case 'over2500':
if (radioAlt <= 2530) {
if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.TwoThousandFiveHundred) {
- this.soundManager.tryPlaySound(soundList.alt_2500);
+ this.pub.pub('enqueueSound', 'alt_2500');
} else if (this.autoCallOutPins & A380XRadioAutoCallOutFlags.TwentyFiveHundred) {
- this.soundManager.tryPlaySound(soundList.alt_2500b);
+ this.pub.pub('enqueueSound', 'alt_2500b');
}
this.AltCallState.action('down');
}
@@ -762,26 +767,28 @@ export class LegacyGpws {
if (radioAlt < 20) {
if (!SimVar.GetSimVarValue('L:A32NX_AUTOPILOT_ACTIVE', 'Bool')) {
this.RetardState.action('play');
- this.soundManager.addPeriodicSound(soundList.retard, 1.1);
+ this.pub.pub('enqueueSound', 'retard');
} else if (radioAlt < 10) {
this.RetardState.action('play');
- this.soundManager.addPeriodicSound(soundList.retard, 1.1);
+ this.pub.pub('enqueueSound', 'retard');
}
}
break;
case 'retardPlaying':
if (
SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:1', 'number') < 2.6 ||
- SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:2', 'number') < 2.6
+ SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:2', 'number') < 2.6 ||
+ SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:3', 'number') < 2.6 ||
+ SimVar.GetSimVarValue('L:A32NX_AUTOTHRUST_TLA:4', 'number') < 2.6
) {
this.RetardState.action('land');
- this.soundManager.removePeriodicSound(soundList.retard);
+ this.pub.pub('dequeueSound', 'retard');
} else if (
SimVar.GetSimVarValue('L:A32NX_FMGC_FLIGHT_PHASE', 'Enum') === FmgcFlightPhase.GoAround ||
radioAlt > 20
) {
this.RetardState.action('go_around');
- this.soundManager.removePeriodicSound(soundList.retard);
+ this.pub.pub('dequeueSound', 'retard');
}
break;
case 'landed':
diff --git a/fbw-a380x/src/systems/systems-host/systems/LegacySoundManager.ts b/fbw-a380x/src/systems/systems-host/systems/LegacySoundManager.ts
index 8512cb125ad..25516bb3c9a 100644
--- a/fbw-a380x/src/systems/systems-host/systems/LegacySoundManager.ts
+++ b/fbw-a380x/src/systems/systems-host/systems/LegacySoundManager.ts
@@ -146,92 +146,4 @@ export const soundList: Record = {
name: 'aural_too_low_terrain',
length: 0.9,
},
- minimums: {
- name: 'aural_minimumnew',
- length: 0.67,
- },
- hundred_above: {
- name: 'aural_100above',
- length: 0.72,
- },
- retard: {
- name: 'new_retard',
- length: 0.9,
- },
- alt_2500: {
- name: 'new_2500',
- length: 1.1,
- },
- alt_2500b: {
- name: 'new_2_500',
- length: 1.047,
- },
- alt_2000: {
- name: 'new_2000',
- length: 0.72,
- },
- alt_1000: {
- name: 'new_1000',
- length: 0.9,
- },
- alt_500: {
- name: 'new_500',
- length: 0.6,
- },
- alt_400: {
- name: 'new_400',
- length: 0.6,
- },
- alt_300: {
- name: 'new_300',
- length: 0.6,
- },
- alt_200: {
- name: 'new_200',
- length: 0.6,
- },
- alt_100: {
- name: 'new_100',
- length: 0.6,
- },
- alt_90: {
- name: '90_380',
- length: 0.4,
- },
- alt_80: {
- name: '80_380',
- length: 0.4,
- },
- alt_70: {
- name: '70_380',
- length: 0.4,
- },
- alt_60: {
- name: '60_380',
- length: 0.4,
- },
- alt_50: {
- name: '50_380',
- length: 0.4,
- },
- alt_40: {
- name: '40_380',
- length: 0.4,
- },
- alt_30: {
- name: '30_380',
- length: 0.4,
- },
- alt_20: {
- name: '20_380',
- length: 0.4,
- },
- alt_10: {
- name: '10_380',
- length: 0.3,
- },
- alt_5: {
- name: '5_380',
- length: 0.3,
- },
};
diff --git a/fbw-a380x/src/wasm/fbw_a380/src/FlyByWireInterface.cpp b/fbw-a380x/src/wasm/fbw_a380/src/FlyByWireInterface.cpp
index 10c137cb3ce..5850fa32186 100644
--- a/fbw-a380x/src/wasm/fbw_a380/src/FlyByWireInterface.cpp
+++ b/fbw-a380x/src/wasm/fbw_a380/src/FlyByWireInterface.cpp
@@ -332,7 +332,7 @@ void FlyByWireInterface::setupLocalVariables() {
idFmaSpeedProtectionActive = std::make_unique("A32NX_FMA_SPEED_PROTECTION_MODE");
idFmaSoftAltModeActive = std::make_unique("A32NX_FMA_SOFT_ALT_MODE");
idFmaCruiseAltModeActive = std::make_unique("A32NX_FMA_CRUISE_ALT_MODE");
- idFmaApproachCapability = std::make_unique("A32NX_ApproachCapability");
+ idFmaApproachCapability = std::make_unique("A32NX_APPROACH_CAPABILITY");
idFmaTripleClick = std::make_unique("A32NX_FMA_TRIPLE_CLICK");
idFmaModeReversion = std::make_unique("A32NX_FMA_MODE_REVERSION");
diff --git a/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.cpp b/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.cpp
index 0ea0c773bcd..18dd71601b0 100644
--- a/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.cpp
+++ b/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.cpp
@@ -622,6 +622,7 @@ bool SimConnectInterface::prepareSimInputSimConnectDataDefinitions() {
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTOPILOT_DISENGAGE_SET, "AUTOPILOT_DISENGAGE_SET", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTOPILOT_DISENGAGE_TOGGLE, "AUTOPILOT_DISENGAGE_TOGGLE", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::TOGGLE_FLIGHT_DIRECTOR, "TOGGLE_FLIGHT_DIRECTOR", false);
+ result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_AUTOPILOT_DISENGAGE, "A32NX.AUTOPILOT_DISENGAGE", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_FCU_AP_1_PUSH, "A32NX.FCU_AP_1_PUSH", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_FCU_AP_2_PUSH, "A32NX.FCU_AP_2_PUSH", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_FCU_AP_DISCONNECT_PUSH, "A32NX.FCU_AP_DISCONNECT_PUSH", false);
@@ -693,6 +694,7 @@ bool SimConnectInterface::prepareSimInputSimConnectDataDefinitions() {
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTO_THROTTLE_ARM, "AUTO_THROTTLE_ARM", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTO_THROTTLE_DISCONNECT, "AUTO_THROTTLE_DISCONNECT", true);
+ result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_AUTO_THROTTLE_DISCONNECT, "A32NX.AUTO_THROTTLE_DISCONNECT", false);
result &= addInputDataDefinition(hSimConnect, 0, Events::AUTO_THROTTLE_TO_GA, "AUTO_THROTTLE_TO_GA", true);
result &= addInputDataDefinition(hSimConnect, 0, Events::A32NX_ATHR_RESET_DISABLE, "A32NX.ATHR_RESET_DISABLE", false);
@@ -2216,7 +2218,7 @@ void SimConnectInterface::processEventWithOneParam(const DWORD eventId, const DW
std::cout << "WASM: event triggered: AUTOPILOT_DISENGAGE_SET" << std::endl;
// Re emitting masked event for autopilot disconnection
- execute_calculator_code("(>K:A32NX.AUTOPILOT_DISENGAGE)", nullptr, nullptr, nullptr);
+ sendEvent(SimConnectInterface::Events::A32NX_AUTOPILOT_DISENGAGE, 0, SIMCONNECT_GROUP_PRIORITY_STANDARD);
}
break;
}
@@ -2678,7 +2680,7 @@ void SimConnectInterface::processEventWithOneParam(const DWORD eventId, const DW
std::cout << "WASM: event triggered: AUTO_THROTTLE_DISCONNECT" << std::endl;
// Re emitting masked event for autobrake disconnection
- execute_calculator_code("(>K:A32NX.AUTO_THROTTLE_DISCONNECT)", nullptr, nullptr, nullptr);
+ sendEvent(Events::A32NX_AUTO_THROTTLE_DISCONNECT, 0, SIMCONNECT_GROUP_PRIORITY_STANDARD);
break;
}
diff --git a/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.h b/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.h
index a97dd8195f0..b5c6093f39f 100644
--- a/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.h
+++ b/fbw-a380x/src/wasm/fbw_a380/src/interface/SimConnectInterface.h
@@ -48,6 +48,7 @@ class SimConnectInterface {
AUTOPILOT_DISENGAGE_SET,
AUTOPILOT_DISENGAGE_TOGGLE,
TOGGLE_FLIGHT_DIRECTOR,
+ A32NX_AUTOPILOT_DISENGAGE,
A32NX_FCU_AP_1_PUSH,
A32NX_FCU_AP_2_PUSH,
A32NX_FCU_AP_DISCONNECT_PUSH,
@@ -116,6 +117,7 @@ class SimConnectInterface {
AP_MACH_HOLD,
AUTO_THROTTLE_ARM,
AUTO_THROTTLE_DISCONNECT,
+ A32NX_AUTO_THROTTLE_DISCONNECT,
AUTO_THROTTLE_TO_GA,
A32NX_ATHR_RESET_DISABLE,
A32NX_THROTTLE_MAPPING_SET_DEFAULTS,