Skip to content

Commit

Permalink
feat(a380x/fms): FMC reset through overhead reset panel (#9685)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
flogross89 authored Jan 13, 2025
1 parent a90b1ae commit 781db96
Show file tree
Hide file tree
Showing 20 changed files with 339 additions and 105 deletions.
1 change: 1 addition & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Include RelativeFile="behaviour\overhead\cargo-air-cond.xml" />
<Include RelativeFile="behaviour\overhead\fire.xml" />
<Include RelativeFile="behaviour\overhead\flight-control.xml" />
<Include RelativeFile="behaviour\overhead\reset.xml" />
<Include RelativeFile="behaviour\pedestal\pedestal-inputs.xml" />
<Include RelativeFile="behaviour\door_animations.xml" />
<Include RelativeFile="behaviour\efis-cp.xml" />
Expand Down Expand Up @@ -4979,6 +4980,18 @@
<SEQ2_CODE_DRIVES_VISIBILITY>False</SEQ2_CODE_DRIVES_VISIBILITY>
</UseTemplate>
</Component>

<Component ID="Overhead_Reset_Panel">
<UseTemplate Name="FBW_Airbus_RESET_PANEL_BUTTON">
<NAME>FMC_A</NAME>
</UseTemplate>
<UseTemplate Name="FBW_Airbus_RESET_PANEL_BUTTON">
<NAME>FMC_B</NAME>
</UseTemplate>
<UseTemplate Name="FBW_Airbus_RESET_PANEL_BUTTON">
<NAME>FMC_C</NAME>
</UseTemplate>
</Component>
</Component>

<CameraTitle>OverheadLower</CameraTitle>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- Copyright (c) 2024 FlyByWire Simulations -->
<!-- SPDX-License-Identifier: GPL-3.0 -->

<ModelBehaviors>
<Template Name="FBW_Airbus_RESET_PANEL_BUTTON">
<Parameters Type="Default">
<NODE_ID>CB_#NAME#</NODE_ID>
<DISABLE_SEQ1 />
<DISABLE_SEQ2 />
<ROUND />
</Parameters>

<UseTemplate Name="FBW_Push_Toggle">
<TOGGLE_SIMVAR>L:A32NX_RESET_PANEL_#NAME#</TOGGLE_SIMVAR>
</UseTemplate>
</Template>
</ModelBehaviors>
21 changes: 15 additions & 6 deletions fbw-a380x/src/systems/failures/src/a380.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { FmcInterface, FmcOperatingModes } from 'instruments/src/MFD/FMC/FmcInte
import {
DatabaseItem,
EfisSide,
FailuresConsumer,
Fix,
NXDataStore,
UpdateThrottler,
Expand Down Expand Up @@ -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<boolean>,
private readonly fmcInop: Subscribable<boolean>,
mfdReference: (DisplayInterface & MfdDisplayInterface) | null,
private readonly failuresConsumer: FailuresConsumer,
) {
this.#operatingMode = operatingMode;
this.#mfdReference = mfdReference;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1033,14 +1039,21 @@ export class FlightManagementComputer implements FmcInterface {
}

async swapNavDatabase(): Promise<void> {
// 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<void> {
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();
}
}
}
2 changes: 2 additions & 0 deletions fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,6 @@ export interface FmcInterface extends FlightPhaseManagerProxyInterface, DataInte
): void;

clearCheckSpeedModeMessage(): void;

reset(): void;
}
90 changes: 59 additions & 31 deletions fbw-a380x/src/systems/instruments/src/MFD/FMC/FmcService.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,83 @@
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
*/
export class FmcService implements FmcServiceInterface {
protected subs = [] as Subscription[];

private readonly sub = this.bus.getSubscriber<MfdSimvars>();
private readonly sub = this.bus.getSubscriber<MfdSimvars & ResetPanelSimvars>();

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<boolean>,
private readonly fmcBFailed: Subscribable<boolean>,
private readonly fmcCFailed: Subscribable<boolean>,
) {
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() {
Expand All @@ -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;
}
Expand Down
13 changes: 9 additions & 4 deletions fbw-a380x/src/systems/instruments/src/MFD/MFD.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -71,18 +72,16 @@ export class MfdComponent extends DisplayComponent<MfdComponentProps> implements
return this.#uiService;
}

public hEventConsumer = this.props.bus.getSubscriber<InternalKccuKeyEvent>().on('kccuKeyEvent');
public readonly hEventConsumer = this.props.bus.getSubscriber<InternalKccuKeyEvent>().on('kccuKeyEvent');

public interactionMode = Subject.create<InteractionMode>(InteractionMode.Touchscreen);
public readonly interactionMode = Subject.create<InteractionMode>(InteractionMode.Touchscreen);

private readonly fmsDataKnob = ConsumerSubject.create(this.sub.on('fmsDataKnob').whenChanged(), 0);

private readonly fmcAIsHealthy = ConsumerSubject.create(this.sub.on('fmcAIsHealthy').whenChanged(), true);

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';
Expand Down Expand Up @@ -343,6 +342,12 @@ export class MfdComponent extends DisplayComponent<MfdComponentProps> implements
>
<div class="mfd-main" ref={this.topRef}>
<div ref={this.activeHeaderRef} />
<MfdFmsPageNotAvail
bus={this.props.bus}
fmcService={this.props.fmcService}
captOrFo={this.props.captOrFo}
requestedSystem={this.uiService.activeUri.map((uri) => uri.sys)}
/>
<MfdMsgList visible={this.messageListOpened} bus={this.props.bus} fmcService={this.props.fmcService} />
<MfdFmsFplnDuplicateNames
ref={this.duplicateNamesRef}
Expand Down
Loading

0 comments on commit 781db96

Please sign in to comment.