Skip to content

Commit

Permalink
2024 SU1 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
mattnischan committed Mar 7, 2025
1 parent 673b468 commit c89cf80
Show file tree
Hide file tree
Showing 279 changed files with 4,825 additions and 1,452 deletions.
98 changes: 74 additions & 24 deletions src/garminsdk/flightplan/Fms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ export class Fms<ID extends string = any> {
let approachRnavTypeFlags: RnavTypeFlags = RnavTypeFlags.None;
let approachIsCircling = false;
let approachIsVtf = false;
let approachIsRnpAr = false;
let referenceFacility: VorFacility | null = null;
let approachRunway: OneWayRunway | null = null;

Expand All @@ -667,6 +668,7 @@ export class Fms<ID extends string = any> {
approachRnavTypeFlags = approach.rnavTypeFlags;
approachIsCircling = !approach.runway;
approachIsVtf = plan.procedureDetails.approachTransitionIndex < 0;
approachIsRnpAr = ApproachUtils.isRnpAr(approach);
if (FmsUtils.approachHasNavFrequency(approach)) {
referenceFacility = (await ApproachUtils.getReferenceFacility(approach, this.facLoader) as VorFacility | undefined) ?? null;
}
Expand Down Expand Up @@ -697,7 +699,18 @@ export class Fms<ID extends string = any> {
}
}

this.setApproachDetails(false, approachLoaded, approachType, approachRnavType, approachRnavTypeFlags, approachIsCircling, approachIsVtf, referenceFacility, approachRunway);
this.setApproachDetails(
false,
approachLoaded,
approachType,
approachRnavType,
approachRnavTypeFlags,
approachIsCircling,
approachIsVtf,
approachIsRnpAr,
referenceFacility,
approachRunway,
);
}

