From ebb3d259b5a08e401eba3ef3dd1757c22461f34e Mon Sep 17 00:00:00 2001 From: Nabeel Shahzad Date: Thu, 19 Sep 2024 17:43:25 -0500 Subject: [PATCH] Add additional rules --- README.md | 5 ++-- gulpfile.mjs | 6 ++-- src/aircraft/FenixA320.ts | 13 ++++---- src/rules/beacon_lights.ts | 4 +-- src/rules/excess_bank.ts | 46 +++++++++++++++++++++++++++++ src/rules/excess_gforce.ts | 35 ++++++++++++++++++++++ src/rules/excess_pitch.ts | 38 ++++++++++++++++++++++++ src/rules/excess_taxi_speed.ts | 43 +++++++++++++++++++++++++++ src/rules/fuel_refilled.ts | 41 +++++++++++++++++++++++++ src/rules/hard_landing.ts | 32 ++++++++++++++++++++ src/rules/lights_off_during_taxi.ts | 40 +++++++++++++++++++++++++ src/rules/lights_over_10k.ts | 44 +++++++++++++++++++++++++++ src/rules/lights_under_10k.ts | 44 +++++++++++++++++++++++++++ src/rules/simrate.ts | 26 ++++++++++++++++ src/rules/slew_activated.ts | 25 ++++++++++++++++ src/rules/speed_under_10k.ts | 39 ++++++++++++++++++++++++ src/rules/stabilized_approach.ts | 40 +++++++++++++++++++++++++ src/rules/stall_warning.ts | 31 +++++++++++++++++++ src/rules/strobes_on_during_taxi.ts | 46 +++++++++++++++++++++++++++++ src/types/global.d.ts | 2 -- src/types/rule.d.ts | 7 ++++- 21 files changed, 588 insertions(+), 19 deletions(-) create mode 100644 src/rules/excess_bank.ts create mode 100644 src/rules/excess_gforce.ts create mode 100644 src/rules/excess_pitch.ts create mode 100644 src/rules/excess_taxi_speed.ts create mode 100644 src/rules/fuel_refilled.ts create mode 100644 src/rules/hard_landing.ts create mode 100644 src/rules/lights_off_during_taxi.ts create mode 100644 src/rules/lights_over_10k.ts create mode 100644 src/rules/lights_under_10k.ts create mode 100644 src/rules/simrate.ts create mode 100644 src/rules/slew_activated.ts create mode 100644 src/rules/speed_under_10k.ts create mode 100644 src/rules/stabilized_approach.ts create mode 100644 src/rules/stall_warning.ts create mode 100644 src/rules/strobes_on_during_taxi.ts diff --git a/README.md b/README.md index 0d5ed6a..08c037b 100644 --- a/README.md +++ b/README.md @@ -163,14 +163,15 @@ The configuration is a class which has a few different components. - MSFS - the lookups you enter are LVars - X-Plane - the looks ups are via datarefs - FSUIPC - the lookups are offsets -3. `match()` +3. `flapNames` - see below +4. `match()` - This needs to return a boolean - A method (`match()`) which passes some information about the starting aircraft - For MSFS, it's the aircraft ICAO - For FSX/P3d, the value looked at is the aircraft title field, offset `0x3D00` - For X-Plane, the value looked at is `sim/aircraft/view/acf_descrip` - This method can be used to determine if this rule should match -4. Methods for the different features (see below) +5. Methods for the different features (see below) - The maps - a group of datarefs or offsets which constitute that feature being "on" or "enabled" In the above example, for the Fenix A320, the landing lights are controlled by two datarefs, both of which the diff --git a/gulpfile.mjs b/gulpfile.mjs index 8ea9de4..298b265 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -109,10 +109,8 @@ export { watchFiles as watch } */ export async function clean() { try { - if (await fs.promises.exists(paths.dist)) { - await deleteAsync([paths.dist]) - await Promise.resolve() - } + await deleteAsync([paths.dist]) + await Promise.resolve() } catch (e) { console.log(e) } diff --git a/src/aircraft/FenixA320.ts b/src/aircraft/FenixA320.ts index be69bf3..380209d 100644 --- a/src/aircraft/FenixA320.ts +++ b/src/aircraft/FenixA320.ts @@ -52,14 +52,11 @@ export default class FenixA320 extends AircraftConfig { } match(title: string, icao: string, config_path: string): boolean { - return ( - ['Fenix', 'A320'].every((w) => title.includes(w)) || - ['FenixA320'].every((w) => title.includes(w)) || - ['Fenix', 'A321'].every((w) => title.includes(w.toLowerCase())) || - ['FenixA319'].every((w) => title.includes(w.toLowerCase())) || - ['FenixA320'].every((w) => title.includes(w.toLowerCase())) || - ['FenixA321'].every((w) => title.includes(w.toLowerCase())) - ) + if (!title.includes('fenix')) { + return false + } + + return ['a319', 'a320', 'a321'].some((w) => title.includes(w)) } beaconLights(value: number): FeatureState { diff --git a/src/rules/beacon_lights.ts b/src/rules/beacon_lights.ts index 6d8eefd..04eac04 100644 --- a/src/rules/beacon_lights.ts +++ b/src/rules/beacon_lights.ts @@ -25,9 +25,9 @@ export default class BeaconLights implements Rule { } return Acars.ViolatedAfterDelay( - this.meta.name, + this.meta.id, this.meta.delay_time, - () => { + (): RuleValue => { if (!pirep.isInActiveState) { return [false] } diff --git a/src/rules/excess_bank.ts b/src/rules/excess_bank.ts new file mode 100644 index 0000000..62e26af --- /dev/null +++ b/src/rules/excess_bank.ts @@ -0,0 +1,46 @@ +/** + * Excess bank angle + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class ExcessBank implements Rule { + meta: Meta = { + id: 'EXCESS_BANK', + name: 'Bank angle exceeded limit', + enabled: true, // Default, will change depending on airline config + message: 'Bank angle exceeded limit', + states: [ + PirepState.Takeoff, + PirepState.Enroute, + PirepState.Approach, + PirepState.Final, + ], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 30, // default, gets overwritten from remote + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (data.onGround) { + return [false] + } + + return Acars.ViolatedAfterDelay(this.meta.id, this.meta.delay_time, () => { + // +Bank is right, -Bank is left + const value = + data.bank < -1 * this.meta.parameter! || + data.bank > this.meta.parameter! + + if (value) { + return [true] + } else { + return [false] + } + }) + } +} diff --git a/src/rules/excess_gforce.ts b/src/rules/excess_gforce.ts new file mode 100644 index 0000000..c04e477 --- /dev/null +++ b/src/rules/excess_gforce.ts @@ -0,0 +1,35 @@ +/** + * Detect if there's excess gforces + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class ExcessGForce implements Rule { + meta: Meta = { + id: 'EXCESS_GFORCE', + name: 'GForce exceeded limit', + enabled: true, // Default, will change depending on airline config + message: 'GForce exceeded limit', + states: [ + PirepState.Takeoff, + PirepState.Enroute, + PirepState.Approach, + PirepState.Final, + ], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 4, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (data.onGround) { + return [false] + } + + return [data.gForce >= this.meta.parameter!] + } +} diff --git a/src/rules/excess_pitch.ts b/src/rules/excess_pitch.ts new file mode 100644 index 0000000..2dd0e52 --- /dev/null +++ b/src/rules/excess_pitch.ts @@ -0,0 +1,38 @@ +/** + * Detect is the aircraft is pitched up or down excessively + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class ExcessPitch implements Rule { + meta: Meta = { + id: 'EXCESS_PITCH', + name: 'Pitch exceeded limit', + enabled: true, + message: 'Pitch exceeded limit', + states: [ + PirepState.Takeoff, + PirepState.Enroute, + PirepState.Approach, + PirepState.Final, + ], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 4, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (data.onGround) { + return [false] + } + + return [ + data.pitch < -1 * this.meta.parameter! || + data.pitch > this.meta.parameter!, + ] + } +} diff --git a/src/rules/excess_taxi_speed.ts b/src/rules/excess_taxi_speed.ts new file mode 100644 index 0000000..b261b22 --- /dev/null +++ b/src/rules/excess_taxi_speed.ts @@ -0,0 +1,43 @@ +/** + * Evaluate if this rule has been violated. If they go overspeed, then start the + * stopwatch. Then mark it as violated if they've gone overspeed for ~ 6 seconds + * 6 seconds to make sure it doesn't interfere with a takeoff roll + */ +import { PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class ExcessTaxiSpeed implements Rule { + meta: Meta = { + id: 'EXCESS_TAXI_SPEED', + name: 'Taxi speed exceeded limit', + enabled: true, + message: 'Taxi speed exceeded limit', + states: [PirepState.TaxiIn, PirepState.TaxiOut], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 4, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + return Acars.ViolatedAfterDelay( + this.meta.id, + this.meta.delay_time, + (): RuleValue => { + if (!data.onGround) { + return [false] + } + + // If they're on a runway, ignore any taxi speed warnings, might be taking off + if (data.approachingRunway != null || data.runway != null) { + return [false] + } + + return [data.groundSpeed.Knots > this.meta.parameter!] + }, + ) + } +} diff --git a/src/rules/fuel_refilled.ts b/src/rules/fuel_refilled.ts new file mode 100644 index 0000000..43fb537 --- /dev/null +++ b/src/rules/fuel_refilled.ts @@ -0,0 +1,41 @@ +/** + * If the fuel level is higher at any point + */ +import { PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class FuelRefilled implements Rule { + meta: Meta = { + id: 'EXCESS_TAXI_SPEED', + name: 'Taxi speed exceeded limit', + enabled: true, + message: 'Taxi speed exceeded limit', + states: [], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 4, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (data.onGround || previousData == null) { + return [false] + } + + if (data.fuelQuantity.Pounds > previousData.fuelQuantity.Pounds) { + return [false] + } + + return [ + Acars.NumberOverPercent( + data.fuelQuantity.Pounds, + previousData.fuelQuantity.Pounds, + 10, + ), + `Fuel Refilled: Current: ${data.fuelQuantity.Pounds}, previous=${previousData.fuelQuantity.Pounds}`, + ] + } +} diff --git a/src/rules/hard_landing.ts b/src/rules/hard_landing.ts new file mode 100644 index 0000000..601a8e9 --- /dev/null +++ b/src/rules/hard_landing.ts @@ -0,0 +1,32 @@ +import { PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class HandLandings implements Rule { + meta: Meta = { + id: 'HARD_LANDING', + name: 'Hard Landing', + enabled: true, // Default, will change depending on airline config + message: 'Hard Landing', + states: [PirepState.Final, PirepState.Landed], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + parameter: 300, // default, gets overwritten from remote + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + const absRate = Math.abs(pirep.landingRate.FeetPerMinute) + const absParam = Math.abs(this.meta.parameter!) + if (absRate < absParam) { + return [false] + } + + console.log( + `Hard landing violation, rate=${absRate}, threshold=${absParam}`, + ) + + return [true] + } +} diff --git a/src/rules/lights_off_during_taxi.ts b/src/rules/lights_off_during_taxi.ts new file mode 100644 index 0000000..4013983 --- /dev/null +++ b/src/rules/lights_off_during_taxi.ts @@ -0,0 +1,40 @@ +/** + * Determine if the beacon lights are on while the aircraft is in motio + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class LightsOffDuringTaxi implements Rule { + meta: Meta = { + id: 'LAND_LIGHTS_OFF_TAXI', + name: 'Landing lights must be off during pushback or taxi', + enabled: true, + message: 'Landing lights must be off during pushback or taxi', + states: [PirepState.TaxiOut, PirepState.Pushback, PirepState.TaxiIn], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (!Acars.IsFeatureEnabled(AircraftFeature.LandingLights)) { + return [false] + } + + return Acars.ViolatedAfterDelay( + this.meta.id, + this.meta.delay_time, + (): RuleValue => { + // Ignore landing lights being turned on + if (data.approachingRunway != null || data.runway != null) { + return [false] + } + + return [data.landingLights] + }, + ) + } +} diff --git a/src/rules/lights_over_10k.ts b/src/rules/lights_over_10k.ts new file mode 100644 index 0000000..d272541 --- /dev/null +++ b/src/rules/lights_over_10k.ts @@ -0,0 +1,44 @@ +/** + * Determine if the beacon lights are on while the aircraft is in motio + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class LightsOver10K implements Rule { + meta: Meta = { + id: 'LAND_LIGHTS_OVER_10K', + name: 'Landing lights must be off above ', + enabled: true, + message: 'Landing lights must be off above ', + states: [], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (!Acars.IsFeatureEnabled(AircraftFeature.LandingLights)) { + return [false] + } + + if (data.onGround) { + return [false] + } + + return Acars.ViolatedAfterDelay( + this.meta.id, + this.meta.delay_time, + (): RuleValue => { + // Ignore landing lights being turned on + const violated = + data.landingLights && + data.planeAltitude.Feet > this.meta.parameter! + 500 + + return [violated, this.meta.message + this.meta.parameter!] + }, + ) + } +} diff --git a/src/rules/lights_under_10k.ts b/src/rules/lights_under_10k.ts new file mode 100644 index 0000000..ba7dd31 --- /dev/null +++ b/src/rules/lights_under_10k.ts @@ -0,0 +1,44 @@ +/** + * Determine if the beacon lights are on while the aircraft is in motio + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class LightsUnder10K implements Rule { + meta: Meta = { + id: 'LAND_LIGHTS_UNDER_10K', + name: 'Landing lights must be on below ', + enabled: true, + message: 'Landing lights must be on below ', + states: [], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (!Acars.IsFeatureEnabled(AircraftFeature.LandingLights)) { + return [false] + } + + if (data.onGround) { + return [false] + } + + return Acars.ViolatedAfterDelay( + this.meta.name, + this.meta.delay_time, + (): RuleValue => { + // Ignore landing lights being turned on + const violated = + !data.landingLights && + data.planeAltitude.Feet < this.meta.parameter! - 500 + + return [violated, this.meta.message + this.meta.parameter!] + }, + ) + } +} diff --git a/src/rules/simrate.ts b/src/rules/simrate.ts new file mode 100644 index 0000000..dbb247b --- /dev/null +++ b/src/rules/simrate.ts @@ -0,0 +1,26 @@ +/** + * If the fuel level is higher at any point + */ +import { SimType } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class SimRate implements Rule { + meta: Meta = { + id: 'SIMRATE_INCREASED', + name: 'Sim rate changed', + enabled: true, + message: 'Sim rate changed', + states: [], + repeatable: true, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 1, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + return [data.simRate > this.meta.parameter!] + } +} diff --git a/src/rules/slew_activated.ts b/src/rules/slew_activated.ts new file mode 100644 index 0000000..cd50177 --- /dev/null +++ b/src/rules/slew_activated.ts @@ -0,0 +1,25 @@ +/** + * If the fuel level is higher at any point + */ +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class SlewActivated implements Rule { + meta: Meta = { + id: 'SLEW_ACTIVATED', + name: 'Slew activated', + enabled: true, + message: 'Slew activated', + states: [], + repeatable: true, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 1, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + return [data.slewActive] + } +} diff --git a/src/rules/speed_under_10k.ts b/src/rules/speed_under_10k.ts new file mode 100644 index 0000000..fcf0061 --- /dev/null +++ b/src/rules/speed_under_10k.ts @@ -0,0 +1,39 @@ +/** + * Determine if the beacon lights are on while the aircraft is in motio + */ +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class SpeedUnder10K implements Rule { + meta: Meta = { + id: 'SPEED_UNDER_10K', + name: 'Speed under 10k exceeds ', + enabled: true, + message: 'Speed under 10k exceeds ', + states: [], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (data.onGround) { + return [false] + } + + return Acars.ViolatedAfterDelay( + this.meta.name, + this.meta.delay_time, + (): RuleValue => { + // Ignore landing lights being turned on + const violated = + data.indicatedAirspeed.Knots > this.meta.parameter! && + data.planeAltitude.Feet < 10000 + + return [violated, this.meta.message + this.meta.parameter!] + }, + ) + } +} diff --git a/src/rules/stabilized_approach.ts b/src/rules/stabilized_approach.ts new file mode 100644 index 0000000..255c238 --- /dev/null +++ b/src/rules/stabilized_approach.ts @@ -0,0 +1,40 @@ +/** + * If the fuel level is higher at any point + */ +import { PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class StabilizedApproach implements Rule { + meta: Meta = { + id: 'STABILIZED_APPROACH', + name: 'Approach is not stabilized', + enabled: true, + message: 'Approach is not stabilized', + states: [PirepState.Approach, PirepState.Final], + repeatable: true, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 1, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (data.onGround) { + return [false] + } + + // Above the min altitude so ignore it + if (data.groundAltitude.Feet > this.meta.parameter!) return [false] + + if (data.gearUp) return [true] + + // No flaps landing doesn't seem right + if (pirep.features == null) { + return [false] + } + + return [pirep.features.flapsCount > 0 && data.flaps === 0] + } +} diff --git a/src/rules/stall_warning.ts b/src/rules/stall_warning.ts new file mode 100644 index 0000000..140ca06 --- /dev/null +++ b/src/rules/stall_warning.ts @@ -0,0 +1,31 @@ +/** + * If the fuel level is higher at any point + */ +import { PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class SimRate implements Rule { + meta: Meta = { + id: 'STALL_WARNING', + name: 'Stall warning', + enabled: true, + message: 'Stall warning', + states: [ + PirepState.Takeoff, + PirepState.Enroute, + PirepState.Approach, + PirepState.Final, + ], + repeatable: true, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + parameter: 1, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + return [!data.onGround && data.stallWarning] + } +} diff --git a/src/rules/strobes_on_during_taxi.ts b/src/rules/strobes_on_during_taxi.ts new file mode 100644 index 0000000..9dec00b --- /dev/null +++ b/src/rules/strobes_on_during_taxi.ts @@ -0,0 +1,46 @@ +/** + * Determine if the beacon lights are on while the aircraft is in motio + */ +import { AircraftFeature, PirepState } from '../defs' +import { Meta, Rule, RuleValue } from '../types/rule' +import { Pirep, Telemetry } from '../types/types' + +export default class LightsOffDuringTaxi implements Rule { + meta: Meta = { + id: 'STROBES_ON_TAXI', + name: 'Strobes must be off during pushback or taxi', + enabled: true, + message: 'Strobes must be off during pushback or taxi', + states: [ + PirepState.TaxiOut, + PirepState.Pushback, + PirepState.TaxiIn, + PirepState.OnBlock, + PirepState.Arrived, + ], + repeatable: false, + cooldown: 60, + max_count: 3, + points: -1, + delay_time: 5000, + } + + violated(pirep: Pirep, data: Telemetry, previousData?: Telemetry): RuleValue { + if (!Acars.IsFeatureEnabled(AircraftFeature.StrobeLights)) { + return [false] + } + + return Acars.ViolatedAfterDelay( + this.meta.name, + this.meta.delay_time, + (): RuleValue => { + // Ignore landing lights being turned on + if (data.approachingRunway != null || data.runway != null) { + return [false] + } + + return [data.strobeLights] + }, + ) + } +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts index b45d0f4..9907636 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -10,8 +10,6 @@ declare global { function IsFeatureEnabled(feature: AircraftFeature): boolean {} /** Get something from storage */ function Get(key: string): string {} - /** Save something to storage */ - function Set(key: string, value?: string): void {} /** * @param key * @param value diff --git a/src/types/rule.d.ts b/src/types/rule.d.ts index efa0389..d3f2a50 100644 --- a/src/types/rule.d.ts +++ b/src/types/rule.d.ts @@ -94,7 +94,12 @@ export interface Meta { /** * The number of points that's substracted by default */ - points: number + points?: number + + /** + * From upstream + */ + parameter?: number /** * This just allows any other properties