Skip to content

Commit

Permalink
fix(a380x): arrivals page approach data (#8938)
Browse files Browse the repository at this point in the history
* chore: this is not tsx

* feat: add approach name function for a380x

* feat: add approach suffix to data

* fix: rnp-ar logic

* fix: runway ls return undefined

* feat: add some type predicates to help us

* feat: add LandingSystemUtils

* fix: hax to work around rec navaid

* fix: approach ls ident/freq/chan

* fix: approach naming on arrivals page

* fix: filter out invalid and unsupported approaches

* fix(a380x/fms): perf appr approach name
  • Loading branch information
tracernz authored Sep 28, 2024
1 parent 341b9c3 commit 4f51143
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2024 FlyByWire Simulations
// SPDX-License-Identifier: GPL-3.0

import { Approach, ApproachType, isVhfNavaid, VhfNavaid, VhfNavaidType } from '@flybywiresim/fbw-sdk';

export class LandingSystemUtils {
// FIXME IlsNavaid when MSFS mapping can support it
static getLsFromApproach(approach: Approach): VhfNavaid | null {
if (approach.legs.length < 1) {
return null;
}

const mapLeg = approach.legs[approach.legs.length - 1];

// FIXME support GLS later...
switch (approach.type) {
case ApproachType.Igs:
case ApproachType.Ils:
case ApproachType.Lda:
case ApproachType.Loc:
case ApproachType.LocBackcourse:
case ApproachType.Sdf:
if (isVhfNavaid(mapLeg.recommendedNavaid) && mapLeg.recommendedNavaid.type === VhfNavaidType.IlsDme) {
return mapLeg.recommendedNavaid;
}
// fallthrough
default:
return null;
}
}
}
2 changes: 2 additions & 0 deletions fbw-a32nx/src/systems/fmgc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { NavigationDatabaseService } from './flightplanning/NavigationDatabaseSe
import { SimBriefUplinkAdapter } from './flightplanning/uplink/SimBriefUplinkAdapter';
import { A320FlightPlanPerformanceData } from './flightplanning/plans/performance/FlightPlanPerformanceData';
import { A320AircraftConfig } from '@fmgc/flightplanning/A320AircraftConfig';
import { LandingSystemUtils } from './flightplanning/data/landingsystem';

function initFmgcLoop(
baseInstrument: BaseInstrument,
Expand Down Expand Up @@ -63,6 +64,7 @@ export {
CoRouteUplinkAdapter,
DataManager,
EventBus,
LandingSystemUtils,
RadioUtils,
a320EfisRangeSettings,
A320AircraftConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { FSComponent, Subject, VNode } from '@microsoft/msfs-sdk';

import './MfdFmsFpln.scss';
import { AbstractMfdPageProps } from 'instruments/src/MFD/MFD';
import { Footer } from 'instruments/src/MFD/pages/common/Footer';
import { Button, ButtonMenuItem } from 'instruments/src/MFD/pages/common/Button';
import { FmsPage } from 'instruments/src/MFD/pages/common/FmsPage';
import { ApproachType } from '@fmgc/index';
import { ApproachType, LandingSystemUtils } from '@fmgc/index';
import { getApproachName } from '../../../shared/utils';

import './MfdFmsFpln.scss';

const ApproachTypeOrder = Object.freeze({
[ApproachType.Mls]: 0,
Expand Down Expand Up @@ -35,17 +36,17 @@ interface MfdFmsFplnArrProps extends AbstractMfdPageProps {}
export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
private toIcao = Subject.create<string>('');

private rwyLs = Subject.create<string>('');

private rwyIdent = Subject.create<string>('');

private rwyLength = Subject.create<string>('');

private rwyCrs = Subject.create<string>('');

private appr = Subject.create<string>('');
private readonly approachName = Subject.create<string>('');

private readonly approachLsFrequencyChannel = Subject.create<string>('');

private rwyFreq = Subject.create<string>('');
private readonly approachLsIdent = Subject.create('');

private via = Subject.create<string>('');

Expand Down Expand Up @@ -115,12 +116,10 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
this.rwyOptions.set(runways);

if (flightPlan.destinationRunway) {
this.rwyLs.set(flightPlan.destinationRunway.lsIdent);
this.rwyIdent.set(flightPlan.destinationRunway.ident.substring(4));
this.rwyLength.set(flightPlan.destinationRunway.length.toFixed(0) ?? '----');
this.rwyCrs.set(flightPlan.destinationRunway.bearing.toFixed(0).padStart(3, '0') ?? '---');
} else {
this.rwyLs.set('----');
this.rwyIdent.set('---');
this.rwyLength.set('----');
this.rwyCrs.set('---');
Expand All @@ -146,14 +145,26 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
];

// Sort approaches by runway
const sortedApproaches = flightPlan.availableApproaches.sort(
(a, b) =>
a.runwayIdent?.localeCompare(b.runwayIdent ?? '') || ApproachTypeOrder[a.type] - ApproachTypeOrder[b.type],
);
// FIXME add runway-by-itself
const sortedApproaches = flightPlan.availableApproaches
.filter(
(a) =>
a.type !== ApproachType.Tacan &&
a.type !== ApproachType.Mls &&
a.type !== ApproachType.MlsTypeA &&
a.type !== ApproachType.MlsTypeBC &&
a.runwayIdent !== undefined && // circling approaches
a.type !== ApproachType.LocBackcourse, // FIXME remove when supported
)
.sort(
(a, b) =>
a.runwayIdent?.localeCompare(b.runwayIdent ?? '') ||
ApproachTypeOrder[a.type] - ApproachTypeOrder[b.type],
);
let isFirstMatch = true;
sortedApproaches.forEach((el, idx) => {
appr.push({
label: el.ident,
label: getApproachName(el),
action: async () => {
await this.props.fmcService.master?.flightPlanService.setDestinationRunway(
el.runwayIdent ?? '',
Expand Down Expand Up @@ -185,8 +196,11 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
}

if (flightPlan.approach) {
this.appr.set(flightPlan.approach.ident);
this.rwyFreq.set(flightPlan.destinationRunway.lsFrequencyChannel?.toFixed(2) ?? '');
this.approachName.set(getApproachName(flightPlan.approach, false));
const ls = flightPlan.approach ? LandingSystemUtils.getLsFromApproach(flightPlan.approach) : undefined;
// FIXME handle non-localizer types
this.approachLsFrequencyChannel.set(ls?.frequency.toFixed(2) ?? '');
this.approachLsIdent.set(ls?.ident ?? '');

if (flightPlan.availableApproachVias.length > 0) {
const vias: ButtonMenuItem[] = [
Expand Down Expand Up @@ -231,12 +245,14 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
this.viaDisabled.set(true);
}
} else if (flightPlan.availableApproaches?.length > 0) {
this.appr.set('------');
this.rwyFreq.set('---.--');
this.approachName.set('------');
this.approachLsFrequencyChannel.set('---.--');
this.approachLsIdent.set('');
this.viaDisabled.set(true);
} else {
this.appr.set('NONE');
this.rwyFreq.set('---.--');
this.approachName.set('NONE');
this.approachLsFrequencyChannel.set('---.--');
this.approachLsIdent.set('');
this.viaDisabled.set(true);
}

Expand Down Expand Up @@ -411,7 +427,7 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
sec: this.secActive,
}}
>
{this.rwyLs}
{this.approachLsIdent}
</span>
</div>
<div class="fc" style="flex: 0.2;">
Expand Down Expand Up @@ -469,7 +485,7 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
sec: this.secActive,
}}
>
{this.appr}
{this.approachName}
</span>
</div>
<div class="fc" style="flex: 0.2;">
Expand All @@ -481,7 +497,7 @@ export class MfdFmsFplnArr extends FmsPage<MfdFmsFplnArrProps> {
sec: this.secActive,
}}
>
{this.rwyFreq}
{this.approachLsFrequencyChannel}
</span>
</div>
<div class="fc" style="flex: 0.2;">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import { ConditionalComponent } from 'instruments/src/MFD/pages/common/Condition
import { MfdSimvars } from 'instruments/src/MFD/shared/MFDSimvarPublisher';
import { VerticalCheckpointReason } from '@fmgc/guidance/vnav/profile/NavGeometryProfile';
import { A380SpeedsUtils } from '@shared/OperatingSpeeds';
import { NXSystemMessages } from 'instruments/src/MFD/shared/NXSystemMessages';
import { NXSystemMessages } from '../../shared/NXSystemMessages';
import { getApproachName } from '../../shared/utils';
import { ApproachType } from '@flybywiresim/fbw-sdk';

interface MfdFmsPerfProps extends AbstractMfdPageProps {}
Expand Down Expand Up @@ -530,7 +531,7 @@ export class MfdFmsPerf extends FmsPage<MfdFmsPerfProps> {

let precisionApproach = false;
if (this.loadedFlightPlan.approach) {
this.apprIdent.set(this.loadedFlightPlan.approach.ident);
this.apprIdent.set(getApproachName(this.loadedFlightPlan.approach, false));
precisionApproach =
this.loadedFlightPlan.approach.type === ApproachType.Ils ||
this.loadedFlightPlan.approach.type === ApproachType.Gls;
Expand Down Expand Up @@ -717,7 +718,7 @@ export class MfdFmsPerf extends FmsPage<MfdFmsPerfProps> {
this.clbTablePredLine2.set(null);
} else {
this.clbTableModeLine1.set('SELECTED');
this.clbTableSpdLine1.set(obs && obs.fcuSpeed >= 1 ? (obs?.fcuSpeed.toFixed(0) ?? null) : null);
this.clbTableSpdLine1.set(obs && obs.fcuSpeed >= 1 ? obs?.fcuSpeed.toFixed(0) ?? null : null);
this.clbTableMachLine1.set(obs && obs.fcuSpeed < 1 ? `.${obs.fcuSpeed.toFixed(2).split('.')[1]}` : null);
this.clbTablePredLine1.set(null);

Expand Down Expand Up @@ -824,7 +825,7 @@ export class MfdFmsPerf extends FmsPage<MfdFmsPerfProps> {
this.crzTableSpdLine1.set(
obs && obs.fcuSpeed < 1
? '---'
: (this.props.fmcService.master?.fmgc.getManagedClimbSpeed().toFixed(0) ?? null),
: this.props.fmcService.master?.fmgc.getManagedClimbSpeed().toFixed(0) ?? null,
);
this.crzTableMachLine1.set(
obs && obs.fcuSpeed < 1
Expand All @@ -840,7 +841,7 @@ export class MfdFmsPerf extends FmsPage<MfdFmsPerfProps> {
this.crzTablePredLine2.set(null);
} else {
this.crzTableModeLine1.set('SELECTED');
this.crzTableSpdLine1.set(obs && obs.fcuSpeed < 1 ? '---' : (obs?.fcuSpeed.toFixed(0) ?? null));
this.crzTableSpdLine1.set(obs && obs.fcuSpeed < 1 ? '---' : obs?.fcuSpeed.toFixed(0) ?? null);
this.crzTableMachLine1.set(obs && obs.fcuSpeed < 1 ? `.${obs.fcuSpeed.toFixed(2).split('.')[1]}` : null);
this.crzTablePredLine1.set(null);

Expand All @@ -849,7 +850,7 @@ export class MfdFmsPerf extends FmsPage<MfdFmsPerfProps> {
this.crzTableSpdLine2.set(
obs && obs.fcuSpeed < 1
? '---'
: (this.props.fmcService.master?.fmgc.getManagedCruiseSpeed().toFixed(0) ?? null),
: this.props.fmcService.master?.fmgc.getManagedCruiseSpeed().toFixed(0) ?? null,
);
this.crzTableMachLine2.set(
obs && obs.fcuSpeed < 1
Expand Down Expand Up @@ -889,7 +890,7 @@ export class MfdFmsPerf extends FmsPage<MfdFmsPerfProps> {
this.desTablePredLine2.set(null);
} else {
this.desTableModeLine1.set('SELECTED');
this.desTableSpdLine1.set(obs && obs.fcuSpeed >= 1 ? (obs?.fcuSpeed.toFixed(0) ?? null) : null);
this.desTableSpdLine1.set(obs && obs.fcuSpeed >= 1 ? obs?.fcuSpeed.toFixed(0) ?? null : null);
this.desTableMachLine1.set(obs && obs.fcuSpeed < 1 ? `.${obs.fcuSpeed.toFixed(2).split('.')[1]}` : null);
this.desTablePredLine1.set('--:-- ----');
this.desTableModeLine2.set('MANAGED');
Expand Down
48 changes: 48 additions & 0 deletions fbw-a380x/src/systems/instruments/src/MFD/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2023-2024 FlyByWire Simulations
// SPDX-License-Identifier: GPL-3.0

import { Approach, ApproachType } from '@flybywiresim/fbw-sdk';

export function secondsToHHmmString(seconds: number) {
const minutesTotal = seconds / 60;
const hours = Math.abs(Math.floor(minutesTotal / 60))
.toFixed(0)
.toString()
.padStart(2, '0');
const minutes = Math.abs(minutesTotal % 60)
.toFixed(0)
.toString()
.padStart(2, '0');
return `${hours}:${minutes}`;
}

const approachTypeNames: Record<ApproachType, string> = {
[ApproachType.Ils]: 'ILS',
[ApproachType.Gls]: 'GLS',
[ApproachType.Igs]: 'IGS',
[ApproachType.Loc]: 'LOC',
[ApproachType.LocBackcourse]: 'BAC',
[ApproachType.Lda]: 'LDA',
[ApproachType.Sdf]: 'SDF',
[ApproachType.Gps]: 'GPS',
[ApproachType.Rnav]: 'RNV',
[ApproachType.Vor]: 'VOR',
[ApproachType.VorDme]: 'VOR',
[ApproachType.Vortac]: 'VOR',
[ApproachType.Ndb]: 'NDB',
[ApproachType.NdbDme]: 'NDB',
[ApproachType.Fms]: 'RNAV',
[ApproachType.Mls]: 'MLS', // not actually supported
[ApproachType.MlsTypeA]: 'MLS', // not actually supported
[ApproachType.MlsTypeBC]: 'MLS', // not actually supported
[ApproachType.Tacan]: 'TAC', // not actually supported
[ApproachType.Unknown]: '',
};

export function getApproachName(approach: Approach, withRnpSuffix = true): string {
// we don't need to worry about circling approaches as they aren't available, so we can always expect a runway ident
// FIXME add (RNP) suffix for RNP-AR missed approaches (even on non-RNAV approaches)
const approachSuffix = approach.suffix ? `-${approach.suffix}` : '';
const arSuffix = withRnpSuffix && approach.authorisationRequired ? ' (RNP)' : '';
return `${approachTypeNames[approach.type]}${approach.runwayIdent?.substring(4)}${approachSuffix}${arSuffix}`;
}
12 changes: 0 additions & 12 deletions fbw-a380x/src/systems/instruments/src/MFD/shared/utils.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,10 @@ export class MsfsMapping {
this.approachHasLandingSystem(appr),
);
const lsIdent = lsAppr ? FacilityCache.ident(lsAppr.finalLegs[lsAppr.finalLegs.length - 1].originIcao) : '';
// FIXME need to return an IlsNavaid for ILS
const lsFrequencyChannel = lsAppr
? navaids.get(lsAppr.finalLegs[lsAppr.finalLegs.length - 1].originIcao)?.freqMHz ?? 0
: 0;
? navaids.get(lsAppr.finalLegs[lsAppr.finalLegs.length - 1].originIcao)?.freqMHz
: undefined;

runways.push({
sectionCode: SectionCode.Airport,
Expand Down Expand Up @@ -615,9 +616,10 @@ export class MsfsMapping {
.filter((approach) => approach.runwayNumber !== 0)
.map((approach) => {
const approachName = this.mapApproachName(approach);
const suffix = approach.approachSuffix.length > 0 ? approach.approachSuffix : undefined;

// the AR flag is not available so we use this heuristic based on analysing the MSFS data
const authorisationRequired = approach.rnavTypeFlags === 0;
const authorisationRequired = approach.approachType === MSApproachType.Rnav && approach.rnavTypeFlags === 0;
const rnp = authorisationRequired ? 0.3 : undefined;

const runwayIdent = `${airportIdent}${approach.runwayNumber.toString().padStart(2, '0')}${this.mapRunwayDesignator(approach.runwayDesignator)}`;
Expand All @@ -632,6 +634,7 @@ export class MsfsMapping {
databaseId: `P${icaoCode}${airportIdent}${approach.name}`,
icaoCode,
ident: approachName,
suffix,
runwayIdent,
multipleIndicator: approach.approachSuffix,
type: this.mapApproachType(approach.approachType),
Expand Down
3 changes: 3 additions & 0 deletions fbw-common/src/systems/navdata/shared/types/Approach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export interface Approach extends DatabaseItem<SectionCode.Airport> {
*/
type: ApproachType;

/** The approach suffix char for multiple approaches, or undefined if none. */
suffix?: string;

/**
* RNP-AR approach?
*/
Expand Down
8 changes: 8 additions & 0 deletions fbw-common/src/systems/navdata/shared/types/IlsNavaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ export interface IlsNavaid extends DatabaseItem<SectionCode.Airport> {
*/
stationDeclination: Degrees;
}

export function isIlsNavaid(o: any): o is IlsNavaid {
return (
typeof o === 'object' &&
o.sectionCode === SectionCode.Airport &&
o.subSectionCode === AirportSubsectionCode.LocalizerGlideSlope
);
}
6 changes: 6 additions & 0 deletions fbw-common/src/systems/navdata/shared/types/VhfNavaid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export interface VhfNavaid extends BaseFix<SectionCode.Navaid> {
distance?: NauticalMiles;
}

export function isVhfNavaid(o: any): o is VhfNavaid {
return (
typeof o === 'object' && o.sectionCode === SectionCode.Navaid && o.subSectionCode === NavaidSubsectionCode.VhfNavaid
);
}

// TODO enum
export type FigureOfMerit = 0 | 1 | 2 | 3 | 7 | 9;

Expand Down

0 comments on commit 4f51143

Please sign in to comment.