Skip to content

Commit e6a1a31

Browse files
author
mattnischan
committed
SU2 release.
1 parent c89cf80 commit e6a1a31

File tree

494 files changed

+28100
-3723
lines changed

Some content is hidden

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

494 files changed

+28100
-3723
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Base events related to barometric transition alerts.
3+
*/
4+
export interface BaseBaroTransitionAlertEvents {
5+
/** Whether the transition altitude alert is active. */
6+
baro_transition_alert_altitude_active: boolean;
7+
8+
/** Whether the transition level alert is active. */
9+
baro_transition_alert_level_active: boolean;
10+
}
11+
12+
/**
13+
* Events related barometric transition alerts with a specific ID.
14+
*/
15+
export type SuffixedBaroTransitionAlertEvents<Id extends string> = {
16+
[P in keyof BaseBaroTransitionAlertEvents as `${P}_${Id}`]: BaseBaroTransitionAlertEvents[P];
17+
};
18+
19+
/**
20+
* Events related to barometric transition alerts.
21+
*/
22+
export type BaroTransitionAlertEvents = SuffixedBaroTransitionAlertEvents<string>;
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
import {
2+
ConsumerSubject, EventBus, MappedSubject, Subject, Subscribable, SubscribableMapFunctions, SubscribableUtils,
3+
Subscription, UserSettingManager, Value
4+
} from '@microsoft/msfs-sdk';
5+
6+
import { AdcSystemEvents } from '../../system/AdcSystem';
7+
import { BaroTransitionAlertUserSettingTypes } from '../../settings/BaroTransitionAlertUserSettings';
8+
import { BaroTransitionAlertEvents } from './BaroTransitionAlertEvents';
9+
10+
/**
11+
* Configuration options for {@link BaroTransitionAlertManager}.
12+
*/
13+
export type BaroTransitionAlertManagerOptions = {
14+
/**
15+
* The ID to assign the manager. Event bus topics published by the manager will be suffixed with its ID. Cannot be
16+
* the empty string.
17+
*/
18+
id: string;
19+
20+
/** The index of the ADC from which to source altitude and barometric setting data. */
21+
adcIndex: number | Subscribable<number>;
22+
};
23+
24+
/**
25+
* Barometric transition alert states.
26+
*/
27+
enum AlertState {
28+
Off = 'Off',
29+
Unarmed = 'Unarmed',
30+
Armed = 'Armed',
31+
Active = 'Active',
32+
ActiveLocked = 'ActiveLocked',
33+
}
34+
35+
/**
36+
* A manager that controls the state of a set of barometric transition alerts. Each manager controls a transition
37+
* altitude alert, which is triggered when climbing through a transition altitude without changing an altimeter's
38+
* barometric setting to standard, and a transition level alert, which is triggered when descending through a
39+
* transition level without changing an altimeter's barometric setting out of standard. The states of the alerts are
40+
* published to the topics defined in {@link BaroTransitionAlertEvents}.
41+
*
42+
* The manager requires that the topics defined in {@link AdcSystemEvents} are published to the event bus.
43+
*/
44+
export class BaroTransitionAlertManager {
45+
private static readonly ALTITUDE_MARGIN = 200; // feet
46+
private static readonly ALTITUDE_HYSTERESIS = 80; // feet
47+
48+
private readonly publisher = this.bus.getPublisher<BaroTransitionAlertEvents>();
49+
50+
private readonly id: string;
51+
52+
private readonly adcIndex: Subscribable<number>;
53+
54+
private readonly isAltitudeDataValid = ConsumerSubject.create(null, false);
55+
private readonly indicatedAlt = ConsumerSubject.create(null, 0);
56+
private readonly baroIsStdActive = ConsumerSubject.create(null, false);
57+
58+
private readonly indicatedAltRounded = this.indicatedAlt.map(SubscribableMapFunctions.withPrecision(1));
59+
60+
private readonly canAlertAltitude = MappedSubject.create(
61+
([isEnabled, threshold, isAltitudeDataValid, isStdActive]) => isEnabled && threshold >= 0 && isAltitudeDataValid && !isStdActive,
62+
this.settingManager.getSetting('baroTransitionAlertAltitudeEnabled'),
63+
this.settingManager.getSetting('baroTransitionAlertAltitudeThreshold'),
64+
this.isAltitudeDataValid,
65+
this.baroIsStdActive,
66+
);
67+
private readonly canAlertLevel = MappedSubject.create(
68+
([isEnabled, threshold, isAltitudeDataValid, isStdActive]) => isEnabled && threshold >= 0 && isAltitudeDataValid && isStdActive,
69+
this.settingManager.getSetting('baroTransitionAlertLevelEnabled'),
70+
this.settingManager.getSetting('baroTransitionAlertLevelThreshold'),
71+
this.isAltitudeDataValid,
72+
this.baroIsStdActive,
73+
);
74+
75+
private readonly altitudeInputs = MappedSubject.create(
76+
this.settingManager.getSetting('baroTransitionAlertAltitudeThreshold'),
77+
this.indicatedAltRounded
78+
);
79+
private readonly levelInputs = MappedSubject.create(
80+
this.settingManager.getSetting('baroTransitionAlertLevelThreshold'),
81+
this.indicatedAltRounded
82+
);
83+
84+
private readonly altitudeAlertState = Subject.create(AlertState.Off);
85+
private readonly altitudeLastActiveAlt = Value.create(0);
86+
87+
private readonly levelAlertState = Subject.create(AlertState.Off);
88+
private readonly levelLastActiveAlt = Value.create(0);
89+
90+
private isAlive = true;
91+
private isInit = false;
92+
private isResumed = false;
93+
94+
private adcIndexSub?: Subscription;
95+
96+
private altitudeInputsSub?: Subscription;
97+
private levelInputsSub?: Subscription;
98+
99+
private altitudeCanAlertSub?: Subscription;
100+
private levelCanAlertSub?: Subscription;
101+
102+
/**
103+
* Creates a new instance of BaroTransitionAlertManager. The manager is created in an uninitialized and paused state.
104+
* @param bus The event bus.
105+
* @param settingManager A manager for barometric transition alert user settings.
106+
* @param options Options with which to configure the manager.
107+
* @throws Error if `options.id` is the empty string.
108+
*/
109+
public constructor(
110+
private readonly bus: EventBus,
111+
private readonly settingManager: UserSettingManager<BaroTransitionAlertUserSettingTypes>,
112+
options: Readonly<BaroTransitionAlertManagerOptions>,
113+
) {
114+
if (options.id === '') {
115+
throw new Error('BaroTransitionAlertManager: ID cannot be the empty string');
116+
}
117+
118+
this.id = options.id;
119+
120+
this.adcIndex = SubscribableUtils.toSubscribable(options.adcIndex, true);
121+
}
122+
123+
/**
124+
* Initializes this manager. Once initialized, the manager will be ready to automatically control the states of its
125+
* barometric transition alerts.
126+
* @throws Error if this manager has been destroyed.
127+
*/
128+
public init(): void {
129+
if (!this.isAlive) {
130+
throw new Error('BaroTransitionAlertManager::init(): cannot initialize a dead manager');
131+
}
132+
133+
if (this.isInit) {
134+
return;
135+
}
136+
137+
this.isInit = true;
138+
139+
const isAlertActive = (state: AlertState): boolean => state === AlertState.Active || state === AlertState.ActiveLocked;
140+
this.altitudeAlertState.map(isAlertActive).sub(this.publishEvent.bind(this, `baro_transition_alert_altitude_active_${this.id}`), true);
141+
this.levelAlertState.map(isAlertActive).sub(this.publishEvent.bind(this, `baro_transition_alert_level_active_${this.id}`), true);
142+
143+
this.adcIndexSub = this.adcIndex.sub(this.onAdcIndexChanged.bind(this), true);
144+
145+
this.altitudeInputsSub = this.altitudeInputs.sub(this.updateAlertState.bind(this, this.altitudeAlertState, this.altitudeLastActiveAlt, 1), false, true);
146+
this.levelInputsSub = this.levelInputs.sub(this.updateAlertState.bind(this, this.levelAlertState, this.levelLastActiveAlt, -1), false, true);
147+
148+
this.altitudeCanAlertSub = this.canAlertAltitude.sub(this.onCanAlertChanged.bind(this, this.altitudeAlertState, this.altitudeInputsSub), false, true);
149+
this.levelCanAlertSub = this.canAlertLevel.sub(this.onCanAlertChanged.bind(this, this.levelAlertState, this.levelInputsSub), false, true);
150+
}
151+
152+
/**
153+
* Resets this manager such that its managed alerts are deactivated. This method has no effect if the manager is not
154+
* initialized.
155+
* @throws Error if this manager has been destroyed.
156+
*/
157+
public reset(): void {
158+
if (!this.isAlive) {
159+
throw new Error('BaroTransitionAlertManager::reset(): cannot reset a dead manager');
160+
}
161+
162+
if (!this.isInit) {
163+
return;
164+
}
165+
166+
if (this.canAlertAltitude.get()) {
167+
this.altitudeAlertState.set(AlertState.Unarmed);
168+
}
169+
170+
if (this.canAlertLevel.get()) {
171+
this.levelAlertState.set(AlertState.Unarmed);
172+
}
173+
}
174+
175+
/**
176+
* Resumes this manager. When the manager is resumed, it will automatically control the states of its barometric
177+
* transition alerts. This method has no effect if the manager is not initialized.
178+
* @throws Error if this manager has been destroyed.
179+
*/
180+
public resume(): void {
181+
if (!this.isAlive) {
182+
throw new Error('BaroTransitionAlertManager::resume(): cannot resume a dead manager');
183+
}
184+
185+
if (!this.isInit || this.isResumed) {
186+
return;
187+
}
188+
189+
this.isAltitudeDataValid.resume();
190+
this.indicatedAlt.resume();
191+
this.baroIsStdActive.resume();
192+
193+
this.canAlertAltitude.resume();
194+
this.canAlertLevel.resume();
195+
196+
this.altitudeInputs.resume();
197+
this.levelInputs.resume();
198+
199+
this.altitudeCanAlertSub!.resume(true);
200+
this.levelCanAlertSub!.resume(true);
201+
}
202+
203+
/**
204+
* Pauses this manager. When the manager is paused, it will not change the states of its barometric transition
205+
* alerts, and the alerts will remain in the state they were in when the manager was paused. This method has no
206+
* effect if the manager is not initialized.
207+
* @throws Error if this manager has been destroyed.
208+
*/
209+
public pause(): void {
210+
if (!this.isAlive) {
211+
throw new Error('BaroTransitionAlertManager::pause(): cannot pause a dead manager');
212+
}
213+
214+
if (!this.isInit || !this.isResumed) {
215+
return;
216+
}
217+
218+
this.isAltitudeDataValid.pause();
219+
this.indicatedAlt.pause();
220+
this.baroIsStdActive.pause();
221+
222+
this.canAlertAltitude.pause();
223+
this.canAlertLevel.pause();
224+
225+
this.altitudeInputs.pause();
226+
this.levelInputs.pause();
227+
228+
this.altitudeCanAlertSub!.pause();
229+
this.levelCanAlertSub!.pause();
230+
231+
this.altitudeInputsSub!.pause();
232+
this.levelInputsSub!.pause();
233+
}
234+
235+
/**
236+
* Publishes an event bus topic from `BaroTransitionAlertEvents`.
237+
* @param topic The topic to publish.
238+
* @param data The topic data.
239+
*/
240+
private publishEvent<K extends keyof BaroTransitionAlertEvents>(topic: K, data: BaroTransitionAlertEvents[K]): void {
241+
this.publisher.pub(topic, data, true, true);
242+
}
243+
244+
/**
245+
* Responds to when the index of the ADC from which this manager sources data changes.
246+
* @param index The index of the new ADC from which this manager sources data.
247+
*/
248+
private onAdcIndexChanged(index: number): void {
249+
const sub = this.bus.getSubscriber<AdcSystemEvents>();
250+
251+
if (index >= 0) {
252+
this.isAltitudeDataValid.reset(false, sub.on(`adc_altitude_data_valid_${index}`));
253+
this.indicatedAlt.reset(0, sub.on(`adc_indicated_alt_${index}`));
254+
this.baroIsStdActive.reset(false, sub.on(`adc_altimeter_baro_is_std_${index}`));
255+
} else {
256+
this.isAltitudeDataValid.reset(false);
257+
this.indicatedAlt.reset(0);
258+
this.baroIsStdActive.reset(false);
259+
}
260+
}
261+
262+
/**
263+
* Responds to when whether one of this manager's alerts can be activated changes.
264+
* @param alertState The state of the alert.
265+
* @param inputsSub The subscription that updates the state of the alert when it can be activated.
266+
* @param canAlert Whether the alert can be activated.
267+
*/
268+
private onCanAlertChanged(alertState: Subject<AlertState>, inputsSub: Subscription, canAlert: boolean): void {
269+
if (canAlert) {
270+
alertState.set(AlertState.Unarmed);
271+
inputsSub.resume(true);
272+
} else {
273+
inputsSub.pause();
274+
alertState.set(AlertState.Off);
275+
}
276+
}
277+
278+
/**
279+
* Updates the state of one of this manager's alerts.
280+
* @param alertState The state of the alert to update.
281+
* @param lastActiveAlt The altitude threshold, in feet, that triggered the most recent activation of the alert to
282+
* update.
283+
* @param direction +1 if the alert is activated while climbing through its altitude threshold, or -1 if the alert is
284+
* activated while descending through the threshold.
285+
* @param inputs The input data for the alert state, as
286+
* `[alert altitude threshold (feet), indicated altitude (feet)]`.
287+
*/
288+
private updateAlertState(
289+
alertState: Subject<AlertState>,
290+
lastActiveAlt: Value<number>,
291+
direction: 1 | -1,
292+
inputs: readonly [number, number]
293+
): void {
294+
const threshold = inputs[0];
295+
const thresholdWithMargin = threshold * direction - BaroTransitionAlertManager.ALTITUDE_MARGIN;
296+
const altitude = inputs[1] * direction;
297+
298+
const state = alertState.get();
299+
switch (state) {
300+
case AlertState.Active:
301+
if (threshold === lastActiveAlt.get()) {
302+
if (altitude < thresholdWithMargin - BaroTransitionAlertManager.ALTITUDE_HYSTERESIS) {
303+
alertState.set(AlertState.Armed);
304+
}
305+
break;
306+
} else {
307+
// If the alert is active but the threshold altitude has changed, then lock the alert to the active state
308+
// until it meets the activation criteria for the new threshold, at which point we revert back to the normal
309+
// active state.
310+
alertState.set(AlertState.ActiveLocked);
311+
}
312+
// fallthrough
313+
case AlertState.ActiveLocked:
314+
case AlertState.Armed:
315+
if (altitude >= thresholdWithMargin) {
316+
alertState.set(AlertState.Active);
317+
lastActiveAlt.set(threshold);
318+
}
319+
break;
320+
default: // Unarmed or Off
321+
if (altitude < thresholdWithMargin) {
322+
alertState.set(AlertState.Armed);
323+
}
324+
}
325+
}
326+
327+
/**
328+
* Destroys this manager. Destroying the manager stops it from automatically controlling the states of its barometric
329+
* transition alerts and allows resources used by the manager to be freed.
330+
*/
331+
public destroy(): void {
332+
this.isAlive = false;
333+
334+
this.adcIndexSub?.destroy();
335+
336+
this.isAltitudeDataValid.destroy();
337+
this.indicatedAlt.destroy();
338+
this.baroIsStdActive.destroy();
339+
340+
this.canAlertAltitude.destroy();
341+
this.canAlertLevel.destroy();
342+
343+
this.altitudeInputs.destroy();
344+
this.levelInputs.destroy();
345+
}
346+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './BaroTransitionAlertEvents';
2+
export * from './BaroTransitionAlertManager';

src/garminsdk/alerts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './barotransition';

0 commit comments

Comments
 (0)