Skip to content

Commit 28dc6a6

Browse files
committed
MSFS 2020 SU16 release.
1 parent 982d9da commit 28dc6a6

File tree

3,285 files changed

+177475
-20054
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

3,285 files changed

+177475
-20054
lines changed

src/garminsdk/autopilot/GarminAPConfig.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ export type GarminAPConfigDirectorOptions = {
9393
*/
9494
lowBankAngle?: number;
9595

96+
/**
97+
* The target pitch angle, in degrees, commanded by the TO director. Positive values indicate upward pitch. Defaults
98+
* to `10`.
99+
*/
100+
toPitchAngle?: number;
101+
102+
/**
103+
* The target pitch angle, in degrees, commanded by the GA director. Positive values indicate upward pitch. Defaults
104+
* to `7.5`.
105+
*/
106+
gaPitchAngle?: number;
107+
96108
/**
97109
* The threshold difference between selected heading and current heading, in degrees, at which the heading director
98110
* unlocks its commanded turn direction and chooses a new optimal turn direction to establish on the selected
@@ -127,6 +139,9 @@ export type GarminAPConfigOptions = GarminAPConfigDirectorOptions & {
127139
* `navToNavGuidance` is undefined.
128140
*/
129141
navToNavOptions?: Readonly<GarminNavToNavManager2Options>;
142+
143+
/** Whether to deactivate the autopilot when GA mode is armed in response to a TO/GA mode button press. Defaults to `true`. */
144+
deactivateAutopilotOnGa?: boolean;
130145
}
131146

132147
/**
@@ -148,6 +163,12 @@ export class GarminAPConfig implements GarminAPConfigInterface {
148163
/** The default maximum bank angle, in degrees, in Low Bank Mode. */
149164
public static readonly DEFAULT_LOW_BANK_ANGLE = 15;
150165

166+
/** The default target pitch angle, in degrees, commanded by the TO director. Positive values indicate upward pitch. */
167+
public static readonly DEFAULT_TO_PITCH_ANGLE = 10;
168+
169+
/** The default target pitch angle, in degrees, commanded by the GA director. Positive values indicate upward pitch. */
170+
public static readonly DEFAULT_GA_PITCH_ANGLE = 7.5;
171+
151172
/** The default HDG director turn direction unlock threshold, in degrees. */
152173
public static readonly DEFAULT_HDG_DIRECTION_UNLOCK_THRESHOLD = 331;
153174

