From 781db96d37be722e99e196fa0472ca545d3d2dfa Mon Sep 17 00:00:00 2001 From: floridude <63071941+flogross89@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:22:23 +0100 Subject: [PATCH] feat(a380x/fms): FMC reset through overhead reset panel (#9685) * feat(a380x/fmc): Enable FMC reset through overhead reset panel * FMC reset if last FMC disabled * changelog * rename component * allow city pair entry after preflight, if city pair not filled * Destroy FMCs when all reset p/b are pulled * some more FMS failed stuff * tracer's comments * JSDoc & rename events * Don't destroy FMCs, just reset * fix reset behavior * fix FMC failure codes * better fail/powered/reset architecture * Update CHANGELOG.md --- .github/CHANGELOG.md | 1 + .../FlyByWire_A380_842/model/A380_COCKPIT.xml | 13 +++ .../model/behaviour/overhead/reset.xml | 17 ++++ fbw-a380x/src/systems/failures/src/a380.ts | 21 +++-- .../src/MFD/FMC/FlightManagementComputer.ts | 73 ++++++++------- .../instruments/src/MFD/FMC/FmcInterface.ts | 2 + .../instruments/src/MFD/FMC/FmcService.ts | 90 ++++++++++++------- .../src/systems/instruments/src/MFD/MFD.tsx | 13 ++- .../instruments/src/MFD/instrument.tsx | 37 +++++--- .../src/MFD/pages/FMS/F-PLN/MfdFmsFpln.scss | 2 +- .../src/MFD/pages/FMS/MfdFmsInit.tsx | 40 +++++---- .../src/MFD/pages/FMS/MfdFmsPageNotAvail.tsx | 63 +++++++++++++ .../src/MFD/pages/common/style.scss | 9 ++ .../src/MFD/shared/MFDSimvarPublisher.tsx | 6 ++ .../providers/FmsDataPublisher.ts | 2 + .../providers/ResetPanelPublisher.tsx | 27 ++++++ .../src/systems/instruments/src/EFB/index.ts | 1 + .../instruments/src/ND/pages/arc/index.tsx | 13 ++- .../src/ND/pages/rose/RoseNavPage.tsx | 13 ++- .../src/ND/types/GenericFmsEvents.ts | 1 + 20 files changed, 339 insertions(+), 105 deletions(-) create mode 100644 fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/overhead/reset.xml create mode 100644 fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsPageNotAvail.tsx create mode 100644 fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/ResetPanelPublisher.tsx diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index fdec81f00be..cc0fe269dae 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -120,6 +120,7 @@ 1. [FMS] Transition altitude/level and RNP now come from navdata in MSFS2024 - @tracernz (Mike) 1. [ATSU] Fixed issues with the ALL-CALLSIGNS recipient on Hoppie - @CronixZero (CronixZero) 1. [A380X/MFD] Add ATCCOM D-ATIS page layout - @heclak (Heclak) +1. [A380X/FMS] Enable FMC reset through overhead reset panel push buttons - @flogross89 (floridude) ## 0.12.0 diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_COCKPIT.xml b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_COCKPIT.xml index 0a1ada10219..1e1b9771bb1 100644 --- a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_COCKPIT.xml +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/A380_COCKPIT.xml @@ -35,6 +35,7 @@ + @@ -4979,6 +4980,18 @@ False + + + + FMC_A + + + FMC_B + + + FMC_C + + OverheadLower diff --git a/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/overhead/reset.xml b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/overhead/reset.xml new file mode 100644 index 00000000000..e0252da058a --- /dev/null +++ b/fbw-a380x/src/base/flybywire-aircraft-a380-842/SimObjects/AirPlanes/FlyByWire_A380_842/model/behaviour/overhead/reset.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/fbw-a380x/src/systems/failures/src/a380.ts b/fbw-a380x/src/systems/failures/src/a380.ts index 237dece47c3..e42f9989f99 100644 --- a/fbw-a380x/src/systems/failures/src/a380.ts +++ b/fbw-a380x/src/systems/failures/src/a380.ts @@ -1,9 +1,8 @@ -// Copyright (c) 2023-2024 FlyByWire Simulations +// Copyright (c) 2023-2025 FlyByWire Simulations // SPDX-License-Identifier: GPL-3.0 -// One can rightfully argue that this constant shouldn't be located in @flybywiresim/failures. -// Once we create an A320 specific package, such as @flybywiresim/a320, we can move it there. import { FailureDefinition } from '@flybywiresim/fbw-sdk'; +// Keep in mind that the CPP code also keeps a list of failure codes: fbw-a380x\src\wasm\fbw_a380\src\failures\FailureList.h export const A380Failure = Object.freeze({ RapidDecompression: 21000, CabinFan1: 21001, @@ -56,9 +55,19 @@ export const A380Failure = Object.freeze({ CpcsApp3: 21048, CpcsApp4: 21049, - FmcA: 22000, - FmcB: 22001, - FmcC: 22002, + Fac1: 22000, // FIXME update CPP code failures + Fac2: 22001, // FIXME update CPP code failures + Elac1: 27000, // FIXME update CPP code failures + Elac2: 27001, // FIXME update CPP code failures + Sec1: 27002, // FIXME update CPP code failures + Sec2: 27003, // FIXME update CPP code failures + Sec3: 27004, // FIXME update CPP code failures + Fcdc1: 27005, // FIXME update CPP code failures + Fcdc2: 27006, // FIXME update CPP code failures + + FmcA: 22100, + FmcB: 22101, + FmcC: 22102, AudioManagementUnit1: 23000, AudioManagementUnit2: 23001, diff --git a/fbw-a380x/src/systems/instruments/src/MFD/FMC/FlightManagementComputer.ts b/fbw-a380x/src/systems/instruments/src/MFD/FMC/FlightManagementComputer.ts index 097939e333e..4f77aed2269 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/FMC/FlightManagementComputer.ts +++ b/fbw-a380x/src/systems/instruments/src/MFD/FMC/FlightManagementComputer.ts @@ -35,7 +35,6 @@ import { FmcInterface, FmcOperatingModes } from 'instruments/src/MFD/FMC/FmcInte import { DatabaseItem, EfisSide, - FailuresConsumer, Fix, NXDataStore, UpdateThrottler, @@ -207,13 +206,14 @@ export class FlightManagementComputer implements FmcInterface { private readonly legacyFmsIsHealthy = Subject.create(false); + private wasReset = false; + constructor( private instance: FmcIndex, operatingMode: FmcOperatingModes, private readonly bus: EventBus, - private readonly isPowered: Subscribable, + private readonly fmcInop: Subscribable, mfdReference: (DisplayInterface & MfdDisplayInterface) | null, - private readonly failuresConsumer: FailuresConsumer, ) { this.#operatingMode = operatingMode; this.#mfdReference = mfdReference; @@ -899,28 +899,34 @@ export class FlightManagementComputer implements FmcInterface { } private onUpdate(dt: number) { - if (!this.isPowered.get() || this.failuresConsumer.isActive(this.failureKey)) { - SimVar.SetSimVarValue( - `L:A32NX_FMC_${this.instance === FmcIndex.FmcA ? 'A' : this.instance === FmcIndex.FmcB ? 'B' : 'C'}_IS_HEALTHY`, - SimVarValueType.Bool, - false, - ); - this.legacyFmsIsHealthy.set(false); - return; - } else { - SimVar.SetSimVarValue( - `L:A32NX_FMC_${this.instance === FmcIndex.FmcA ? 'A' : this.instance === FmcIndex.FmcB ? 'B' : 'C'}_IS_HEALTHY`, - SimVarValueType.Bool, - true, - ); - this.legacyFmsIsHealthy.set(true); - } + const isHealthy = !this.fmcInop.get(); - // Stop early, if not FmcA - if (this.instance !== FmcIndex.FmcA) { + SimVar.SetSimVarValue( + `L:A32NX_FMC_${this.instance === FmcIndex.FmcA ? 'A' : this.instance === FmcIndex.FmcB ? 'B' : 'C'}_IS_HEALTHY`, + SimVarValueType.Bool, + isHealthy, + ); + + // Stop early, if not FmcA or if all FMCs failed + const allFmcResetsPulled = + SimVar.GetSimVarValue('L:A32NX_RESET_PANEL_FMC_A', SimVarValueType.Bool) && + SimVar.GetSimVarValue('L:A32NX_RESET_PANEL_FMC_B', SimVarValueType.Bool) && + SimVar.GetSimVarValue('L:A32NX_RESET_PANEL_FMC_B', SimVarValueType.Bool); + const allFmcInop = + !SimVar.GetSimVarValue('L:A32NX_FMC_A_IS_HEALTHY', SimVarValueType.Bool) && + !SimVar.GetSimVarValue('L:A32NX_FMC_B_IS_HEALTHY', SimVarValueType.Bool) && + !SimVar.GetSimVarValue('L:A32NX_FMC_C_IS_HEALTHY', SimVarValueType.Bool); + + this.legacyFmsIsHealthy.set(!allFmcInop); + if (this.instance !== FmcIndex.FmcA || (this.instance === FmcIndex.FmcA && allFmcInop)) { + if (this.instance === FmcIndex.FmcA && (allFmcResetsPulled || allFmcInop) && this.wasReset === false) { + this.reset(); + } return; } + this.wasReset = false; + this.flightPhaseManager.shouldActivateNextPhase(dt); const throttledDt = this.fmsUpdateThrottler.canUpdate(dt); @@ -1033,14 +1039,21 @@ export class FlightManagementComputer implements FmcInterface { } async swapNavDatabase(): Promise { - // FIXME reset ATSU when it is added to A380X - // this.atsu.resetAtisAutoUpdate(); - await this.flightPlanService.reset(); - this.fmgc.data.reset(); - this.initSimVars(); - this.deleteAllStoredWaypoints(); - this.clearLatestFmsErrorMessage(); - this.mfdReference?.uiService.navigateTo('fms/data/status'); - this.navigation.resetState(); + await this.reset(); + } + + async reset(): Promise { + if (this.instance === FmcIndex.FmcA) { + // FIXME reset ATSU when it is added to A380X + // this.atsu.resetAtisAutoUpdate(); + this.wasReset = true; + await this.flightPlanService.reset(); + this.fmgc.data.reset(); + this.initSimVars(); + this.deleteAllStoredWaypoints(); + this.clearLatestFmsErrorMessage(); + this.mfdReference?.uiService.navigateTo('fms/data/status'); + this.navigation.resetState(); + } } } diff --git a/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcInterface.ts b/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcInterface.ts index a71d9c28081..b6d0e133106 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcInterface.ts +++ b/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcInterface.ts @@ -206,4 +206,6 @@ export interface FmcInterface extends FlightPhaseManagerProxyInterface, DataInte ): void; clearCheckSpeedModeMessage(): void; + + reset(): void; } diff --git a/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcService.ts b/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcService.ts index 2a1516b62fd..9bd874c151c 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcService.ts +++ b/fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcService.ts @@ -1,11 +1,18 @@ -import { FailuresConsumer } from '@flybywiresim/fbw-sdk'; import { DisplayInterface } from '@fmgc/flightplanning/interface/DisplayInterface'; -import { ConsumerSubject, EventBus, Subscription } from '@microsoft/msfs-sdk'; +import { + ConsumerSubject, + EventBus, + MappedSubject, + SimVarValueType, + Subscribable, + Subscription, +} from '@microsoft/msfs-sdk'; import { FlightManagementComputer } from 'instruments/src/MFD/FMC/FlightManagementComputer'; import { FmcInterface, FmcOperatingModes } from 'instruments/src/MFD/FMC/FmcInterface'; import { FmcIndex, FmcServiceInterface } from 'instruments/src/MFD/FMC/FmcServiceInterface'; import { MfdDisplayInterface } from 'instruments/src/MFD/MFD'; import { MfdSimvars } from 'instruments/src/MFD/shared/MFDSimvarPublisher'; +import { ResetPanelSimvars } from 'instruments/src/MsfsAvionicsCommon/providers/ResetPanelPublisher'; /* * Handles navigation (and potentially other aspects) for MFD pages @@ -13,22 +20,64 @@ import { MfdSimvars } from 'instruments/src/MFD/shared/MFDSimvarPublisher'; export class FmcService implements FmcServiceInterface { protected subs = [] as Subscription[]; - private readonly sub = this.bus.getSubscriber(); + private readonly sub = this.bus.getSubscriber(); protected fmc: FmcInterface[] = []; private readonly dcEssBusPowered = ConsumerSubject.create(this.sub.on('dcBusEss'), false); + private readonly fmcAReset = ConsumerSubject.create(this.sub.on('a380x_reset_panel_fmc_a'), false); + private readonly fmcAInop = MappedSubject.create( + ([powered, reset, fail]) => !powered || reset || fail, + this.dcEssBusPowered, + this.fmcAReset, + this.fmcAFailed, + ); private readonly dc1BusPowered = ConsumerSubject.create(this.sub.on('dcBus1'), false); + private readonly fmcBReset = ConsumerSubject.create(this.sub.on('a380x_reset_panel_fmc_b'), false); + private readonly fmcBInop = MappedSubject.create( + ([powered, reset, fail]) => !powered || reset || fail, + this.dc1BusPowered, + this.fmcBReset, + this.fmcBFailed, + ); private readonly dc2BusPowered = ConsumerSubject.create(this.sub.on('dcBus2'), false); + private readonly fmcCReset = ConsumerSubject.create(this.sub.on('a380x_reset_panel_fmc_c'), false); + private readonly fmcCInop = MappedSubject.create( + ([powered, reset, fail]) => !powered || reset || fail, + this.dc2BusPowered, + this.fmcCReset, + this.fmcCFailed, + ); + private readonly fmsDataKnob = ConsumerSubject.create(this.sub.on('fmsDataKnob'), 1); + + private readonly fmsCaptSideFailed = MappedSubject.create( + ([knob, fmcAFailed, fmcBFailed, fmcCFailed]) => (knob === 0 ? fmcBFailed && fmcCFailed : fmcAFailed && fmcCFailed), + this.fmsDataKnob, + this.fmcAInop, + this.fmcBInop, + this.fmcCInop, + ); + private readonly fmsFoSideFailed = MappedSubject.create( + ([knob, fmcAFailed, fmcBFailed, fmcCFailed]) => (knob === 2 ? fmcAFailed && fmcCFailed : fmcBFailed && fmcCFailed), + this.fmsDataKnob, + this.fmcAInop, + this.fmcBInop, + this.fmcCInop, + ); constructor( - private bus: EventBus, - mfdReference: (DisplayInterface & MfdDisplayInterface) | null, - private readonly failuresConsumer: FailuresConsumer, + private readonly bus: EventBus, + private readonly mfdReference: (DisplayInterface & MfdDisplayInterface) | null, + private readonly fmcAFailed: Subscribable, + private readonly fmcBFailed: Subscribable, + private readonly fmcCFailed: Subscribable, ) { - this.createFmc(mfdReference); + this.createFmc(this.mfdReference); + + this.fmsCaptSideFailed.sub((f) => SimVar.SetSimVarValue('L:A32NX_FMS_L_FAILED', SimVarValueType.Bool, f)); + this.fmsFoSideFailed.sub((f) => SimVar.SetSimVarValue('L:A32NX_FMS_R_FAILED', SimVarValueType.Bool, f)); } get master() { @@ -47,38 +96,17 @@ export class FmcService implements FmcServiceInterface { // Only FMC-A is operative for now, this takes up enough resources already // Before more FMC can be added, they have to be synced this.fmc.push( - new FlightManagementComputer( - FmcIndex.FmcA, - FmcOperatingModes.Master, - this.bus, - this.dcEssBusPowered, - mfdReference, - this.failuresConsumer, - ), + new FlightManagementComputer(FmcIndex.FmcA, FmcOperatingModes.Master, this.bus, this.fmcAInop, mfdReference), ); this.fmc[FmcIndex.FmcA].operatingMode = FmcOperatingModes.Master; this.fmc.push( - new FlightManagementComputer( - FmcIndex.FmcB, - FmcOperatingModes.Slave, - this.bus, - this.dc2BusPowered, - mfdReference, - this.failuresConsumer, - ), + new FlightManagementComputer(FmcIndex.FmcB, FmcOperatingModes.Slave, this.bus, this.fmcBInop, mfdReference), ); this.fmc[FmcIndex.FmcB].operatingMode = FmcOperatingModes.Slave; this.fmc.push( - new FlightManagementComputer( - FmcIndex.FmcC, - FmcOperatingModes.Standby, - this.bus, - this.dc1BusPowered, - mfdReference, - this.failuresConsumer, - ), + new FlightManagementComputer(FmcIndex.FmcC, FmcOperatingModes.Standby, this.bus, this.fmcCInop, mfdReference), ); this.fmc[FmcIndex.FmcC].operatingMode = FmcOperatingModes.Standby; } diff --git a/fbw-a380x/src/systems/instruments/src/MFD/MFD.tsx b/fbw-a380x/src/systems/instruments/src/MFD/MFD.tsx index 166e5dd066e..3ec132b828e 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/MFD.tsx +++ b/fbw-a380x/src/systems/instruments/src/MFD/MFD.tsx @@ -27,6 +27,7 @@ import { FmsErrorType } from '@fmgc/FmsError'; import { FmcServiceInterface } from 'instruments/src/MFD/FMC/FmcServiceInterface'; import { CdsDisplayUnit, DisplayUnitID } from '../MsfsAvionicsCommon/CdsDisplayUnit'; import { InternalKccuKeyEvent, MfdSimvars } from './shared/MFDSimvarPublisher'; +import { MfdFmsPageNotAvail } from 'instruments/src/MFD/pages/FMS/MfdFmsPageNotAvail'; export const getDisplayIndex = () => { const url = document.getElementsByTagName('a380x-mfd')[0].getAttribute('url'); @@ -71,9 +72,9 @@ export class MfdComponent extends DisplayComponent implements return this.#uiService; } - public hEventConsumer = this.props.bus.getSubscriber().on('kccuKeyEvent'); + public readonly hEventConsumer = this.props.bus.getSubscriber().on('kccuKeyEvent'); - public interactionMode = Subject.create(InteractionMode.Touchscreen); + public readonly interactionMode = Subject.create(InteractionMode.Touchscreen); private readonly fmsDataKnob = ConsumerSubject.create(this.sub.on('fmsDataKnob').whenChanged(), 0); @@ -81,8 +82,6 @@ export class MfdComponent extends DisplayComponent implements private readonly fmcBIsHealthy = ConsumerSubject.create(this.sub.on('fmcBIsHealthy').whenChanged(), true); - private readonly fmcCIsHealthy = ConsumerSubject.create(this.sub.on('fmcCIsHealthy').whenChanged(), true); - private readonly activeFmsSource = MappedSubject.create( ([knob, a, b]) => { const capt = a ? 'FMS 1' : 'FMS 1-C'; @@ -343,6 +342,12 @@ export class MfdComponent extends DisplayComponent implements >
+ uri.sys)} + /> (); + private readonly resetPanelPublisher = new ResetPanelSimvarPublisher(this.bus); - private mfdFoRef = FSComponent.createRef(); + private readonly mfdCaptRef = FSComponent.createRef(); + + private readonly mfdFoRef = FSComponent.createRef(); private readonly fmcService: FmcServiceInterface; + private readonly fmcAFailed = Subject.create(false); + private readonly fmcBFailed = Subject.create(false); + private readonly fmcCFailed = Subject.create(false); + private readonly failuresConsumer = new FailuresConsumer('A32NX'); constructor(public readonly instrument: BaseInstrument) { - this.simVarPublisher = new MfdSimvarPublisher(this.bus); - this.hEventPublisher = new HEventPublisher(this.bus); - this.fgDataPublisher = new FGDataPublisher(this.bus); - this.fmsDataPublisher = new FmsMfdPublisher(this.bus); - this.backplane.addPublisher('mfd', this.simVarPublisher); this.backplane.addPublisher('hEvent', this.hEventPublisher); this.backplane.addPublisher('clock', this.clockPublisher); this.backplane.addPublisher('fg', this.fgDataPublisher); this.backplane.addPublisher('fms', this.fmsDataPublisher); - - this.fmcService = new FmcService(this.bus, this.mfdCaptRef.getOrDefault(), this.failuresConsumer); + this.backplane.addPublisher('resetPanel', this.resetPanelPublisher); + + this.fmcService = new FmcService( + this.bus, + this.mfdCaptRef.getOrDefault(), + this.fmcAFailed, + this.fmcBFailed, + this.fmcCFailed, + ); this.doInit(); } @@ -106,6 +116,9 @@ class MfdInstrument implements FsInstrument { public Update(): void { this.backplane.onUpdate(); this.failuresConsumer.update(); + this.fmcAFailed.set(this.failuresConsumer.isActive(A380Failure.FmcA)); + this.fmcBFailed.set(this.failuresConsumer.isActive(A380Failure.FmcB)); + this.fmcCFailed.set(this.failuresConsumer.isActive(A380Failure.FmcC)); } public onInteractionEvent(args: string[]): void { diff --git a/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/F-PLN/MfdFmsFpln.scss b/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/F-PLN/MfdFmsFpln.scss index ad2bb0111c0..ac925ed70c0 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/F-PLN/MfdFmsFpln.scss +++ b/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/F-PLN/MfdFmsFpln.scss @@ -276,7 +276,7 @@ display: flex; flex-direction: column; background-color: $display-background; - } +} .mfd-fms-fpln-duplicate-table { display: flex; diff --git a/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsInit.tsx b/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsInit.tsx index e0e9fb70b77..89d09968c69 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsInit.tsx +++ b/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsInit.tsx @@ -17,7 +17,7 @@ import { Button, ButtonMenuItem } from 'instruments/src/MFD/pages/common/Button' import { maxCertifiedAlt } from '@shared/PerformanceConstants'; import { FmsPage } from 'instruments/src/MFD/pages/common/FmsPage'; import { NXDataStore } from '@flybywiresim/fbw-sdk'; -import { ISimbriefData } from '../../../../../../../../fbw-common/src/systems/instruments/src/EFB/Apis/Simbrief/simbriefInterface'; +import { ISimbriefData } from '@flybywiresim/flypad'; import { SimBriefUplinkAdapter } from '@fmgc/flightplanning/uplink/SimBriefUplinkAdapter'; import { FmgcFlightPhase } from '@shared/flightphase'; import { NXFictionalMessages } from 'instruments/src/MFD/shared/NXSystemMessages'; @@ -30,7 +30,7 @@ interface MfdFmsInitProps extends AbstractMfdPageProps {} export class MfdFmsInit extends FmsPage { private simBriefOfp: ISimbriefData | null = null; - private cpnyFplnButtonLabel: Subscribable = this.props.fmcService.master + private readonly cpnyFplnButtonLabel: Subscribable = this.props.fmcService.master ? this.props.fmcService.master.fmgc.data.cpnyFplnAvailable.map((it) => { if (!it) { return ( @@ -51,7 +51,7 @@ export class MfdFmsInit extends FmsPage { }) : Subject.create(<>); - private cpnyFplnButtonMenuItems: Subscribable = this.props.fmcService.master + private readonly cpnyFplnButtonMenuItems: Subscribable = this.props.fmcService.master ? this.props.fmcService.master.fmgc.data.cpnyFplnAvailable.map((it) => it ? [ @@ -68,48 +68,54 @@ export class MfdFmsInit extends FmsPage { ) : Subject.create([]); - private fromIcao = Subject.create(null); + private readonly fromIcao = Subject.create(null); - private toIcao = Subject.create(null); + private readonly toIcao = Subject.create(null); - private cityPairDisabled = MappedSubject.create( - ([fp, tmpy]) => fp > FmgcFlightPhase.Preflight || tmpy, + private readonly cityPairDisabled = MappedSubject.create( + ([fp, tmpy, fromIcao, toIcao]) => (fp > FmgcFlightPhase.Preflight && (!fromIcao || !toIcao)) || tmpy, this.activeFlightPhase, this.tmpyActive, + this.fromIcao, + this.toIcao, ); - private altnIcao = Subject.create(null); + private readonly altnIcao = Subject.create(null); - private altnDisabled = MappedSubject.create(([toIcao, fromIcao]) => !toIcao || !fromIcao, this.fromIcao, this.toIcao); + private readonly altnDisabled = MappedSubject.create( + ([toIcao, fromIcao]) => !toIcao || !fromIcao, + this.fromIcao, + this.toIcao, + ); - private cpnyRte = Subject.create(null); // FIXME not found + private readonly cpnyRte = Subject.create(null); // FIXME not found - private altnRte = Subject.create(null); // FIXME not found + private readonly altnRte = Subject.create(null); // FIXME not found - private crzFl = Subject.create(null); + private readonly crzFl = Subject.create(null); - private costIndex = Subject.create(null); + private readonly costIndex = Subject.create(null); - private costIndexDisabled = MappedSubject.create( + private readonly costIndexDisabled = MappedSubject.create( ([toIcao, fromIcao, flightPhase]) => !toIcao || !fromIcao || flightPhase >= FmgcFlightPhase.Descent, this.fromIcao, this.toIcao, this.activeFlightPhase, ); - private tripWindDisabled = MappedSubject.create( + private readonly tripWindDisabled = MappedSubject.create( ([toIcao, fromIcao]) => !toIcao || !fromIcao, this.fromIcao, this.toIcao, ); - private cpnyRteMandatory = MappedSubject.create( + private readonly cpnyRteMandatory = MappedSubject.create( ([toIcao, fromIcao]) => !toIcao || !fromIcao, this.fromIcao, this.toIcao, ); - private departureButtonDisabled = MappedSubject.create( + private readonly departureButtonDisabled = MappedSubject.create( ([toIcao, fromIcao, phase]) => !toIcao || !fromIcao || phase !== FmgcFlightPhase.Preflight, this.fromIcao, this.toIcao, diff --git a/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsPageNotAvail.tsx b/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsPageNotAvail.tsx new file mode 100644 index 00000000000..af95338c544 --- /dev/null +++ b/fbw-a380x/src/systems/instruments/src/MFD/pages/FMS/MfdFmsPageNotAvail.tsx @@ -0,0 +1,63 @@ +import { + ConsumerSubject, + DisplayComponent, + EventBus, + FSComponent, + MappedSubject, + Subscribable, + Subscription, + VNode, +} from '@microsoft/msfs-sdk'; + +import './F-PLN/MfdFmsFpln.scss'; +import { FmcServiceInterface } from 'instruments/src/MFD/FMC/FmcServiceInterface'; +import { MfdSimvars } from 'instruments/src/MFD/shared/MFDSimvarPublisher'; +import { MfdSystem } from 'instruments/src/MFD/pages/common/MfdUiService'; + +interface MfdFmsPageNotAvailProps { + bus: EventBus; + fmcService: FmcServiceInterface; + captOrFo: 'CAPT' | 'FO'; + requestedSystem: Subscribable; +} + +export class MfdFmsPageNotAvail extends DisplayComponent { + // Make sure to collect all subscriptions here, otherwise page navigation doesn't work. + private subs = [] as Subscription[]; + + private readonly fmsFailed = ConsumerSubject.create( + this.props.bus.getSubscriber().on(this.props.captOrFo === 'FO' ? 'fmsFoFailed' : 'fmsCaptFailed'), + false, + ); + + private readonly warningDisplay = MappedSubject.create( + ([failed, sys]) => (failed && sys === MfdSystem.Fms ? 'inherit' : 'none'), + this.fmsFailed, + this.props.requestedSystem, + ); + + public onAfterRender(node: VNode): void { + super.onAfterRender(node); + } + + public destroy(): void { + // Destroy all subscriptions to remove all references to this instance. + this.subs.forEach((x) => x.destroy()); + + super.destroy(); + } + + render(): VNode { + return ( +
+
+ {/* begin page content */} +
+
FMS PAGE NOT AVAIL
+
+ {/* end page content */} +
+
+ ); + } +} diff --git a/fbw-a380x/src/systems/instruments/src/MFD/pages/common/style.scss b/fbw-a380x/src/systems/instruments/src/MFD/pages/common/style.scss index 4a7f4deac90..ad184d43434 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/pages/common/style.scss +++ b/fbw-a380x/src/systems/instruments/src/MFD/pages/common/style.scss @@ -851,3 +851,12 @@ color: $display-dark-grey; font-family: "Ecam", monospace; } + +.mfd-amber-error-message { + display: flex; + flex: 1; + justify-content: center; + align-items: center; + color: $display-amber; + font-size: 24px; +} diff --git a/fbw-a380x/src/systems/instruments/src/MFD/shared/MFDSimvarPublisher.tsx b/fbw-a380x/src/systems/instruments/src/MFD/shared/MFDSimvarPublisher.tsx index 7d293ed50d1..e4915ad4d04 100644 --- a/fbw-a380x/src/systems/instruments/src/MFD/shared/MFDSimvarPublisher.tsx +++ b/fbw-a380x/src/systems/instruments/src/MFD/shared/MFDSimvarPublisher.tsx @@ -30,6 +30,8 @@ export type MfdSimvars = { fmcAIsHealthy: boolean; fmcBIsHealthy: boolean; fmcCIsHealthy: boolean; + fmsCaptFailed: boolean; + fmsFoFailed: boolean; }; export type InternalKccuKeyEvent = { @@ -66,6 +68,8 @@ export enum MfdVars { fmcAIsHealthy = 'L:A32NX_FMC_A_IS_HEALTHY', fmcBIsHealthy = 'L:A32NX_FMC_B_IS_HEALTHY', fmcCIsHealthy = 'L:A32NX_FMC_C_IS_HEALTHY', + fmsCaptFailed = 'L:A32NX_FMS_L_FAILED', + fmsFoFailed = 'L:A32NX_FMS_R_FAILED', } /** A publisher to poll and publish nav/com simvars. */ @@ -99,6 +103,8 @@ export class MfdSimvarPublisher extends SimVarPublisher { ['fmcAIsHealthy', { name: MfdVars.fmcAIsHealthy, type: SimVarValueType.Bool }], ['fmcBIsHealthy', { name: MfdVars.fmcBIsHealthy, type: SimVarValueType.Bool }], ['fmcCIsHealthy', { name: MfdVars.fmcCIsHealthy, type: SimVarValueType.Bool }], + ['fmsCaptFailed', { name: MfdVars.fmsCaptFailed, type: SimVarValueType.Bool }], + ['fmsFoFailed', { name: MfdVars.fmsFoFailed, type: SimVarValueType.Bool }], ]); public constructor(bus: EventBus) { diff --git a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/FmsDataPublisher.ts b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/FmsDataPublisher.ts index 6b9f29c4016..23d9a952816 100644 --- a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/FmsDataPublisher.ts +++ b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/FmsDataPublisher.ts @@ -21,6 +21,7 @@ export interface FmsVars { apprMessage1: number; mrpLat: number; mrpLong: number; + fmsFailed: boolean; } export class FmsDataPublisher extends SwitchableSimVarProvider { @@ -58,6 +59,7 @@ export class FmsDataPublisher extends SwitchableSimVarProvider `L:A32NX_EFIS_${side}_APPR_MSG_1`, type: SimVarValueType.Number }], ['mrpLat', { name: (side) => `L:A32NX_EFIS_${side}_MRP_LAT`, type: SimVarValueType.Degree }], ['mrpLong', { name: (side) => `L:A32NX_EFIS_${side}_MRP_LONG`, type: SimVarValueType.Degree }], + ['fmsFailed', { name: (side) => `L:A32NX_FMS_${side}_FAILED`, type: SimVarValueType.Bool }], ]), stateSubject, bus, diff --git a/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/ResetPanelPublisher.tsx b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/ResetPanelPublisher.tsx new file mode 100644 index 00000000000..5a2a98a24b7 --- /dev/null +++ b/fbw-a380x/src/systems/instruments/src/MsfsAvionicsCommon/providers/ResetPanelPublisher.tsx @@ -0,0 +1,27 @@ +// Copyright (c) 2025 FlyByWire Simulations +// +// SPDX-License-Identifier: GPL-3.0 + +import { EventBus, SimVarDefinition, SimVarValueType, SimVarPublisher } from '@microsoft/msfs-sdk'; + +/** + * Events for reset panel on overhead panel. If pulled out, the LVar is set to true, if pushed in it's set to false. + * Functionally, these behave similarly to circuit breakers, however they only interrupt software. If pulled out, execution of SW is halted. + */ +export type ResetPanelSimvars = { + a380x_reset_panel_fmc_a: boolean; + a380x_reset_panel_fmc_b: boolean; + a380x_reset_panel_fmc_c: boolean; +}; + +export class ResetPanelSimvarPublisher extends SimVarPublisher { + private static simvars = new Map([ + ['a380x_reset_panel_fmc_a', { name: 'L:A32NX_RESET_PANEL_FMC_A', type: SimVarValueType.Bool }], + ['a380x_reset_panel_fmc_b', { name: 'L:A32NX_RESET_PANEL_FMC_B', type: SimVarValueType.Bool }], + ['a380x_reset_panel_fmc_c', { name: 'L:A32NX_RESET_PANEL_FMC_C', type: SimVarValueType.Bool }], + ]); + + public constructor(bus: EventBus) { + super(ResetPanelSimvarPublisher.simvars, bus); + } +} diff --git a/fbw-common/src/systems/instruments/src/EFB/index.ts b/fbw-common/src/systems/instruments/src/EFB/index.ts index 0945e0aabbd..9b6f797f90e 100644 --- a/fbw-common/src/systems/instruments/src/EFB/index.ts +++ b/fbw-common/src/systems/instruments/src/EFB/index.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 export * from './AircraftContext'; +export * from './Apis/Simbrief'; export * from './Efb'; export * from './Enum/Airframe'; export * from './Assets/Error'; diff --git a/fbw-common/src/systems/instruments/src/ND/pages/arc/index.tsx b/fbw-common/src/systems/instruments/src/ND/pages/arc/index.tsx index 36d4cf3aae1..6a7c075edf8 100644 --- a/fbw-common/src/systems/instruments/src/ND/pages/arc/index.tsx +++ b/fbw-common/src/systems/instruments/src/ND/pages/arc/index.tsx @@ -27,6 +27,7 @@ import { Flag } from '../../shared/Flag'; import { NDPage } from '../NDPage'; import { NDControlEvents } from '../../NDControlEvents'; import { GenericFcuEvents } from '../../types/GenericFcuEvents'; +import { GenericFmsEvents } from '../../types/GenericFmsEvents'; export interface ArcModePageProps extends ComponentProps { bus: ArincEventBus; @@ -82,14 +83,22 @@ export class ArcModePage extends NDPage> { this.props.trackWord, ); + private readonly fmsFailed = ConsumerSubject.create( + this.props.bus.getSubscriber().on('fmsFailed').whenChanged(), + false, + ); + // TODO in the future, this should be looking at stuff like FM position invalid or not map frames transmitted private readonly mapFlagShown = MappedSubject.create( - ([headingWord, latWord, longWord]) => { - return !headingWord.isNormalOperation() || !latWord.isNormalOperation() || !longWord.isNormalOperation(); + ([headingWord, latWord, longWord, fmsFailed]) => { + return ( + !headingWord.isNormalOperation() || !latWord.isNormalOperation() || !longWord.isNormalOperation() || fmsFailed + ); }, this.props.headingWord, this.pposLatWord, this.pposLonWord, + this.fmsFailed, ); onShow() { diff --git a/fbw-common/src/systems/instruments/src/ND/pages/rose/RoseNavPage.tsx b/fbw-common/src/systems/instruments/src/ND/pages/rose/RoseNavPage.tsx index 5262443af2e..322a64afd74 100644 --- a/fbw-common/src/systems/instruments/src/ND/pages/rose/RoseNavPage.tsx +++ b/fbw-common/src/systems/instruments/src/ND/pages/rose/RoseNavPage.tsx @@ -12,6 +12,7 @@ import { RoseMode } from './RoseMode'; import { RoseModeUnderlay } from './RoseModeUnderlay'; import { NDControlEvents } from '../../NDControlEvents'; import { GenericFcuEvents } from '../../types/GenericFcuEvents'; +import { GenericFmsEvents } from '../../types/GenericFmsEvents'; export class RoseNavPage extends RoseMode { private readonly pposLatWord = Arinc429RegisterSubject.createEmpty(); @@ -23,13 +24,21 @@ export class RoseNavPage extends RoseMode { -1, ); + private readonly fmsFailed = ConsumerSubject.create( + this.props.bus.getSubscriber().on('fmsFailed').whenChanged(), + false, + ); + private readonly mapFlagShown = MappedSubject.create( - ([headingWord, latWord, longWord]) => { - return !headingWord.isNormalOperation() || !latWord.isNormalOperation() || !longWord.isNormalOperation(); + ([headingWord, latWord, longWord, fmsFailed]) => { + return ( + !headingWord.isNormalOperation() || !latWord.isNormalOperation() || !longWord.isNormalOperation() || fmsFailed + ); }, this.props.headingWord, this.pposLatWord, this.pposLonWord, + this.fmsFailed, ); private readonly planeRotation = MappedSubject.create( diff --git a/fbw-common/src/systems/instruments/src/ND/types/GenericFmsEvents.ts b/fbw-common/src/systems/instruments/src/ND/types/GenericFmsEvents.ts index 99cbaaf10de..38aa0f089a1 100644 --- a/fbw-common/src/systems/instruments/src/ND/types/GenericFmsEvents.ts +++ b/fbw-common/src/systems/instruments/src/ND/types/GenericFmsEvents.ts @@ -18,4 +18,5 @@ export interface GenericFmsEvents { apprMessage1: number; mrpLat: number; mrpLong: number; + fmsFailed: boolean; }