/**
Expand Down Expand Up @@ -1456,7 +1469,7 @@ export class Fms<ID extends string = any> {
}

++this.updateApproachDetailsOpId;
this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, RnavTypeFlags.None, false, false, null, null);
this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, RnavTypeFlags.None, false, false, false, null, null);
plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);

plan.calculate(0);
Expand Down Expand Up @@ -2105,19 +2118,21 @@ export class Fms<ID extends string = any> {
}
}

const approachType = visualRunway ? AdditionalApproachType.APPROACH_TYPE_VISUAL : facility.approaches[approachIndex].approachType;
const bestRnavType = visualRunway ? RnavTypeFlags.None : FmsUtils.getBestRnavType(facility.approaches[approachIndex].rnavTypeFlags);
const rnavTypeFlags = visualRunway ? RnavTypeFlags.None : facility.approaches[approachIndex].rnavTypeFlags;
const approachIsCircling = !visualRunway && !facility.approaches[approachIndex].runway ? true : false;
const approach = facility.approaches[approachIndex];
const approachType = visualRunway ? AdditionalApproachType.APPROACH_TYPE_VISUAL : approach.approachType;
const bestRnavType = visualRunway ? RnavTypeFlags.None : FmsUtils.getBestRnavType(approach.rnavTypeFlags);
const rnavTypeFlags = visualRunway ? RnavTypeFlags.None : approach.rnavTypeFlags;
const approachIsCircling = !visualRunway && !approach.runway ? true : false;
const isVtf = approachTransitionIndex < 0;
const isRnpAr = visualRunway ? false : ApproachUtils.isRnpAr(approach);

let referenceFacility: VorFacility | null = null;
if (!visualRunway && FmsUtils.approachHasNavFrequency(facility.approaches[approachIndex])) {
referenceFacility = (await ApproachUtils.getReferenceFacility(facility.approaches[approachIndex], this.facLoader) as VorFacility | undefined) ?? null;
if (!visualRunway && FmsUtils.approachHasNavFrequency(approach)) {
referenceFacility = (await ApproachUtils.getReferenceFacility(approach, this.facLoader) as VorFacility | undefined) ?? null;
}

++this.updateApproachDetailsOpId;
this.setApproachDetails(true, true, approachType, bestRnavType, rnavTypeFlags, approachIsCircling, isVtf, referenceFacility, approachRunway);
this.setApproachDetails(true, true, approachType, bestRnavType, rnavTypeFlags, approachIsCircling, isVtf, isRnpAr, referenceFacility, approachRunway);

this.autoDesignateProcedureConstraints(plan, approachSegment.segmentIndex);

Expand Down Expand Up @@ -2187,6 +2202,8 @@ export class Fms<ID extends string = any> {
? FmsUtils.buildVisualApproach(facility, visualRunway!, this.visualApproachOptions.finalFixDistance, this.visualApproachOptions.strghtFixDistance)
: facility.approaches[approachIndex];

const finalLegs = approach.finalLegs;

const transition = approach.transitions[approachTransitionIndex];
const isVtf = approachTransitionIndex < 0;
const insertProcedureObject: InsertProcedureObject = { procedureLegs: [] };
Expand All @@ -2200,14 +2217,33 @@ export class Fms<ID extends string = any> {
}
}

const lastTransitionLeg = insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1];
const lastTransitionLeg = insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] as InsertProcedureObjectLeg | undefined;

let finalLegsStartIndex = 0;

if (isVtf) {
insertProcedureObject.procedureLegs.push(FlightPlan.createLeg({ type: LegType.ThruDiscontinuity }));
} else if (lastTransitionLeg) {
// Check if the last transition leg is an XF leg that terminates at the FACF (flagged as IF) or FAF. If so, then
// we need to merge the last transition leg with the FACF/FAF leg and skip every leg in the final legs array
// before the latter.
if (FlightPlanUtils.isToFixLeg(lastTransitionLeg.type) && !ICAO.isValueEmpty(lastTransitionLeg.fixIcaoStruct)) {
for (let i = 0; i < finalLegs.length; i++) {
const leg = finalLegs[i];
if (
BitFlags.isAny(leg.fixTypeFlags, FixTypeFlags.IF | FixTypeFlags.FAF)
&& FlightPlanUtils.isToFixLeg(leg.type)
&& ICAO.valueEquals(leg.fixIcaoStruct, lastTransitionLeg.fixIcaoStruct)
) {
insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] = this.mergeDuplicateLegData(lastTransitionLeg, leg);
finalLegsStartIndex = i + 1;
break;
}
}
}
}

const finalLegs = approach.finalLegs;
for (let i = 0; i < finalLegs.length; i++) {
for (let i = finalLegsStartIndex; i < finalLegs.length; i++) {
const leg = FlightPlanUtils.convertLegRunwayIcaosToSdkFormat(FlightPlan.createLeg(finalLegs[i]));
if (i === 0 && lastTransitionLeg && this.isDuplicateIFLeg(lastTransitionLeg, leg)) {
insertProcedureObject.procedureLegs[insertProcedureObject.procedureLegs.length - 1] = this.mergeDuplicateLegData(lastTransitionLeg, leg);
Expand Down Expand Up @@ -2425,7 +2461,7 @@ export class Fms<ID extends string = any> {
}

++this.updateApproachDetailsOpId;
this.setApproachDetails(true, true, approachType, bestRnavType, rnavTypeFlags, approachIsCircling, isVtf, referenceFacility, approachRunway);
this.setApproachDetails(true, true, approachType, bestRnavType, rnavTypeFlags, approachIsCircling, isVtf, false, referenceFacility, approachRunway);

await plan.calculate();

Expand Down Expand Up @@ -3347,7 +3383,7 @@ export class Fms<ID extends string = any> {
const plan = this.getFlightPlan();

++this.updateApproachDetailsOpId;
this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, RnavTypeFlags.None, false, false, null, null);
this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, RnavTypeFlags.None, false, false, false, null, null);
plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);

const hasArrival = plan.procedureDetails.arrivalIndex >= 0;
Expand Down Expand Up @@ -4264,7 +4300,7 @@ export class Fms<ID extends string = any> {
plan.deleteUserData(FmsFplUserDataKey.ApproachSkipCourseReversal);

++this.updateApproachDetailsOpId;
this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, RnavTypeFlags.None, false, false, null, null);
this.setApproachDetails(true, false, ApproachType.APPROACH_TYPE_UNKNOWN, RnavTypeFlags.None, RnavTypeFlags.None, false, false, false, null, null);
plan.deleteUserData(Fms.VTF_FAF_DATA_KEY);

plan.setCalculatingLeg(0);
Expand Down Expand Up @@ -5941,18 +5977,32 @@ export class Fms<ID extends string = any> {
}

/**
* Merges two duplicate legs such that the new merged leg contains the fix type and altitude data from the source leg
* and all other data is derived from the target leg.
* Merges two duplicate legs. The merged leg will be identical to the target leg with the following exceptions:
* - The merged leg's fix type flags are the union of those of the target and source legs.
* - The merged leg's altitude restriction data are equal to those of the source leg if the source leg defines an
* altitude restriction. Otherwise the merged leg's altitude restriction data are equal to those of the target leg.
* - The merged leg's speed restriction data are equal to those of the source leg if the source leg defines a speed
* restriction. Otherwise the merged leg's speed restriction data are equal to those of the target leg.
* @param target The target leg.
* @param source The source leg.
* @returns the merged leg.
* @returns The merged leg.
*/
private mergeDuplicateLegData(target: FlightPlanLeg, source: FlightPlanLeg): FlightPlanLeg {
const merged = FlightPlan.createLeg(target);

merged.fixTypeFlags |= source.fixTypeFlags;
merged.altDesc = source.altDesc;
merged.altitude1 = source.altitude1;
merged.altitude2 = source.altitude2;

if (source.altDesc !== AltitudeRestrictionType.Unused) {
merged.altDesc = source.altDesc;
merged.altitude1 = source.altitude1;
merged.altitude2 = source.altitude2;
}

if (source.speedRestrictionDesc !== SpeedRestrictionType.Unused) {
merged.speedRestrictionDesc = source.speedRestrictionDesc;
merged.speedRestriction = source.speedRestriction;
}

return merged;
}

