Skip to content

Commit

Permalink
fix(a380/mfd): trip fuel calculation & block not editable across flig…
Browse files Browse the repository at this point in the history
…hts in fuel & load (#9788)

* fix(mfd): move calculations of trip fuel & extra to fmc

* feat(mfd): add CI in use msg if no CI after engine start

* fix(mfd): clear taxi fuel after to. Fix engines running condition

* fix(mfd): block fuel reset, dont allow taxi fuel delete

* docs: add changelog entry

* fix lint

* docs: remove inop rte rsv

* change enginesWereStarted order to before weight check
  • Loading branch information
BravoMike99 authored Jan 23, 2025
1 parent 78ef3c9 commit 4b4b498
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 58 deletions.
1 change: 1 addition & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
1. [A380X/SD] Display oil quantity on ENG page - @flogross89 (floridude)
1. [A380X/FMS] Accept KCCU plus/minus key as first entry into field - @flogross89 (floridude)
1. [ATSU] Add MSFS as TAF source in MSFS2024 - @tracernz (Mike)
1. [A380X/MFD] Fix wrong Landing weight calculation & block fuel not editable across flights in FUEL & LOAD - @BravoMike99 (bruno_pt99)

## 0.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
EfisSide,
Fix,
NXDataStore,
Units,
UpdateThrottler,
Waypoint,
a380EfisRangeSettings,
Expand Down Expand Up @@ -268,6 +269,7 @@ export class FlightManagementComputer implements FmcInterface {
!Number.isFinite(this.flightPlanService.active.performanceData.costIndex)
) {
this.flightPlanService.active.setPerformanceData('costIndex', 0);
this.addMessageToQueue(NXSystemMessages.costIndexInUse.getModifiedMessage('0'));
}
}),
this.legacyFmsIsHealthy.sub((v) => {
Expand Down Expand Up @@ -372,7 +374,7 @@ export class FlightManagementComputer implements FmcInterface {
// TOW before engine start: TOW = ZFW + BLOCK - TAXI
const zfw = this.fmgc.data.zeroFuelWeight.get() ?? maxZfw;
if (this.fmgc.data.zeroFuelWeight.get() && this.fmgc.data.blockFuel.get() && this.fmgc.data.taxiFuel.get()) {
return zfw + (this.fmgc.data.blockFuel.get() ?? maxBlockFuel) - this.fmgc.data.taxiFuel.get();
return zfw + (this.fmgc.data.blockFuel.get() ?? maxBlockFuel) - (this.fmgc.data.taxiFuel.get() ?? 0);
}
return null;
}
Expand All @@ -388,7 +390,35 @@ export class FlightManagementComputer implements FmcInterface {
}

public getTripFuel(): number | null {
return 25_000; // FIXME Dummy value
const destPred = this.guidanceController.vnavDriver.getDestinationPrediction();
if (destPred) {
const fob = this.fmgc.getFOB() * 1_000;
const destFuelKg = Units.poundToKilogram(destPred.estimatedFuelOnBoard);
return fob - destFuelKg;
}
return null;
}

public getExtraFuel(): number | null {
const destPred = this.guidanceController.vnavDriver.getDestinationPrediction();
if (destPred) {
if (this.flightPhase.get() === FmgcFlightPhase.Preflight) {
// EXTRA = BLOCK - TAXI - TRIP - MIN FUEL DEST - RTE RSV
return (
(this.enginesWereStarted.get() ? this.fmgc.getFOB() * 1_000 : this.fmgc.data.blockFuel.get() ?? 0) -
(this.fmgc.data.taxiFuel.get() ?? 0) -
(this.getTripFuel() ?? 0) -
(this.fmgc.data.minimumFuelAtDestination.get() ?? 0) -
(this.fmgc.data.routeReserveFuelWeight.get() ?? 0)
);
} else {
return (
Units.poundToKilogram(destPred.estimatedFuelOnBoard) - (this.fmgc.data.minimumFuelAtDestination.get() ?? 0)
);
}
}

return null;
}

public getRecMaxFlightLevel(): number | null {
Expand Down Expand Up @@ -664,6 +694,8 @@ export class FlightManagementComputer implements FmcInterface {
this.acInterface.updateThrustReductionAcceleration();
}

pd.taxiFuelPilotEntry.set(null);
pd.defaultTaxiFuel.set(null);
pd.routeReserveFuelWeightPilotEntry.set(null);
pd.routeReserveFuelPercentagePilotEntry.set(0);
pd.routeReserveFuelWeightCalculated.set(0);
Expand Down Expand Up @@ -934,6 +966,15 @@ export class FlightManagementComputer implements FmcInterface {
if (throttledDt !== -1) {
this.navigation.update(throttledDt);
if (this.flightPlanService.hasActive) {
const flightPhase = this.flightPhase.get();
this.enginesWereStarted.set(
flightPhase >= FmgcFlightPhase.Takeoff ||
(flightPhase == FmgcFlightPhase.Preflight && SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:1', 'number') > 20) ||
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:2', 'number') > 20 ||
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:3', 'number') > 20 ||
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:4', 'number') > 20,
);

this.acInterface.updateThrustReductionAcceleration();
this.acInterface.updateTransitionAltitudeLevel();
this.acInterface.updatePerformanceData();
Expand All @@ -956,19 +997,6 @@ export class FlightManagementComputer implements FmcInterface {
this.acInterface.updateMinimums(destPred.distanceFromAircraft);
}
this.acInterface.updateIlsCourse(this.navigation.getNavaidTuner().getMmrRadioTuningStatus(1));

if (!this.enginesWereStarted.get()) {
const flightPhase = this.fmgc.getFlightPhase();
const oneEngineWasStarted =
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:1', 'percent') > 20 ||
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:2', 'percent') > 20 ||
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:3', 'percent') > 20 ||
SimVar.GetSimVarValue('L:A32NX_ENGINE_N2:4', 'percent') > 20;
this.enginesWereStarted.set(
flightPhase >= FmgcFlightPhase.Takeoff ||
(flightPhase === FmgcFlightPhase.Preflight && oneEngineWasStarted),
);
}
}
this.checkGWParams();
this.updateMessageQueue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ export class FmcAircraftInterface {
case FmgcFlightPhase.GoAround: {
if (this.fmaVerticalMode.get() === VerticalMode.SRS_GA) {
const speed = Math.min(
this.fmgc.data.approachVls.get() + (engineOut ? 15 : 25),
this.fmgc.data.approachVls.get() ?? Infinity + (engineOut ? 15 : 25),
Math.max(
SimVar.GetSimVarValue('L:A32NX_GOAROUND_INIT_SPEED', 'number'),
this.fmgc.data.approachSpeed.get() ?? 0,
Expand Down Expand Up @@ -1233,7 +1233,7 @@ export class FmcAircraftInterface {
SimVar.SetSimVarValue('L:A32NX_FM_GROSS_WEIGHT', 'Number', gw);
}

if (this.fmc.enginesWereStarted.get()) {
if (this.fmc.enginesWereStarted.get() && this.flightPhase.get() !== FmgcFlightPhase.Done) {
this.fmc.fmgc.data.blockFuel.set(this.fmc.fmgc.getFOB() * 1_000);
}
}
Expand Down
3 changes: 3 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 @@ -151,6 +151,9 @@ export interface FmcInterface extends FlightPhaseManagerProxyInterface, DataInte
/** in kilograms */
getTripFuel(): number | null;

/** in kilograms */
getExtraFuel(): number | null;

/** as flight level */
getRecMaxFlightLevel(): number | null;

Expand Down
17 changes: 10 additions & 7 deletions fbw-a380x/src/systems/instruments/src/MFD/FMC/fmgc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,15 @@ export class FmgcData {
/** in kg. null if not set. */
public readonly taxiFuelPilotEntry = Subject.create<number | null>(null);

/** in kg */
public readonly taxiFuel = this.taxiFuelPilotEntry.map((it) =>
it === null ? AirlineModifiableInformation.EK.taxiFuel : it,
);
public readonly taxiFuelIsPilotEntered = this.taxiFuelPilotEntry.map((v) => v !== null);

public readonly defaultTaxiFuel = Subject.create<number | null>(AirlineModifiableInformation.EK.taxiFuel);

public readonly taxiFuelIsPilotEntered = this.taxiFuelPilotEntry.map((it) => it !== null);
public readonly taxiFuel = MappedSubject.create(
([pilotEntryTaxiFuel, defaultTaxiFuel]) => (pilotEntryTaxiFuel !== null ? pilotEntryTaxiFuel : defaultTaxiFuel),
this.taxiFuelPilotEntry,
this.defaultTaxiFuel,
);

/** in kg. null if not set. */
public readonly routeReserveFuelWeightPilotEntry = Subject.create<number | null>(null);
Expand Down Expand Up @@ -304,11 +307,11 @@ export class FmgcData {
/** in feet. null if not set. */
public readonly approachRadioMinimum = Subject.create<number | null>(null);

public readonly approachVref = Subject.create<Knots>(129);
public readonly approachVref = Subject.create<Knots | null>(null);

public readonly approachFlapConfig = Subject.create<FlapConf>(FlapConf.CONF_FULL);

public readonly approachVls = Subject.create<Knots>(134);
public readonly approachVls = Subject.create<Knots | null>(null);

/**
* Estimated take-off time, in seconds. Displays as HH:mm:ss. Null if not set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { FmsPage } from 'instruments/src/MFD/pages/common/FmsPage';
import { MfdSimvars } from 'instruments/src/MFD/shared/MFDSimvarPublisher';
import { FmgcFlightPhase } from '@shared/flightphase';
import { AirlineModifiableInformation } from '@shared/AirlineModifiableInformation';
import { Units } from '@flybywiresim/fbw-sdk';
import { getEtaFromUtcOrPresent } from '../../shared/utils';

interface MfdFmsFuelLoadProps extends AbstractMfdPageProps {}
Expand Down Expand Up @@ -166,14 +165,11 @@ export class MfdFmsFuelLoad extends FmsPage<MfdFmsFuelLoadProps> {
}

const destPred = this.props.fmcService.master.guidanceController.vnavDriver.getDestinationPrediction();
const tripFuel = this.props.fmcService.master.getTripFuel();
this.tripFuelWeight.set(tripFuel);
this.tripFuelTime.set(getEtaFromUtcOrPresent(destPred?.secondsFromPresent, true));
this.extraFuelWeight.set(this.props.fmcService.master.getExtraFuel());
if (this.activeFlightPhase.get() === FmgcFlightPhase.Preflight) {
// EXTRA = BLOCK - TAXI - TRIP - MIN FUEL DEST - RTE RSV
const fob = this.props.fmcService.master.fmgc.getFOB() * 1_000;
const tripFuel =
fob - (destPred?.estimatedFuelOnBoard ? Units.poundToKilogram(destPred?.estimatedFuelOnBoard) : fob);
this.tripFuelWeight.set(tripFuel);
this.tripFuelTime.set(getEtaFromUtcOrPresent(destPred?.secondsFromPresent, true));

// Calculate Rte Rsv fuel if not manually entered
const pilotEnteredReserveFuel = this.props.fmcService.master.fmgc.data.routeReserveFuelIsPilotEntered.get();
this.props.fmcService.master.fmgc.data.routeReserveFuelWeightCalculated.set(
Expand All @@ -184,28 +180,7 @@ export class MfdFmsFuelLoad extends FmsPage<MfdFmsFuelLoadProps> {
if (!pilotEnteredReserveFuel) {
this.props.fmcService.master.fmgc.data.routeReserveFuelWeightPilotEntry.set(null);
}

const block = this.props.fmcService.master.fmgc.data.blockFuel.get() ?? 0;
this.extraFuelWeight.set(
(this.props.fmcService.master.enginesWereStarted.get() ? fob : block) -
(this.props.fmcService.master.fmgc.data.taxiFuel.get() ?? 0) -
(this.tripFuelWeight.get() ?? 0) -
(this.props.fmcService.master.fmgc.data.minimumFuelAtDestination.get() ?? 0) -
(this.props.fmcService.master.fmgc.data.routeReserveFuelWeight.get() ?? 0),
);
} else {
if (destPred) {
const fobKg = this.props.fmcService.master.fmgc.getFOB() * 1000;
const destFuelKg = Units.poundToKilogram(destPred?.estimatedFuelOnBoard);
const remainingTripFuel = fobKg - destFuelKg;
this.tripFuelWeight.set(remainingTripFuel);
this.tripFuelTime.set(getEtaFromUtcOrPresent(destPred.secondsFromPresent, true));
this.extraFuelWeight.set(
destFuelKg - (this.props.fmcService.master.fmgc.data.minimumFuelAtDestination.get() ?? 0),
);
}
}

this.updateDestAndAltnPredictions();
}),
);
Expand Down Expand Up @@ -306,12 +281,12 @@ export class MfdFmsFuelLoad extends FmsPage<MfdFmsFuelLoadProps> {
<div style="margin-bottom: 20px;">
<InputField<number>
dataEntryFormat={new WeightFormat(Subject.create(0), Subject.create(maxTaxiFuel))}
dataHandlerDuringValidation={async (v) =>
this.props.fmcService.master?.fmgc.data.taxiFuelPilotEntry.set(v)
}
dataHandlerDuringValidation={async (v) => {
this.props.fmcService.master?.fmgc.data.taxiFuelPilotEntry.set(v);
}}
enteredByPilot={this.props.fmcService.master.fmgc.data.taxiFuelIsPilotEntered}
value={this.props.fmcService.master.fmgc.data.taxiFuel}
inactive={this.activeFlightPhase.map((it) => it >= FmgcFlightPhase.Takeoff)}
disabled={this.activeFlightPhase.map((it) => it >= FmgcFlightPhase.Takeoff)}
alignText="flex-end"
containerStyle="width: 150px;"
errorHandler={(e) => this.props.fmcService.master?.showFmsErrorMessage(e)}
Expand Down
2 changes: 1 addition & 1 deletion fbw-a380x/src/systems/instruments/src/MFD/pages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Status legend:
| Status | URI | Sprint/Prio | Missing functionality |
| ------------- | ------------- | ------------- | ------------- |
|| fms/\*/init | 1 | CPNY WIND REQUEST, RTE SEL, ALTN RTE SEL, CPNY T.O REQUEST |
|| fms/\*/fuel-load | 1 | RTE RSV, correct fuel calculation, FUEL PLANNING |
|| fms/\*/fuel-load | 1 | correct fuel calculation, FUEL PLANNING |
|| fms/\*/perf | 1 | OPT FL, REC MAX, EO behavior, display of type of speed restriction (when MANAGED), PRED TO |
| | | | |
|| fms/\*/f-pln | 1 | F-PLN INFO button, exit of hold not possible via button |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export const NXSystemMessages = {
checkToData: new TypeIIMessage('CHECK TAKE OFF DATA', true),
comUnavailable: new TypeIMessage('COM UNAVAILABLE'),
cstrDelUpToWpt: new TypeIIMessage('CONSTRAINTS BEFORE WWWWW : DELETED', false, 'WWWWW'),
costIndexInUse: new TypeIMessage('COST INDEX-NNN IN USE', false, 'NNN'),
databaseCodingError: new TypeIIMessage('DATABASE CODING ERROR'),
dcduFileFull: new TypeIMessage('DCDU FILE FULL'),
destEfobBelowMin: new TypeIIMessage('DEST EFOB BELOW MIN', true),
Expand Down

0 comments on commit 4b4b498

Please sign in to comment.