@@ -158,6 +179,9 @@ export class GarminAPConfig implements GarminAPConfigInterface {
158179
/** @inheritDoc */
159180
public readonly cdiId: string;
160181

182+
/** @inheritDoc */
183+
public readonly deactivateAutopilotOnGa: boolean;
184+
161185
public autopilotDriverOptions: AutopilotDriverOptions;
162186

163187
/**
@@ -185,6 +209,9 @@ export class GarminAPConfig implements GarminAPConfigInterface {
185209
private readonly lnavMaxBankAngle: number;
186210
private readonly lowBankAngle: number;
187211

212+
private readonly toPitchAngle: number;
213+
private readonly gaPitchAngle: number;
214+
188215
private readonly hdgTurnReversalThreshold: number;
189216

190217
private vnavManager?: GarminVNavManager2;
@@ -248,6 +275,7 @@ export class GarminAPConfig implements GarminAPConfigInterface {
248275
options?: Readonly<GarminAPConfigOptions>
249276
) {
250277
this.cdiId = options?.cdiId ?? '';
278+
this.deactivateAutopilotOnGa = options?.deactivateAutopilotOnGa ?? true;
251279

252280
if (arg2 instanceof FlightPlanner) {
253281
this.flightPlanner = arg2;
@@ -305,6 +333,9 @@ export class GarminAPConfig implements GarminAPConfigInterface {
305333
this.lnavMaxBankAngle = options?.lnavMaxBankAngle ?? GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
306334
this.lowBankAngle = options?.lowBankAngle ?? GarminAPConfig.DEFAULT_LOW_BANK_ANGLE;
307335
this.hdgTurnReversalThreshold = options?.hdgTurnReversalThreshold ?? GarminAPConfig.DEFAULT_HDG_DIRECTION_UNLOCK_THRESHOLD;
336+
337+
this.toPitchAngle = options?.toPitchAngle ?? GarminAPConfig.DEFAULT_TO_PITCH_ANGLE;
338+
this.gaPitchAngle = options?.gaPitchAngle ?? GarminAPConfig.DEFAULT_GA_PITCH_ANGLE;
308339
}
309340

310341
/** @inheritdoc */
@@ -372,6 +403,9 @@ export class GarminAPConfig implements GarminAPConfigInterface {
372403
public createVorDirector(apValues: APValues): APNavDirector {
373404
return new APNavDirector(this.bus, apValues, APLateralModes.VOR, {
374405
maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.vorMaxBankAngle, this.lowBankAngle) : this.vorMaxBankAngle,
406+
canArm: GarminAPUtils.navCanArm,
407+
canActivate: GarminAPUtils.navCanActivate,
408+
canRemainActive: GarminAPUtils.navCanRemainActive,
375409
lateralInterceptCurve: GarminAPUtils.navIntercept
376410
});
377411
}
@@ -380,6 +414,9 @@ export class GarminAPConfig implements GarminAPConfigInterface {
380414
public createLocDirector(apValues: APValues): APNavDirector {
381415
return new APNavDirector(this.bus, apValues, APLateralModes.LOC, {
382416
maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.locMaxBankAngle, this.lowBankAngle) : this.locMaxBankAngle,
417+
canArm: GarminAPUtils.navCanArm,
418+
canActivate: GarminAPUtils.navCanActivate,
419+
canRemainActive: GarminAPUtils.navCanRemainActive,
383420
lateralInterceptCurve: GarminAPUtils.navIntercept
384421
});
385422
}
@@ -388,6 +425,9 @@ export class GarminAPConfig implements GarminAPConfigInterface {
388425
public createBcDirector(apValues: APValues): APBackCourseDirector {
389426
return new APBackCourseDirector(this.bus, apValues, {
390427
maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.locMaxBankAngle, this.lowBankAngle) : this.locMaxBankAngle,
428+
canArm: GarminAPUtils.backCourseCanArm,
429+
canActivate: GarminAPUtils.backCourseCanActivate,
430+
canRemainActive: GarminAPUtils.backCourseCanRemainActive,
391431
lateralInterceptCurve: (distanceToSource: number, deflection: number, xtk: number, tas: number) => GarminAPUtils.localizerIntercept(xtk, tas)
392432
});
393433
}
@@ -440,6 +480,7 @@ export class GarminAPConfig implements GarminAPConfigInterface {
440480
public createGpDirector(apValues: APValues): APGPDirector {
441481
return new APGPDirector(this.bus, apValues, {
442482
guidance: this.glidepathGuidance,
483+
canArm: GarminAPUtils.glidepathCanArm.bind(undefined, apValues),
443484
canCapture: this.glidepathGuidance
444485
? () => {
445486
return apValues.lateralActive.get() === APLateralModes.GPSS && this.glidepathGuidance!.get().canCapture;
@@ -450,19 +491,21 @@ export class GarminAPConfig implements GarminAPConfigInterface {
450491

451492
/** @inheritdoc */
452493
public createGsDirector(apValues: APValues): APGSDirector {
453-
return new APGSDirector(this.bus, apValues);
494+
return new APGSDirector(this.bus, apValues, {
495+
canArm: GarminAPUtils.glideslopeCanArm,
496+
canActivate: GarminAPUtils.glideslopeCanActivate,
497+
canRemainActive: GarminAPUtils.glideslopeCanRemainActive
498+
});
454499
}
455500

456501
/** @inheritdoc */
457-
public createToVerticalDirector(): PlaneDirector | undefined {
458-
//TODO: This value should be read in from the systems.cfg 'pitch_takeoff_ga' value
459-
460-
return new APTogaPitchDirector(10);
502+
public createToVerticalDirector(apValues: APValues): PlaneDirector | undefined {
503+
return new APTogaPitchDirector(apValues, { targetPitchAngle: this.toPitchAngle });
461504
}
462505

463506
/** @inheritdoc */
464-
public createGaVerticalDirector(): PlaneDirector | undefined {
465-
return new APTogaPitchDirector(7.5);
507+
public createGaVerticalDirector(apValues: APValues): PlaneDirector | undefined {
508+
return new APTogaPitchDirector(apValues, { targetPitchAngle: this.gaPitchAngle });
466509
}
467510

468511
/** @inheritdoc */

src/garminsdk/autopilot/GarminAPUtils.ts

Lines changed: 206 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,214 @@
1-
import { MathUtils, NavMath, UnitType } from '@microsoft/msfs-sdk';
1+
import {
2+
APBackCourseDirectorActivateNavData,
3+
APBackCourseDirectorNavData,
4+
APGSDirectorActivateNavData, APGSDirectorNavData, APLateralModes, APNavDirectorActivateNavData, APNavDirectorNavData,
5+
APValues, APVerticalModes, MathUtils, NavMath, NavSourceType, RadioUtils, SimVarValueType, UnitType
6+
} from '@microsoft/msfs-sdk';
27

38
/**
49
* A utility class for working with Garmin autopilots.
510
*/
611
export class GarminAPUtils {
12+
/**
13+
* Checks whether a nav director can be armed.
14+
* @param mode The director's lateral mode.
15+
* @param apValues Autopilot values from the director's parent autopilot.
16+
* @param navData The current radio navigation data received by the director.
17+
* @returns Whether the director can be armed.
18+
*/
19+
public static navCanArm(mode: APLateralModes, apValues: APValues, navData: Readonly<APNavDirectorNavData>): boolean {
20+
if (apValues.cdiSource.get().type === NavSourceType.Gps) {
21+
return !!apValues.navToNavArmableLateralMode && apValues.navToNavArmableLateralMode() === mode;
22+
} else {
23+
return navData.navSource.index !== 0 && RadioUtils.isLocalizerFrequency(navData.frequency) === (mode === APLateralModes.LOC);
24+
}
25+
}
26+
27+
/**
28+
* Checks whether a nav director can be activated from an armed state.
29+
* @param mode The director's lateral mode.
30+
* @param apValues Autopilot values from the director's parent autopilot.
31+
* @param navData The current radio navigation data received by the director.
32+
* @returns Whether the director can be activated from an armed state.
33+
*/
34+
public static navCanActivate(mode: APLateralModes, apValues: APValues, navData: Readonly<APNavDirectorNavData>): boolean {
35+
if (
36+
apValues.cdiSource.get().type === NavSourceType.Nav
37+
&& navData.deviation !== null
38+
&& Math.abs(navData.deviation) < 1
39+
&& (navData.hasLoc || navData.obsCourse !== null)
40+
) {
41+
const dtk = navData.hasLoc
42+
? navData.locCourse
43+
: navData.obsCourse;
44+
if (dtk === null) {
45+
return false;
46+
}
47+
const headingDiff = NavMath.diffAngle(SimVar.GetSimVarValue('PLANE HEADING DEGREES MAGNETIC', SimVarValueType.Degree), dtk);
48+
const sensitivity = navData.hasLoc ? 1 : .6;
49+
if (Math.abs(navData.deviation * sensitivity) < 1 && Math.abs(headingDiff) < 110) {
50+
return true;
51+
}
52+
}
53+
54+
return false;
55+
}
56+
57+
/**
58+
* Checks whether a nav director can remain in the active state.
59+
* @param mode The director's lateral mode.
60+
* @param apValues Autopilot values from the director's parent autopilot.
61+
* @param navData The current radio navigation data received by the director.
62+
* @param isInZoneOfConfusion Whether the source of the radio navigation data is a VOR and the airplane's position
63+
* is close enough to the VOR to render lateral deviation values unreliable.
64+
* @param activateNavData The radio navigation data received by the director at the moment of activation.
65+
* @returns Whether the director can remain in the active state.
66+
*/
67+
public static navCanRemainActive(
68+
mode: APLateralModes,
69+
apValues: APValues,
70+
navData: Readonly<APNavDirectorNavData>,
71+
isInZoneOfConfusion: boolean,
72+
activateNavData: Readonly<APNavDirectorActivateNavData>
73+
): boolean {
74+
if (
75+
apValues.cdiSource.get().type !== NavSourceType.Nav
76+
|| navData.navSource.index !== activateNavData.navSource.index
77+
|| navData.frequency !== activateNavData.frequency
78+
) {
79+
return false;
80+
}
81+
82+
if (mode === APLateralModes.LOC) {
83+
return navData.hasLoc
84+
&& navData.locCourse !== null
85+
&& navData.deviation !== null
86+
&& Math.abs(navData.deviation) < 1;
87+
} else {
88+
return !navData.hasLoc
89+
&& navData.obsCourse !== null
90+
&& (isInZoneOfConfusion || (navData.deviation !== null && Math.abs(navData.deviation) < 1));
91+
}
92+
}
93+
94+
/**
95+
* Checks whether a localizer back-course director can be armed.
96+
* @param apValues Autopilot values from the director's parent autopilot.
97+
* @param navData The current radio navigation data received by the director.
98+
* @returns Whether the director can be armed.
99+
*/
100+
public static backCourseCanArm(apValues: APValues, navData: Readonly<APBackCourseDirectorNavData>): boolean {
101+
return apValues.cdiSource.get().type === NavSourceType.Nav
102+
&& navData.navSource.index !== 0
103+
&& RadioUtils.isLocalizerFrequency(navData.frequency);
104+
}
105+
106+
/**
107+
* Checks whether a localizer back-course director can be activated from an armed state.
108+
* @param apValues Autopilot values from the director's parent autopilot.
109+
* @param navData The current radio navigation data received by the director.
110+
* @returns Whether the director can be activated from an armed state.
111+
*/
112+
public static backCourseCanActivate(apValues: APValues, navData: Readonly<APBackCourseDirectorNavData>): boolean {
113+
if (
114+
apValues.cdiSource.get().type === NavSourceType.Nav
115+
&& navData.hasLoc
116+
&& navData.locCourse !== null
117+
&& navData.deviation !== null
118+
&& Math.abs(navData.deviation) < 1
119+
) {
120+
const dtk = NavMath.normalizeHeading(navData.locCourse + 180);
121+
const headingDiff = NavMath.diffAngle(SimVar.GetSimVarValue('PLANE HEADING DEGREES MAGNETIC', SimVarValueType.Degree), dtk);
122+
if (Math.abs(headingDiff) < 110) {
123+
return true;
124+
}
125+
}
126+
127+
return false;
128+
}
129+
130+
/**
131+
* Checks whether a localizer back-course director can remain in the active state.
132+
* @param apValues Autopilot values from the director's parent autopilot.
133+
* @param navData The current radio navigation data received by the director.
134+
* @param activateNavData The radio navigation data received by the director at the moment of activation.
135+
* @returns Whether the director can remain in the active state.
136+
*/
137+
public static backCourseCanRemainActive(
138+
apValues: APValues,
139+
navData: Readonly<APBackCourseDirectorNavData>,
140+
activateNavData: Readonly<APBackCourseDirectorActivateNavData>
141+
): boolean {
142+
return apValues.cdiSource.get().type === NavSourceType.Nav
143+
&& navData.navSource.index === activateNavData.navSource.index
144+
&& navData.frequency === activateNavData.frequency
145+
&& navData.hasLoc
146+
&& navData.locCourse !== null
147+
&& navData.deviation !== null
148+
&& Math.abs(navData.deviation) < 1;
149+
}
150+
151+
/**
152+
* Checks whether a glideslope director can be armed.
153+
* @param apValues Autopilot values from the director's parent autopilot.
154+
* @param navData The current radio navigation data received by the director.
155+
* @returns Whether the director can be armed.
156+
*/
157+
public static glideslopeCanArm(apValues: APValues, navData: Readonly<APGSDirectorNavData>): boolean {
158+
if (apValues.lateralActive.get() !== APLateralModes.LOC && apValues.lateralArmed.get() !== APLateralModes.LOC) {
159+
return false;
160+
}
161+
162+
if (apValues.cdiSource.get().type === NavSourceType.Gps) {
163+
return !!apValues.navToNavArmableVerticalMode && apValues.navToNavArmableVerticalMode() === APVerticalModes.GS;
164+
} else {
165+
return navData.navSource.index !== 0 && RadioUtils.isLocalizerFrequency(navData.frequency);
166+
}
167+
}
168+
169+
/**
170+
* Checks whether a glideslope director can be activated from an armed state.
171+
* @param apValues Autopilot values from the director's parent autopilot.
172+
* @param navData The current radio navigation data received by the director.
173+
* @returns Whether the director can be activated from an armed state.
174+
*/
175+
public static glideslopeCanActivate(apValues: APValues, navData: Readonly<APGSDirectorNavData>): boolean {
176+
return apValues.lateralActive.get() === APLateralModes.LOC
177+
&& navData.gsAngleError !== null
178+
&& Math.abs(navData.gsAngleError) <= 0.1;
179+
}
180+
181+
/**
182+
* Checks whether a glideslope director can remain in the active state.
183+
* @param apValues Autopilot values from the director's parent autopilot.
184+
* @param navData The current radio navigation data received by the director.
185+
* @param activateNavData The radio navigation data received by the director at the moment of activation.
186+
* @returns Whether the director can remain in the active state.
187+
*/
188+
public static glideslopeCanRemainActive(
189+
apValues: APValues,
190+
navData: Readonly<APGSDirectorNavData>,
191+
activateNavData: Readonly<APGSDirectorActivateNavData>
192+
): boolean {
193+
return apValues.lateralActive.get() === APLateralModes.LOC
194+
&& navData.navSource.index === activateNavData.navSource.index
195+
&& navData.frequency === activateNavData.frequency
196+
&& navData.gsAngleError !== null;
197+
}
198+
199+
/**
200+
* Checks whether a glidepath director can be armed.
201+
* @param apValues Autopilot values from the director's parent autopilot.
202+
* @returns Whether the director can be armed.
203+
*/
204+
public static glidepathCanArm(apValues: APValues): boolean {
205+
return apValues.cdiSource.get().type === NavSourceType.Gps
206+
&& (
207+
apValues.lateralActive.get() === APLateralModes.GPSS
208+
|| apValues.lateralArmed.get() === APLateralModes.GPSS
209+
);
210+
}
211+
7212
/**
8213
* Calculates intercept angles for radio nav.
9214
* @param distanceToSource The distance from the plane to the source of the navigation signal, in nautical miles.

0 commit comments

Comments
 (0)