Skip to content

Commit

Permalink
feat(fms): run predictions without v-speeds (#8921)
Browse files Browse the repository at this point in the history
* Run predictions without vspeeds

* Move default parameters to class

* Fix inital vertical checkpoint

* Use `getGrossWeight`

* Do not set `blockFuel` automatically

* Adapt to new `getFOB()` function

---------

Co-authored-by: Andreas Guther <[email protected]>
  • Loading branch information
BlueberryKing and aguther authored Sep 29, 2024
1 parent 1e87d2f commit 2165480
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 24 deletions.
1 change: 1 addition & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
1. [FMS] Show runway ident on lateral/vertical revision page of the missed approach point - @BlueberryKing (BlueberryKing)
1. [ECAM] Fixed ALL ECAM Button cycling through STS page and being inconsisten on press - @Maximilian-Reuter (\_chaoz_)
1. [CDU] Fix for EFOB calculation not pulling from block fuel prior to engine start - @PatM (Patrick Macken)
1. [FMS] Run vertical predictions without V-speeds - @BlueberryKing (BlueberryKing)

## 0.11.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,12 +338,13 @@ class CDUInitPage {
}
}
static fuelPredConditionsMet(mcdu) {
return isFinite(mcdu.blockFuel) &&
isFinite(mcdu.zeroFuelWeightMassCenter) &&
isFinite(mcdu.zeroFuelWeight) &&
const fob = mcdu.getFOB();

return Number.isFinite(fob) &&
Number.isFinite(mcdu.zeroFuelWeightMassCenter) &&
Number.isFinite(mcdu.zeroFuelWeight) &&
mcdu.flightPlanService.active && mcdu.flightPlanService.active.legCount > 0 &&
mcdu._zeroFuelWeightZFWCGEntered &&
(mcdu._blockFuelEntered || mcdu.isAnEngineOn());
mcdu._zeroFuelWeightZFWCGEntered;
}
static trySetFuelPred(mcdu) {
if (CDUInitPage.fuelPredConditionsMet(mcdu) && !mcdu._fuelPredDone) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ class FMCMainDisplay extends BaseAirliners {
this.machToCasManualCrossoverCurve.add(0.8, 300);
this.machToCasManualCrossoverCurve.add(0.82, 350);

this.updateFuelVars();
this.updatePerfSpeeds();

this.flightPhaseManager.init();
Expand Down Expand Up @@ -2062,7 +2061,11 @@ class FMCMainDisplay extends BaseAirliners {

// only used by trySetRouteAlternateFuel
isAltFuelInRange(fuel) {
return 0 < fuel && fuel < (this.blockFuel - this._routeTripFuelWeight);
if (Number.isFinite(this.blockFuel)) {
return 0 < fuel && fuel < (this.blockFuel - this._routeTripFuelWeight);
}

return 0 < fuel;
}

async trySetRouteAlternateFuel(altFuel) {
Expand Down Expand Up @@ -3969,10 +3972,6 @@ class FMCMainDisplay extends BaseAirliners {
return this.navigation.getSelectedNavaids(1);
}

updateFuelVars() {
this.blockFuel = SimVar.GetSimVarValue("FUEL TOTAL QUANTITY", "gallons") * SimVar.GetSimVarValue("FUEL WEIGHT PER GALLON", "kilograms") / 1000;
}

/**
* Set the takeoff flap config
* @param {0 | 1 | 2 | 3 | null} flaps
Expand Down Expand Up @@ -4593,8 +4592,12 @@ class FMCMainDisplay extends BaseAirliners {
return /^[+-]?\d*(?:\.\d+)?$/.test(str);
}

/**
* Gets the entered zero fuel weight, or undefined if not entered
* @returns {number | undefined} the zero fuel weight in tonnes or undefined
*/
getZeroFuelWeight() {
return this.zeroFuelWeight * 2204.625;
return this.zeroFuelWeight;
}

getV2Speed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const GEOMETRY_RECOMPUTATION_TIMER = 5_000;
export interface Fmgc {
getZeroFuelWeight(): number;
getFOB(): number;
getGrossWeight(): number | null;
getV2Speed(): Knots;
getTropoPause(): Feet;
getManagedClimbSpeed(): Knots;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,21 @@ export class VerticalProfileComputationParametersObserver {
managedDescentSpeed: this.fmgc.getManagedDescentSpeed(),
managedDescentSpeedMach: this.fmgc.getManagedDescentSpeedMach(),

zeroFuelWeight: this.fmgc.getZeroFuelWeight(),
fuelOnBoard: UnitType.TONNE.convertTo(this.fmgc.getFOB(), UnitType.POUND),
v2Speed: this.fmgc.getV2Speed(),
zeroFuelWeight: this.getZeroFuelWeight(),
fuelOnBoard: this.getFuelOnBoard(),
v2Speed: this.getV2Speed(),
tropoPause: this.fmgc.getTropoPause(),
perfFactor: 0, // FIXME: Use actual value,
departureElevation: this.fmgc.getDepartureElevation() ?? 0,
departureElevation: this.fmgc.getDepartureElevation() ?? DefaultVerticalProfileParameters.departureElevation,
/**
* This differs from the altitude I use to start building the descent profile.
* This one one is the altitude of the destination airport, the other one is the final procedure altitude.
*/
destinationElevation: this.fmgc.getDestinationElevation(),
accelerationAltitude: this.fmgc.getAccelerationAltitude(),
thrustReductionAltitude: this.fmgc.getThrustReductionAltitude(),
accelerationAltitude:
this.fmgc.getAccelerationAltitude() ?? DefaultVerticalProfileParameters.accelerationAltitude,
thrustReductionAltitude:
this.fmgc.getThrustReductionAltitude() ?? DefaultVerticalProfileParameters.thrustReductionAltitude,
originTransitionAltitude: this.fmgc.getOriginTransitionAltitude(),
// We do it this way because the cruise altitude is cleared in the MCDU once you start the descent
cruiseAltitude: this.flightPlanService.active.performanceData.cruiseFlightLevel
Expand All @@ -116,7 +118,7 @@ export class VerticalProfileComputationParametersObserver {
flightPhase: this.fmgc.getFlightPhase(),
preselectedClbSpeed: this.fmgc.getPreSelectedClbSpeed(),
preselectedCruiseSpeed: this.fmgc.getPreSelectedCruiseSpeed(),
takeoffFlapsSetting: this.fmgc.getTakeoffFlapsSetting(),
takeoffFlapsSetting: this.fmgc.getTakeoffFlapsSetting() ?? DefaultVerticalProfileParameters.flapsSetting,
estimatedDestinationFuel: UnitType.TONNE.convertTo(this.fmgc.getDestEFOB(false), UnitType.POUND),

approachQnh: this.fmgc.getApproachQnh(),
Expand Down Expand Up @@ -145,6 +147,23 @@ export class VerticalProfileComputationParametersObserver {
);
}

private getV2Speed(): Knots {
const fmV2 = this.fmgc.getV2Speed();
if (Number.isFinite(fmV2)) {
return fmV2;
}

const fmGw = this.fmgc.getGrossWeight();
const flaps = this.fmgc.getTakeoffFlapsSetting() ?? DefaultVerticalProfileParameters.flapsSetting;
const departureElevation = this.fmgc.getDepartureElevation() ?? DefaultVerticalProfileParameters.departureElevation;

if (Number.isFinite(fmGw)) {
return DefaultVerticalProfileParameters.getV2Speed(flaps, fmGw, departureElevation);
}

return null;
}

get(): VerticalProfileComputationParameters {
return this.parameters;
}
Expand All @@ -157,17 +176,96 @@ export class VerticalProfileComputationParametersObserver {
this.parameters.approachSpeed > 100;

const hasZeroFuelWeight = Number.isFinite(this.parameters.zeroFuelWeight);
const hasGrossWeight = Number.isFinite(this.fmgc.getGrossWeight());
const hasCruiseAltitude = Number.isFinite(this.parameters.cruiseAltitude);
const hasTakeoffParameters =
this.parameters.v2Speed > 0 &&
this.parameters.thrustReductionAltitude > 0 &&
this.parameters.accelerationAltitude > 0;
this.parameters.thrustReductionAltitude > 0 && this.parameters.accelerationAltitude > 0;

return (
(this.parameters.flightPhase > FmgcFlightPhase.Takeoff || hasTakeoffParameters) &&
areApproachSpeedsValid &&
hasZeroFuelWeight &&
hasGrossWeight &&
hasCruiseAltitude
);
}

getZeroFuelWeight(): Pounds {
const fmZfw = this.fmgc.getZeroFuelWeight();

return Number.isFinite(fmZfw) ? UnitType.TONNE.convertTo(fmZfw, UnitType.POUND) : undefined;
}

getFuelOnBoard(): Pounds {
const fmFuelOnBoard = this.fmgc.getFOB();

return Number.isFinite(fmFuelOnBoard) ? UnitType.TONNE.convertTo(fmFuelOnBoard, UnitType.POUND) : undefined;
}
}

class DefaultVerticalProfileParameters {
static readonly accelerationAltitude = 1500;

static readonly thrustReductionAltitude = 1500;

static readonly v2Speeds: Record<number, ((m: number) => number)[]> = {
1: [
() => 126,
() => 126,
() => 126,
(m: number) => 126 + 0.2 * (m - 50),
(m: number) => 127 + m - 55,
(m: number) => 132 + m - 60,
(m: number) => 137 + m - 65,
(m: number) => 142 + m - 70,
(m: number) => 147 + m - 75,
() => 151,
], // Conf 1 + F
2: [
() => 126,
() => 126,
() => 126,
() => 126,
(m: number) => 126 + 0.2 * (m - 55),
(m: number) => 127 + m - 60,
(m: number) => 132 + m - 65,
(m: number) => 137 + 0.8 * (m - 70),
(m: number) => 141 + m - 75,
() => 146,
], // Conf 2
3: [
() => 125,
() => 125,
() => 125,
() => 125,
() => 125,
(m: number) => 125 + 0.6 * (m - 60),
(m: number) => 128 + 0.8 * (m - 65),
(m: number) => 132 + m - 70,
(m: number) => 137 + 0.8 * (m - 75),
() => 141,
], // Conf 3
};

static readonly flapsSetting = 1;

static readonly departureElevation = 0;

static readonly destinationElevation = 0;

/**
* Gives the minimal V2 speed for a given configuration, gross weight and elevation.
* @param conf flap configuration
* @param fmGw gross weight in tonnes
* @param elevation departure elevation in feet
* @returns
*/
static getV2Speed(conf: number, fmGw: number, elevation: number): number {
const massIndex = Math.ceil((Math.min(fmGw, 80) - 40) / 5);

return Math.floor(
DefaultVerticalProfileParameters.v2Speeds[conf][massIndex](fmGw) +
(conf === 2 ? Math.abs(elevation * 0.0002) : 0),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export class TakeoffPathBuilder {

profile.checkpoints.push({
reason: VerticalCheckpointReason.Liftoff,
distanceFromStart: 0.6,
secondsFromPresent: 20,
distanceFromStart: 0,
secondsFromPresent: 0,
altitude: departureElevation,
remainingFuelOnBoard: fuelOnBoard,
speed: v2Speed + 10,
Expand Down

0 comments on commit 2165480

Please sign in to comment.