Expand Down Expand Up @@ -6165,6 +6215,7 @@ export class Fms<ID extends string = any> {
* @param rnavTypeFlags The RNAV minimum type flags for the approach.
* @param isCircling Whether the approach is a circling approach.
* @param isVtf Whether the approach is a vectors-to-final approach.
* @param isRnpAr Whether the approach is an RNP-AR approach.
* @param referenceFacility The approach's reference facility.
* @param runway The assigned runway for the approach
*/
Expand All @@ -6176,6 +6227,7 @@ export class Fms<ID extends string = any> {
rnavTypeFlags?: RnavTypeFlags,
isCircling?: boolean,
isVtf?: boolean,
isRnpAr?: boolean,
referenceFacility?: VorFacility | null,
runway?: OneWayRunway | null
): void {
Expand All @@ -6185,14 +6237,12 @@ export class Fms<ID extends string = any> {
rnavTypeFlags !== undefined && this.approachDetails.set('rnavTypeFlags', rnavTypeFlags);
isCircling !== undefined && this.approachDetails.set('isCircling', isCircling);
isVtf !== undefined && this.approachDetails.set('isVtf', isVtf);
isRnpAr !== undefined && this.approachDetails.set('isRnpAr', isRnpAr);
referenceFacility !== undefined && this.approachDetails.set('referenceFacility', referenceFacility);
runway !== undefined && this.approachDetails.set('runway', runway);

const approachDetails = this.approachDetails.get();

// If an approach is flagged as RNAV but has no defined RNAV minima, assume it is an RNP (AR) approach if it is not circling.
this.approachDetails.set('isRnpAr', approachDetails.type === ApproachType.APPROACH_TYPE_RNAV && approachDetails.bestRnavType === 0 && !approachDetails.isCircling);

if (this.needPublishApproachDetails) {
this.needPublishApproachDetails = false;

Expand Down
2 changes: 1 addition & 1 deletion src/garminsdk/flightplan/FmsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class FmsUtils {
);

const runwayCode = RunwayUtils.getRunwayCode(runway.direction);
const runwayLetter = RunwayUtils.getDesignatorLetter(runway.runwayDesignator).padStart(1, ' ');
const runwayLetter = RunwayUtils.getDesignatorLetter(runway.runwayDesignator).padStart(1, '-');
initialLegIdent ??= 'STRGHT';

const iafFixIcao = ICAO.value(IcaoType.VisualApproach, `${runwayCode}${runwayLetter}`, airport.icaoStruct.ident, initialLegIdent);
Expand Down
4 changes: 2 additions & 2 deletions src/garminsdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@microsoft/msfs-garminsdk",
"version": "2.0.5",
"version": "2.0.7",
"description": "Common files for the Garmin series of avionics systems",
"main": "garminsdk.js",
"scripts": {
Expand All @@ -11,7 +11,7 @@
"author": "Working Title Simulations",
"license": "MIT",
"devDependencies": {
"@microsoft/msfs-sdk": "2.0.5",
"@microsoft/msfs-sdk": "2.0.7",
"@microsoft/msfs-types": "1.14.6",
"@rollup/plugin-node-resolve": "^15.0.1",
"@types/node": "^18.11.18",
Expand Down
20 changes: 15 additions & 5 deletions src/sdk/autopilot/calculators/SmoothingPathCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,9 +851,7 @@ export class SmoothingPathCalculator implements VNavPathCalculator {
}
}

if (constraint.index === verticalPlan.lastDescentConstraintLegIndex) {
constraint.isPathEnd = true;
constraint.isTarget = true;
if (constraint.isPathEnd) {
constraintIsBod = true;
}

Expand Down Expand Up @@ -1080,15 +1078,20 @@ export class SmoothingPathCalculator implements VNavPathCalculator {
}

/**
* Populates a vertical flight plan's constraints with legs and updates the constraint distances and VNAV path
* eligibility data.
* Populates a vertical flight plan's constraints with legs, updates the constraint distances and VNAV path
* eligibility data, and resets the constraint path and FPA data.
* @param verticalPlan The vertical flight plan for which to populate constraints.
*/
protected populateConstraints(verticalPlan: VerticalFlightPlan): void {
for (let constraintIndex = 0; constraintIndex < verticalPlan.constraints.length; constraintIndex++) {
const constraint = verticalPlan.constraints[constraintIndex];
const previousConstraint = verticalPlan.constraints[constraintIndex + 1];

constraint.isPathEnd = false;
constraint.isTarget = false;
constraint.fpa = 0;
constraint.targetAltitude = 0;

constraint.legs.length = 0;

constraint.distance = VNavUtils.getConstraintDistanceFromLegs(constraint, previousConstraint, verticalPlan);
Expand Down Expand Up @@ -1151,6 +1154,8 @@ export class SmoothingPathCalculator implements VNavPathCalculator {
return false;
}

let hasFoundPathEndConstraint = false;

for (let targetConstraintIndex = lastDescentConstraintIndex; targetConstraintIndex <= firstDescentConstraintIndex; targetConstraintIndex++) {
const constraint = verticalPlan.constraints[targetConstraintIndex];

Expand All @@ -1169,6 +1174,11 @@ export class SmoothingPathCalculator implements VNavPathCalculator {
currentTargetConstraint = constraint;
currentTargetConstraint.targetAltitude = constraint.minAltitude > Number.NEGATIVE_INFINITY ? constraint.minAltitude : constraint.maxAltitude;
currentTargetConstraint.isTarget = true;

if (!hasFoundPathEndConstraint) {
currentTargetConstraint.isPathEnd = true;
hasFoundPathEndConstraint = true;
}
} else { continue; }
}

Expand Down
41 changes: 39 additions & 2 deletions src/sdk/autopilot/directors/APAltCapDirector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { SimVarValueType } from '../../data/SimVars';
import { MathUtils } from '../../math/MathUtils';
import { UnitType } from '../../math/NumberUnit';
import { DebounceTimer } from '../../utils/time/DebounceTimer';
import { APValues } from '../APValues';
import { VNavUtils } from '../vnav/VNavUtils';
import { DirectorState, PlaneDirector } from './PlaneDirector';
Expand All @@ -21,6 +22,14 @@ export type APAltCapDirectorOptions = {
* A function that returns true if the capturing shall start.
*/
shouldActivate: APAltCapDirectorActivationFunc | undefined;

/**
* The time to inhibit altitude capture when the target altitude is changed, in ms.
* Setting the time to null disables inhibition.
* Defaults to 500 ms.
* Note that if alt capture is already active when the target is changed, this will have no effect.
*/
targetChangeInhibitTime: number | null;
};

/**
Expand Down Expand Up @@ -57,6 +66,11 @@ export type APAltCapDirectorActivationFunc = (
* An altitude capture autopilot director.
*/
export class APAltCapDirector implements PlaneDirector {
private static readonly DEFAULT_TARGET_CHANGE_INHIBIT_MS = 500;
private static readonly EMPTY_FUNCTION = (): void => {};

private readonly targetChangeInhibitTimer?: DebounceTimer;
private readonly targetChangeInhibitTime: number | null = APAltCapDirector.DEFAULT_TARGET_CHANGE_INHIBIT_MS;

public state: DirectorState;

Expand All @@ -73,6 +87,11 @@ export class APAltCapDirector implements PlaneDirector {
private readonly captureAltitude: APAltCapDirectorCaptureFunc = APAltCapDirector.captureAltitude;
private readonly shouldActivate: APAltCapDirectorActivationFunc = APAltCapDirector.shouldActivate;

/**
* Inhibits altitude capture actrivation for {@link APAltCapDirector.targetChangeInhibitTime}.
*/
private inhibitAltCapture?: () => void;

/**
* Creates an instance of the APAltCapDirector.
* @param apValues Autopilot data for this director.
Expand All @@ -81,6 +100,10 @@ export class APAltCapDirector implements PlaneDirector {
* defined, a default function is used.
* --> captureAltitude: An optional function which calculates desired pitch angles to capture a target altitude. If not
* defined, a default function is used.
* --> targetChangeInhibitTime: The time to inhibit altitude capture when the target altitude is changed, in ms.
* Setting the time to null disables inhibition.
* Defaults to 500 ms.
* Note that if alt capture is already active when the target is changed, this will have no effect.
*/
constructor(
private readonly apValues: APValues,
Expand All @@ -93,8 +116,18 @@ export class APAltCapDirector implements PlaneDirector {
if (options?.shouldActivate !== undefined) {
this.shouldActivate = options.shouldActivate;
}
}
if (options?.targetChangeInhibitTime !== undefined) {
this.targetChangeInhibitTime = options.targetChangeInhibitTime;
}

if (this.targetChangeInhibitTime !== null) {
this.targetChangeInhibitTimer = new DebounceTimer();
this.inhibitAltCapture = () => {
this.targetChangeInhibitTimer?.schedule(APAltCapDirector.EMPTY_FUNCTION, this.targetChangeInhibitTime!);
};
this.apValues.selectedAltitude.sub(this.inhibitAltCapture, false);
}
}

/**
* Activates this director.
Expand Down Expand Up @@ -157,6 +190,10 @@ export class APAltCapDirector implements PlaneDirector {
* Attempts to activate altitude capture.
*/
private tryActivate(): void {
if (this.targetChangeInhibitTimer?.isPending()) {
return;
}

const selectedAltitude = this.apValues.selectedAltitude.get();
const vs = SimVar.GetSimVarValue('VERTICAL SPEED', SimVarValueType.FPM);
const alt = SimVar.GetSimVarValue('INDICATED ALTITUDE', SimVarValueType.Feet);
Expand Down Expand Up @@ -226,4 +263,4 @@ export class APAltCapDirector implements PlaneDirector {
const desiredFpa = MathUtils.clamp(Math.asin(desiredVs / UnitType.KNOT.convertTo(tas, UnitType.FPM)) * Avionics.Utils.RAD2DEG, -initialFpaAbs, initialFpaAbs);
return MathUtils.clamp(desiredFpa, -15, 15);
}
}
}
Loading

0 comments on commit c89cf80

Please sign in to comment.