Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(a380x/mfd): Vertical Revision speed limit modification #9845

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
1. [EFB] Navigraph subscriptions other than Ultimate are now shown on the EFB to reduce confusion with "Unknown" - @tracernz (Mike)
1. [A380X/FWS] Add RAT OUT, REFUEL PNL DOOR, HI ALT AIRPORT & REFUELING/DEFUELING memos - @BravoMike99 (bruno_pt99)
1. [A380X/ND] Correct positioning of TCAS Messages and added TCAS STBY - @MrJigs7 (MrJigs)
1. [A380X/MFD] Add speed limit modification on VERT REV SPD page

## 0.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,18 @@ export interface FlightPlanInterface<P extends FlightPlanPerformanceData = Fligh
planIndex: number,
): Promise<void>;

setPilotEntryClimbSpeedLimitSpeed(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void>;

setPilotEntryClimbSpeedLimitAltitude(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void>;

deleteClimbSpeedLimit(planIndex: FlightPlanIndex, alternate: boolean): Promise<void>;

setPilotEntryDescentSpeedLimitSpeed(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void>;

setPilotEntryDescentSpeedLimitAltitude(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void>;

deleteDescentSpeedLimit(planIndex: FlightPlanIndex, alternate: boolean): Promise<void>;
Copy link
Member

@tracernz tracernz Feb 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should all have full JSDocs for all parameters with unit and possible values (as should the existing ones, but too late for them).


// TODO do not pass in waypoint object (rpc)
isWaypointInUse(waypoint: Waypoint): Promise<boolean>;

Expand Down
115 changes: 114 additions & 1 deletion fbw-a32nx/src/systems/fmgc/src/flightplanning/FlightPlanService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { FlightPlanLegDefinition } from '@fmgc/flightplanning/legs/FlightPlanLeg
import { FlightPlanInterface } from '@fmgc/flightplanning/FlightPlanInterface';
import { AltitudeConstraint } from '@fmgc/flightplanning/data/constraint';
import { CopyOptions } from '@fmgc/flightplanning/plans/CloningOptions';
import { FlightPlanPerformanceData } from '@fmgc/flightplanning/plans/performance/FlightPlanPerformanceData';
import {
DefaultPerformanceData,
FlightPlanPerformanceData,
} from '@fmgc/flightplanning/plans/performance/FlightPlanPerformanceData';

export class FlightPlanService<P extends FlightPlanPerformanceData = FlightPlanPerformanceData>
implements FlightPlanInterface<P>
Expand Down Expand Up @@ -574,6 +577,116 @@ export class FlightPlanService<P extends FlightPlanPerformanceData = FlightPlanP
plan.editFixInfoEntry(index, callback);
}

async setPilotEntryClimbSpeedLimitSpeed(value: number, planIndex = FlightPlanIndex.Active, alternate = false) {
const finalIndex = this.config.TMPY_ON_CONSTRAINT_EDIT ? this.prepareDestructiveModification(planIndex) : planIndex;

const plan = this.flightPlanManager.get(finalIndex);

const performanceDataAltitudeKey = alternate ? 'alternateClimbSpeedLimitAltitude' : 'climbSpeedLimitAltitude';

if (plan.performanceData[performanceDataAltitudeKey] === null) {
plan.setPerformanceData(performanceDataAltitudeKey, DefaultPerformanceData.ClimbSpeedLimitAltitude);
}

plan.setPerformanceData(alternate ? 'alternateClimbSpeedLimitSpeed' : 'climbSpeedLimitSpeed', value);
plan.setPerformanceData(
alternate ? 'isAlternateClimbSpeedLimitPilotEntered' : 'isClimbSpeedLimitPilotEntered',
true,
);

plan.incrementVersion();
}

async setPilotEntryClimbSpeedLimitAltitude(value: number, planIndex = FlightPlanIndex.Active, alternate = false) {
const finalIndex = this.config.TMPY_ON_CONSTRAINT_EDIT ? this.prepareDestructiveModification(planIndex) : planIndex;

const plan = this.flightPlanManager.get(finalIndex);

const performanceDataSpeedKey = alternate ? 'alternateClimbSpeedLimitSpeed' : 'climbSpeedLimitSpeed';

if (plan.performanceData[performanceDataSpeedKey] === null) {
plan.setPerformanceData(performanceDataSpeedKey, DefaultPerformanceData.ClimbSpeedLimitSpeed);
}

plan.setPerformanceData(alternate ? 'alternateClimbSpeedLimitAltitude' : 'climbSpeedLimitAltitude', value);
plan.setPerformanceData(
alternate ? 'isAlternateClimbSpeedLimitPilotEntered' : 'isClimbSpeedLimitPilotEntered',
true,
);

plan.incrementVersion();
}

async deleteClimbSpeedLimit(planIndex = FlightPlanIndex.Active, alternate = false) {
const finalIndex = this.config.TMPY_ON_CONSTRAINT_EDIT ? this.prepareDestructiveModification(planIndex) : planIndex;

const plan = this.flightPlanManager.get(finalIndex);

plan.setPerformanceData(alternate ? 'alternateClimbSpeedLimitSpeed' : 'climbSpeedLimitSpeed', null);
plan.setPerformanceData(alternate ? 'alternateClimbSpeedLimitAltitude' : 'climbSpeedLimitAltitude', null);
plan.setPerformanceData(
alternate ? 'isAlternateClimbSpeedLimitPilotEntered' : 'isClimbSpeedLimitPilotEntered',
false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deletion would be setting a pilot value of null IMO? Anything setting it to a non-default value would be a pilot entry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept it as false for consistency as that's how it also is on the A32NX currently (default entry or deleted -> false). I can change both to null but in that case I need to update the performance data interface as the type there is just boolean currently.

);

plan.incrementVersion();
}

async setPilotEntryDescentSpeedLimitSpeed(value: number, planIndex = FlightPlanIndex.Active, alternate = false) {
const finalIndex = this.config.TMPY_ON_CONSTRAINT_EDIT ? this.prepareDestructiveModification(planIndex) : planIndex;

const plan = this.flightPlanManager.get(finalIndex);

const performanceDataAltitudeKey = alternate ? 'alternateDescentSpeedLimitAltitude' : 'descentSpeedLimitAltitude';

if (plan.performanceData[performanceDataAltitudeKey] === null) {
plan.setPerformanceData(performanceDataAltitudeKey, DefaultPerformanceData.DescentSpeedLimitAltitude);
}

plan.setPerformanceData(alternate ? 'alternateDescentSpeedLimitSpeed' : 'descentSpeedLimitSpeed', value);
plan.setPerformanceData(
alternate ? 'isAlternateDescentSpeedLimitPilotEntered' : 'isDescentSpeedLimitPilotEntered',
true,
);

plan.incrementVersion();
}

async setPilotEntryDescentSpeedLimitAltitude(value: number, planIndex = FlightPlanIndex.Active, alternate = false) {
const finalIndex = this.config.TMPY_ON_CONSTRAINT_EDIT ? this.prepareDestructiveModification(planIndex) : planIndex;

const plan = this.flightPlanManager.get(finalIndex);

const performanceDataSpeedKey = alternate ? 'alternateDescentSpeedLimitSpeed' : 'descentSpeedLimitSpeed';

if (plan.performanceData[performanceDataSpeedKey] === null) {
plan.setPerformanceData(performanceDataSpeedKey, DefaultPerformanceData.DescentSpeedLimitSpeed);
}

plan.setPerformanceData(alternate ? 'alternateDescentSpeedLimitAltitude' : 'descentSpeedLimitAltitude', value);
plan.setPerformanceData(
alternate ? 'isAlternateDescentSpeedLimitPilotEntered' : 'isDescentSpeedLimitPilotEntered',
true,
);

plan.incrementVersion();
}

async deleteDescentSpeedLimit(planIndex = FlightPlanIndex.Active, alternate = false) {
const finalIndex = this.config.TMPY_ON_CONSTRAINT_EDIT ? this.prepareDestructiveModification(planIndex) : planIndex;

const plan = this.flightPlanManager.get(finalIndex);

plan.setPerformanceData(alternate ? 'alternateDescentSpeedLimitSpeed' : 'descentSpeedLimitSpeed', null);
plan.setPerformanceData(alternate ? 'alternateDescentSpeedLimitAltitude' : 'descentSpeedLimitAltitude', null);
plan.setPerformanceData(
alternate ? 'isAlternateDescentSpeedLimitPilotEntered' : 'isDescentSpeedLimitPilotEntered',
false,
);

plan.incrementVersion();
}

get activeLegIndex(): number {
return this.active.activeLegIndex;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1523,9 +1523,7 @@ export abstract class BaseFlightPlan<P extends FlightPlanPerformanceData = Fligh

element.pilotEnteredAltitudeConstraint = constraint;
if (!constraint) {
element.definition.altitudeDescriptor = AltitudeDescriptor.None;
element.definition.altitude1 = undefined;
element.definition.altitude2 = undefined;
element.clearAltitudeConstraints();
}

this.syncLegDefinitionChange(index);
Expand All @@ -1547,9 +1545,7 @@ export abstract class BaseFlightPlan<P extends FlightPlanPerformanceData = Fligh
}

if (!speed) {
element.pilotEnteredSpeedConstraint = undefined;
element.definition.speedDescriptor = undefined;
element.definition.speed = undefined;
element.clearSpeedConstraints();
} else {
element.pilotEnteredSpeedConstraint = { speedDescriptor: SpeedDescriptor.Maximum, speed };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,25 @@ export class FlightPlanRpcClient<P extends FlightPlanPerformanceData> implements
return this.callFunctionViaRpc('editFixInfoEntry', index, callback, planIndex);
}

setPilotEntryClimbSpeedLimitSpeed(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void> {
return this.callFunctionViaRpc('setPilotEntryClimbSpeedLimitSpeed', value, planIndex, alternate);
}
setPilotEntryClimbSpeedLimitAltitude(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void> {
return this.callFunctionViaRpc('setPilotEntryClimbSpeedLimitAltitude', value, planIndex, alternate);
}
deleteClimbSpeedLimit(planIndex: FlightPlanIndex, alternate: boolean): Promise<void> {
return this.callFunctionViaRpc('deleteClimbSpeedLimit', planIndex, alternate);
}
setPilotEntryDescentSpeedLimitSpeed(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void> {
return this.callFunctionViaRpc('setPilotEntryDescentSpeedLimitSpeed', value, planIndex, alternate);
}
setPilotEntryDescentSpeedLimitAltitude(value: number, planIndex: FlightPlanIndex, alternate: boolean): Promise<void> {
return this.callFunctionViaRpc('setPilotEntryDescentSpeedLimitAltitude', value, planIndex, alternate);
}
deleteDescentSpeedLimit(planIndex: FlightPlanIndex, alternate: boolean): Promise<void> {
return this.callFunctionViaRpc('deleteDescentSpeedLimit', planIndex, alternate);
}

isWaypointInUse(waypoint: Waypoint): Promise<boolean> {
return this.callFunctionViaRpc('isWaypointInUse', waypoint);
}
Expand Down
4 changes: 2 additions & 2 deletions fbw-a32nx/src/systems/fmgc/src/guidance/GuidanceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export interface Fmgc {
getFlightPhase(): FmgcFlightPhase;
getManagedCruiseSpeed(): Knots;
getManagedCruiseSpeedMach(): Mach;
getClimbSpeedLimit(): SpeedLimit;
getDescentSpeedLimit(): SpeedLimit;
getClimbSpeedLimit(): SpeedLimit | null;
getDescentSpeedLimit(): SpeedLimit | null;
getPreSelectedClbSpeed(): Knots;
getPreSelectedCruiseSpeed(): Knots;
getTakeoffFlapsSetting(): FlapConf | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,12 +612,12 @@ export class FmcAircraftInterface {
// apply speed limit/alt
if (this.flightPhase.get() <= FmgcFlightPhase.Cruise) {
const climbSpeedLimit = this.fmgc.getClimbSpeedLimit();
if (climbSpeedLimit.speed !== null && alt <= climbSpeedLimit.underAltitude) {
if (climbSpeedLimit !== null && alt <= climbSpeedLimit.underAltitude) {
kcas = Math.min(climbSpeedLimit.speed, kcas);
}
} else if (this.flightPhase.get() < FmgcFlightPhase.GoAround) {
const descentSpeedLimit = this.fmgc.getDescentSpeedLimit();
if (descentSpeedLimit.speed !== null && alt <= descentSpeedLimit.underAltitude) {
if (descentSpeedLimit !== null && alt <= descentSpeedLimit.underAltitude) {
kcas = Math.min(descentSpeedLimit.speed, kcas);
}
}
Expand Down Expand Up @@ -790,12 +790,10 @@ export class FmcAircraftInterface {
}
case FmgcFlightPhase.Climb: {
let speed = this.fmgc.getManagedClimbSpeed();
const speedLimit = this.fmgc.getClimbSpeedLimit();

if (
this.fmgc.getClimbSpeedLimit() !== undefined &&
SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet') < this.fmgc.getClimbSpeedLimit().underAltitude
) {
speed = Math.min(speed, this.fmgc.getClimbSpeedLimit().speed);
if (speedLimit !== null && SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet') < speedLimit.underAltitude) {
speed = Math.min(speed, speedLimit.speed);
}

speed = Math.min(speed, this.getSpeedConstraint());
Expand All @@ -806,12 +804,9 @@ export class FmcAircraftInterface {
}
case FmgcFlightPhase.Cruise: {
let speed = this.fmgc.getManagedCruiseSpeed();

if (
this.fmgc.getClimbSpeedLimit() !== undefined &&
SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet') < this.fmgc.getClimbSpeedLimit().underAltitude
) {
speed = Math.min(speed, this.fmgc.getClimbSpeedLimit().speed);
const speedLimit = this.fmgc.getClimbSpeedLimit();
if (speedLimit !== null && SimVar.GetSimVarValue('INDICATED ALTITUDE', 'feet') < speedLimit.underAltitude) {
speed = Math.min(speed, speedLimit.speed);
}

[this.managedSpeedTarget, isMach] = this.getManagedTargets(speed, this.fmgc.getManagedCruiseSpeedMach());
Expand Down Expand Up @@ -968,25 +963,25 @@ export class FmcAircraftInterface {
private speedLimitExceeded = false;

checkSpeedLimit() {
let speedLimit: number;
let speedLimitAlt: number;
let speedLimit: number | undefined;
let speedLimitAlt: number | undefined;
switch (this.flightPhase.get()) {
case FmgcFlightPhase.Climb:
case FmgcFlightPhase.Cruise:
speedLimit = this.fmgc.getClimbSpeedLimit().speed;
speedLimitAlt = this.fmgc.getClimbSpeedLimit().underAltitude;
speedLimit = this.fmgc.getClimbSpeedLimit()?.speed;
speedLimitAlt = this.fmgc.getClimbSpeedLimit()?.underAltitude;
break;
case FmgcFlightPhase.Descent:
speedLimit = this.fmgc.getDescentSpeedLimit().speed;
speedLimitAlt = this.fmgc.getDescentSpeedLimit().underAltitude;
speedLimit = this.fmgc.getDescentSpeedLimit()?.speed;
speedLimitAlt = this.fmgc.getDescentSpeedLimit()?.underAltitude;
break;
default:
// no speed limit in other phases
this.speedLimitExceeded = false;
return;
}

if (speedLimit === undefined) {
if (speedLimit === undefined || speedLimitAlt === undefined) {
this.speedLimitExceeded = false;
return;
}
Expand Down
34 changes: 26 additions & 8 deletions fbw-a380x/src/systems/instruments/src/MFD/FMC/fmgc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,16 +288,12 @@ export class FmgcData {

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

public readonly climbSpeedLimit = Subject.create<SpeedLimit>({ speed: 250, underAltitude: 10_000 });

public readonly cruisePreSelMach = Subject.create<number | null>(null);

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

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

public readonly descentSpeedLimit = Subject.create<SpeedLimit>({ speed: 250, underAltitude: 10_000 });

/** in feet/min. null if not set. */
public readonly descentCabinRate = Subject.create<number>(-350);

Expand Down Expand Up @@ -478,12 +474,34 @@ export class FmgcDataService implements Fmgc {
return this.data.cruisePreSelMach.get() ?? 0.85;
}

getClimbSpeedLimit(): SpeedLimit {
return { speed: 250, underAltitude: 10_000 };
getClimbSpeedLimit(): SpeedLimit | null {
if (!this.flightPlanService.has(FlightPlanIndex.Active)) {
return null;
}
const speedLimitSpeed = this.flightPlanService.active.performanceData.climbSpeedLimitSpeed;
const speedLimitAltitude = this.flightPlanService.active.performanceData.climbSpeedLimitAltitude;
if (speedLimitSpeed && speedLimitAltitude) {
return {
speed: speedLimitSpeed,
underAltitude: speedLimitAltitude,
};
}
return null;
}

getDescentSpeedLimit(): SpeedLimit {
return { speed: 250, underAltitude: 10_000 };
getDescentSpeedLimit(): SpeedLimit | null {
if (!this.flightPlanService.has(FlightPlanIndex.Active)) {
return null;
}
const speedLimitSpeed = this.flightPlanService.active.performanceData.descentSpeedLimitSpeed;
const speedLimitAltitude = this.flightPlanService.active.performanceData.descentSpeedLimitAltitude;
if (speedLimitSpeed && speedLimitAltitude) {
return {
speed: speedLimitSpeed,
underAltitude: speedLimitAltitude,
};
}
return null;
}

/** in knots */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,19 @@
.mfd-vert-rev-clbdes {
margin-top: 20px;
}

.mfd-vert-rev-spd-lim-header {
width: 100%;
margin-top: 20px;
padding-left: 10px;
}

.mfd-vert-rev-spd-lim {
display: flex;
width: 100%;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 30px;
padding-left: 10px;
}
Loading