77import { GarminObsDirector } from './directors';
88import { GarminNavToNavManager } from './GarminNavToNavManager';
99import { GarminVNavGuidanceOptions, GarminVNavManager2 } from './GarminVNavManager2';
10+ import { AutopilotDriverOptions } from '@microsoft/msfs-sdk/autopilot/AutopilotDriver';
1011
1112/**
1213 * Options for configuring a Garmin LNAV director.
@@ -17,38 +18,63 @@ export type GarminLNavDirectorOptions = Pick<LNavDirectorOptions, 'disableArming
1718 * Options for configuring Garmin autopilot directors.
1819 */
1920export type GarminAPConfigDirectorOptions = {
21+ /** The default rate at which commanded pitch changes, in degrees per second. Defaults to `5`. */
22+ defaultPitchRate?: number;
23+
24+ /** The default rate at which commanded bank changes, in degrees per second. Defaults to `10`. */
25+ defaultBankRate?: number;
26+
2027 /** Options for the LNAV director. */
21- lnavOptions: Partial<Readonly<GarminLNavDirectorOptions>>;
28+ lnavOptions? : Partial<Readonly<GarminLNavDirectorOptions>>;
2229
2330 /** Options for the VNAV director. */
24- vnavOptions: Partial<Readonly<GarminVNavGuidanceOptions>>;
31+ vnavOptions?: Partial<Readonly<GarminVNavGuidanceOptions>>;
32+
33+ /** The minimum bank angle, in degrees, supported by the ROL director. Defaults to `6`. */
34+ rollMinBankAngle?: number;
2535
26- /** The minimum bank angle, in degrees, supported by the ROL director. */
27- rollMinBankAngle : number;
36+ /** The maximum bank angle, in degrees, supported by the ROL director. Defaults to `25` . */
37+ rollMaxBankAngle? : number;
2838
29- /** The maximum bank angle, in degrees, supported by the ROL director. */
30- rollMaxBankAngle : number;
39+ /** The maximum bank angle, in degrees, supported by the HDG director. Defaults to `25` . */
40+ hdgMaxBankAngle? : number;
3141
32- /** The maximum bank angle, in degrees, supported by the HDG director. */
33- hdgMaxBankAngle : number;
42+ /** The maximum bank angle, in degrees, supported by the VOR director. Defaults to `25` . */
43+ vorMaxBankAngle? : number;
3444
35- /** The maximum bank angle, in degrees, supported by the VOR director. */
36- vorMaxBankAngle : number;
45+ /** The maximum bank angle, in degrees, supported by the LOC director. Defaults to `25` . */
46+ locMaxBankAngle? : number;
3747
38- /** The maximum bank angle, in degrees, supported by the LOC director. */
39- locMaxBankAngle : number;
48+ /** The maximum bank angle, in degrees, supported by the LNAV director. Defaults to `25` . */
49+ lnavMaxBankAngle? : number;
4050
41- /** The maximum bank angle, in degrees, supported by the LNAV director. */
42- lnavMaxBankAngle: number;
51+ /**
52+ * The maximum bank angle, in degrees, to apply to the HDG, VOR, LOC, and LNAV directors while in Low Bank Mode.
53+ * Defaults to `15`.
54+ */
55+ lowBankAngle?: number;
4356
44- /** The maximum bank angle, in degrees, to apply to the HDG, VOR, LOC, and LNAV directors while in Low Bank Mode. */
45- lowBankAngle: number;
57+ /**
58+ * The threshold difference between selected heading and current heading, in degrees, at which the heading director
59+ * unlocks its commanded turn direction and chooses a new optimal turn direction to establish on the selected
60+ * heading, potentially resulting in a turn reversal. Any value less than or equal to 180 degrees effectively
61+ * prevents the director from locking a commanded turn direction. Any value greater than or equal to 360 degrees will
62+ * require the selected heading to traverse past the current heading in the desired turn direction in order for the
63+ * director to issue a turn reversal. Defaults to `331`.
64+ */
65+ hdgTurnReversalThreshold?: number;
4666};
4767
4868/**
4969 * A Garmin Autopilot Configuration.
5070 */
5171export class GarminAPConfig implements APConfig {
72+ /** The default commanded pitch angle rate, in degrees per second. */
73+ public static readonly DEFAULT_PITCH_RATE = 5;
74+
75+ /** The default commanded bank angle rate, in degrees per second. */
76+ public static readonly DEFAULT_BANK_RATE = 10;
77+
5278 /** The default minimum bank angle, in degrees, for ROL director. */
5379 public static readonly DEFAULT_ROLL_MIN_BANK_ANGLE = 6;
5480
@@ -58,10 +84,15 @@ export class GarminAPConfig implements APConfig {
5884 /** The default maximum bank angle, in degrees, in Low Bank Mode. */
5985 public static readonly DEFAULT_LOW_BANK_ANGLE = 15;
6086
87+ /** The default HDG director turn direction unlock threshold, in degrees. */
88+ public static readonly DEFAULT_HDG_DIRECTION_UNLOCK_THRESHOLD = 331;
89+
6190 public defaultLateralMode = APLateralModes.ROLL;
6291 public defaultVerticalMode = APVerticalModes.PITCH;
6392 public defaultMaxBankAngle = GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
6493
94+ public autopilotDriverOptions: AutopilotDriverOptions;
95+
6596 /** Options for the LNAV director. */
6697 private readonly lnavOptions: Partial<Readonly<GarminLNavDirectorOptions>>;
6798
@@ -76,28 +107,26 @@ export class GarminAPConfig implements APConfig {
76107 private readonly lnavMaxBankAngle: number;
77108 private readonly lowBankAngle: number;
78109
110+ private readonly hdgTurnReversalThreshold: number;
111+
79112 /**
80113 * Creates a new instance of GarminAPConfig.
81114 * @param bus The event bus.
82115 * @param flightPlanner The flight planner.
83116 * @param verticalPathCalculator The vertical path calculator to use for the VNAV director.
84- * @param options Options to configure the directors. Option values default to the following if not defined:
85- * * `lnavOptions`: `undefined`
86- * * `vnavOptions`: `undefined`
87- * * `rollMinBankAngle`: `GarminAPConfig.DEFAULT_ROLL_MIN_BANK_ANGLE`
88- * * `rollMaxBankAngle`: `GarminAPConfig.DEFAULT_MAX_BANK_ANGLE`
89- * * `hdgMaxBankAngle`: `GarminAPConfig.DEFAULT_MAX_BANK_ANGLE`
90- * * `vorMaxBankAngle`: `GarminAPConfig.DEFAULT_MAX_BANK_ANGLE`
91- * * `locMaxBankAngle`: `GarminAPConfig.DEFAULT_MAX_BANK_ANGLE`
92- * * `lnavMaxBankAngle`: `GarminAPConfig.DEFAULT_MAX_BANK_ANGLE`
93- * * `lowBankAngle`: `GarminAPConfig.DEFAULT_LOW_BANK_ANGLE`
117+ * @param options Options to configure the directors.
94118 */
95119 constructor(
96120 private readonly bus: EventBus,
97121 private readonly flightPlanner: FlightPlanner,
98122 private readonly verticalPathCalculator: VNavPathCalculator,
99- options?: Partial< Readonly<GarminAPConfigDirectorOptions> >
123+ options?: Readonly<GarminAPConfigDirectorOptions>
100124 ) {
125+ this.autopilotDriverOptions = {
126+ pitchServoRate: options?.defaultPitchRate ?? GarminAPConfig.DEFAULT_PITCH_RATE,
127+ bankServoRate: options?.defaultBankRate ?? GarminAPConfig.DEFAULT_BANK_RATE
128+ };
129+
101130 this.lnavOptions = { ...options?.lnavOptions };
102131 this.vnavOptions = { ...options?.vnavOptions };
103132 this.rollMinBankAngle = options?.rollMinBankAngle ?? GarminAPConfig.DEFAULT_ROLL_MIN_BANK_ANGLE;
@@ -107,6 +136,7 @@ export class GarminAPConfig implements APConfig {
107136 this.locMaxBankAngle = options?.locMaxBankAngle ?? GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
108137 this.lnavMaxBankAngle = options?.lnavMaxBankAngle ?? GarminAPConfig.DEFAULT_MAX_BANK_ANGLE;
109138 this.lowBankAngle = options?.lowBankAngle ?? GarminAPConfig.DEFAULT_LOW_BANK_ANGLE;
139+ this.hdgTurnReversalThreshold = options?.hdgTurnReversalThreshold ?? GarminAPConfig.DEFAULT_HDG_DIRECTION_UNLOCK_THRESHOLD;
110140 }
111141
112142 /** @inheritdoc */
@@ -117,7 +147,8 @@ export class GarminAPConfig implements APConfig {
117147 /** @inheritdoc */
118148 public createHeadingDirector(apValues: APValues): APHdgDirector {
119149 return new APHdgDirector(this.bus, apValues, {
120- maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.hdgMaxBankAngle, this.lowBankAngle) : this.hdgMaxBankAngle
150+ maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.hdgMaxBankAngle, this.lowBankAngle) : this.hdgMaxBankAngle,
151+ turnReversalThreshold: this.hdgTurnReversalThreshold
121152 });
122153 }
123154
@@ -138,12 +169,12 @@ export class GarminAPConfig implements APConfig {
138169
139170 /** @inheritdoc */
140171 public createRollDirector(apValues: APValues): APRollDirector {
141- return new APRollDirector(this.bus, apValues, { minBankAngle: this.rollMinBankAngle, maxBankAngle: this.rollMaxBankAngle });
172+ return new APRollDirector(apValues, { minBankAngle: this.rollMinBankAngle, maxBankAngle: this.rollMaxBankAngle });
142173 }
143174
144175 /** @inheritdoc */
145- public createWingLevelerDirector(): APLvlDirector {
146- return new APLvlDirector(this.bus);
176+ public createWingLevelerDirector(apValues: APValues ): APLvlDirector {
177+ return new APLvlDirector(this.bus, apValues );
147178 }
148179
149180 /** @inheritdoc */
@@ -185,7 +216,7 @@ export class GarminAPConfig implements APConfig {
185216
186217 /** @inheritdoc */
187218 public createBcDirector(apValues: APValues): APBackCourseDirector {
188- return new APBackCourseDirector(this.bus, apValues, APLateralModes.BC, {
219+ return new APBackCourseDirector(this.bus, apValues, {
189220 maxBankAngle: () => apValues.maxBankId.get() === 1 ? Math.min(this.locMaxBankAngle, this.lowBankAngle) : this.locMaxBankAngle,
190221 lateralInterceptCurve: GarminAPConfig.navInterceptCurve
191222 });
@@ -203,7 +234,7 @@ export class GarminAPConfig implements APConfig {
203234
204235 /** @inheritdoc */
205236 public createVsDirector(apValues: APValues): APVSDirector {
206- return new APVSDirector(this.bus, apValues);
237+ return new APVSDirector(apValues);
207238 }
208239
209240 /** @inheritdoc */
@@ -213,17 +244,17 @@ export class GarminAPConfig implements APConfig {
213244
214245 /** @inheritdoc */
215246 public createFlcDirector(apValues: APValues): APFLCDirector {
216- return new APFLCDirector(this.bus, apValues);
247+ return new APFLCDirector(apValues);
217248 }
218249
219250 /** @inheritdoc */
220251 public createAltHoldDirector(apValues: APValues): APAltDirector {
221- return new APAltDirector(this.bus, apValues);
252+ return new APAltDirector(apValues);
222253 }
223254
224255 /** @inheritdoc */
225256 public createAltCapDirector(apValues: APValues): APAltCapDirector {
226- return new APAltCapDirector(this.bus, apValues);
257+ return new APAltCapDirector(apValues);
227258 }
228259
229260 /** @inheritdoc */
@@ -232,8 +263,9 @@ export class GarminAPConfig implements APConfig {
232263 }
233264
234265 /** @inheritdoc */
266+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
235267 public createVNavPathDirector(apValues: APValues): PlaneDirector | undefined {
236- return new APVNavPathDirector(this.bus, apValues );
268+ return new APVNavPathDirector(this.bus);
237269 }
238270
239271 /** @inheritdoc */
@@ -264,13 +296,18 @@ export class GarminAPConfig implements APConfig {
264296 }
265297
266298 /** @inheritdoc */
267- public createToLateralDirector(): PlaneDirector | undefined {
268- return new APLvlDirector(this.bus, true);
299+ public createToLateralDirector(apValues: APValues): PlaneDirector | undefined {
300+ return new APLvlDirector(this.bus, apValues, { isToGaMode: true });
301+ }
302+
303+ /** @inheritdoc */
304+ public createGaLateralDirector(apValues: APValues): PlaneDirector | undefined {
305+ return new APLvlDirector(this.bus, apValues, { isToGaMode: true });
269306 }
270307
271308 /** @inheritdoc */
272- public createGaLateralDirector (): PlaneDirector | undefined {
273- return new APLvlDirector(this.bus, true) ;
309+ public createFmsLocLateralDirector (): undefined {
310+ return undefined ;
274311 }
275312
276313 /** @inheritdoc */
@@ -282,18 +319,18 @@ export class GarminAPConfig implements APConfig {
282319 * Calculates intercept angles for radio nav.
283320 * @param distanceToSource The distance from the plane to the source of the navigation signal, in nautical miles.
284321 * @param deflection The lateral deflection of the desired track relative to the plane, normalized from `-1` to `1`.
285- * Negative values indicate that the desired track is to the left of the plane.
322+ * Positive values indicate that the desired track is to the right of the plane.
323+ * @param xtk The cross-track error of the plane from the desired track, in nautical miles. Positive values indicate
324+ * indicate that the plane is to the right of the track.
286325 * @param tas The true airspeed of the plane, in knots.
287326 * @param isLoc Whether the source of the navigation signal is a localizer. Defaults to `false`.
288327 * @returns The intercept angle, in degrees, to capture the desired track from the navigation signal.
289328 */
290- private static navInterceptCurve(distanceToSource: number, deflection: number, tas: number, isLoc?: boolean): number {
329+ private static navInterceptCurve(distanceToSource: number, deflection: number, xtk: number, tas: number, isLoc?: boolean): number {
291330 if (isLoc) {
292- return GarminAPConfig.localizerInterceptCurve(distanceToSource, deflection , tas);
331+ return GarminAPConfig.localizerInterceptCurve(xtk , tas);
293332 } else {
294- // max deflection is 10 degrees or 0.175 radians
295- const fullScaleDeflectionInRadians = 0.175;
296- return GarminAPConfig.defaultInterceptCurve(Math.sin(fullScaleDeflectionInRadians * -deflection) * distanceToSource, tas);
333+ return GarminAPConfig.defaultInterceptCurve(xtk, tas);
297334 }
298335 }
299336
@@ -311,24 +348,19 @@ export class GarminAPConfig implements APConfig {
311348
312349 /**
313350 * Calculates intercept angles for localizers.
314- * @param distanceToSource The distance from the plane to the localizer, in nautical miles.
315- * @param deflection The lateral deflection of the desired track relative to the plane, normalized from `-1` to `1`.
316- * Negative values indicate that the desired track is to the left of the plane.
351+ * @param xtk The cross-track error of the plane from the desired track, in nautical miles. Positive values indicate
352+ * indicate that the plane is to the right of the track.
317353 * @param tas The true airspeed of the plane, in knots.
318354 * @returns The intercept angle, in degrees, to capture the localizer course.
319355 */
320- private static localizerInterceptCurve(distanceToSource: number, deflection: number, tas: number): number {
321- // max deflection is 2.5 degrees or 0.0436332 radians
322- const fullScaleDeflectionInRadians = 0.0436332;
323-
324- const xtkNM = Math.sin(fullScaleDeflectionInRadians * -deflection) * distanceToSource;
325- const xtkMeters = UnitType.NMILE.convertTo(xtkNM, UnitType.METER);
356+ private static localizerInterceptCurve(xtk: number, tas: number): number {
357+ const xtkMeters = UnitType.NMILE.convertTo(xtk, UnitType.METER);
326358 const xtkMetersAbs = Math.abs(xtkMeters);
327359
328360 if (xtkMetersAbs < 4) {
329361 return 0;
330362 } else if (xtkMetersAbs < 250) {
331- return NavMath.clamp(Math.abs(xtkNM * 75), 1, 5);
363+ return NavMath.clamp(Math.abs(xtk * 75), 1, 5);
332364 }
333365
334366 const turnRadiusMeters = NavMath.turnRadius(tas, 22.5);
0 commit comments