From e958d000b058167962d98c1847e97f7b42df3e4b Mon Sep 17 00:00:00 2001 From: Benedict Etzel Date: Thu, 5 Aug 2021 22:35:22 +0200 Subject: [PATCH] to squash: reorganize for tests --- .../a320_systems/src/flight_warning/mod.rs | 314 ++++++++- .../{signals.rs => parameters.rs} | 83 ++- .../a320_systems/src/flight_warning/test.rs | 227 ++++++ .../src/flight_warning/warnings.rs | 648 ++++++++++-------- src/systems/systems/src/flight_warning/mod.rs | 25 + src/systems/systems/src/simulation/mod.rs | 20 + 6 files changed, 987 insertions(+), 330 deletions(-) rename src/systems/a320_systems/src/flight_warning/{signals.rs => parameters.rs} (84%) create mode 100644 src/systems/a320_systems/src/flight_warning/test.rs diff --git a/src/systems/a320_systems/src/flight_warning/mod.rs b/src/systems/a320_systems/src/flight_warning/mod.rs index da2a63125a23..7da27aea72d7 100644 --- a/src/systems/a320_systems/src/flight_warning/mod.rs +++ b/src/systems/a320_systems/src/flight_warning/mod.rs @@ -1,53 +1,89 @@ -use signals::A320SignalTable; +use parameters::A320FWCParameterTable; +use systems::flight_warning::parameters::{Arinc429Parameter, DiscreteParameter}; use systems::simulation::UpdateContext; use warnings::*; -mod signals; +mod parameters; +mod test; mod warnings; pub(super) struct A320FlightWarningComputer { + index: usize, new_ground_def: NewGroundActivation, ground_detection: GroundDetectionActivation, speed_detection: SpeedDetectionActivation, engines_not_running: EnginesNotRunning, both_engine_running: EngRunningActivation, altitude_def: AltitudeDefActivation, + eng_take_off_cfm: EngTakeOffCfmActivation, + tla_pwr_reverse: TlaPwrReverseActivation, + tla_at_mct_or_flex_to_cfm: TlaAtMctOrFlexToCfmActivation, + tla_at_cl_cfm: TlaAtClCfmActivation, + neo_ecu: NeoEcuActivation, cfm_flight_phases: CfmFlightPhasesDefActivation, - flight_phases_ground: FlightPhasesGround, - flight_phases_air: FlightPhasesAir, + flight_phases_ground: FlightPhasesGroundActivation, + flight_phases_air: FlightPhasesAirActivation, } impl A320FlightWarningComputer { - pub fn new() -> Self { + pub fn new(index: usize) -> Self { Self { + index: index, new_ground_def: NewGroundActivation::new(), ground_detection: GroundDetectionActivation::new(), speed_detection: SpeedDetectionActivation::new(), engines_not_running: EnginesNotRunning::new(), both_engine_running: EngRunningActivation::new(), altitude_def: AltitudeDefActivation::new(), + eng_take_off_cfm: EngTakeOffCfmActivation::new(), + tla_pwr_reverse: TlaPwrReverseActivation::new(), + tla_at_mct_or_flex_to_cfm: TlaAtMctOrFlexToCfmActivation::new(), + tla_at_cl_cfm: TlaAtClCfmActivation::new(), + neo_ecu: NeoEcuActivation::new(), cfm_flight_phases: CfmFlightPhasesDefActivation::new(), - flight_phases_ground: FlightPhasesGround::new(), - flight_phases_air: FlightPhasesAir::new(), + flight_phases_ground: FlightPhasesGroundActivation::new(), + flight_phases_air: FlightPhasesAirActivation::new(), } } - fn update(&mut self, context: &UpdateContext, signals: &A320SignalTable) { - self.new_ground_def.update(context, signals); + pub fn index(&self) -> usize { + self.index + } + + /// Acquires the parameter table for further processing by the FWC. In future this method may + /// acquire the data through the opposite FWC and the SDACs. + fn acquire(&self, _context: &UpdateContext) -> A320FWCParameterTable { + A320FWCParameterTable::new() + } + + fn update_flight_phase(&mut self, context: &UpdateContext, parameters: &A320FWCParameterTable) { + self.new_ground_def.update(context, parameters); self.ground_detection - .update(context, signals, &self.new_ground_def); + .update(context, parameters, &self.new_ground_def); - self.speed_detection.update(context, signals); + self.speed_detection.update(context, parameters); self.engines_not_running - .update(context, signals, &self.ground_detection); + .update(context, parameters, &self.ground_detection); self.both_engine_running - .update(context, signals, &self.engines_not_running); - self.altitude_def.update(context, signals); - // todo self.cfm_flight_phases.update(context, (), self.altitude_def) + .update(context, parameters, &self.engines_not_running); + self.altitude_def.update(context, parameters); + + self.eng_take_off_cfm.update(context, parameters); + self.tla_pwr_reverse + .update(context, parameters, &self.eng_take_off_cfm); + self.cfm_flight_phases.update( + context, + parameters, + &self.neo_ecu, + &self.tla_at_mct_or_flex_to_cfm, + &self.tla_pwr_reverse, + &self.altitude_def, + &self.tla_at_cl_cfm, + ); self.flight_phases_ground.update( context, - signals, + parameters, &self.ground_detection, &self.speed_detection, &self.both_engine_running, @@ -62,6 +98,96 @@ impl A320FlightWarningComputer { &self.flight_phases_ground, ); } + + pub fn update(&mut self, context: &UpdateContext, parameters: &A320FWCParameterTable) { + //let parameters = self.acquire(context); + self.update_flight_phase(context, parameters); + } + + /// This method can be called to assert that exactly and only the supplied flight phase is + /// currently active. + pub fn assert_exact_flight_phase(self, flight_phase: usize) { + assert!( + !((flight_phase == 1) ^ self.flight_phases_ground.phase_1()), + if flight_phase == 1 { + "Flight phase 1 wasn't active" + } else { + "Flight phase 1 was active" + } + ); + assert!( + !((flight_phase == 2) ^ self.flight_phases_ground.phase_2()), + if flight_phase == 2 { + "Flight phase 2 wasn't active" + } else { + "Flight phase 2 was active" + } + ); + assert!( + !((flight_phase == 3) ^ self.flight_phases_ground.phase_3()), + if flight_phase == 3 { + "Flight phase 3 wasn't active" + } else { + "Flight phase 3 was active" + } + ); + assert!( + !((flight_phase == 4) ^ self.flight_phases_ground.phase_4()), + if flight_phase == 4 { + "Flight phase 4 wasn't active" + } else { + "Flight phase 4 was active" + } + ); + assert!( + !((flight_phase == 5) ^ self.flight_phases_air.phase_5()), + if flight_phase == 5 { + "Flight phase 5 wasn't active" + } else { + "Flight phase 5 was active" + } + ); + assert!( + !((flight_phase == 6) ^ self.flight_phases_air.phase_6()), + if flight_phase == 6 { + "Flight phase 6 wasn't active" + } else { + "Flight phase 6 was active" + } + ); + assert!( + !((flight_phase == 7) ^ self.flight_phases_air.phase_7()), + if flight_phase == 7 { + "Flight phase 7 wasn't active" + } else { + "Flight phase 7 was active" + } + ); + assert!( + !((flight_phase == 8) ^ self.flight_phases_ground.phase_8()), + if flight_phase == 8 { + "Flight phase 8 wasn't active" + } else { + "Flight phase 8 was active" + } + ); + assert!( + !((flight_phase == 9) ^ self.flight_phases_ground.phase_9()), + if flight_phase == 9 { + "Flight phase 9 wasn't active" + } else { + "Flight phase 9 was active" + } + ); + assert!( + !((flight_phase == 10) ^ self.flight_phases_ground.phase_10()), + if flight_phase == 10 { + "Flight phase 10 wasn't active" + } else { + "Flight phase 10 was active" + } + ); + } } #[cfg(test)] @@ -76,26 +202,142 @@ mod tests { use super::*; use systems::flight_warning::parameters::{Arinc429Parameter, DiscreteParameter}; - #[test] - fn when_spawning_cold_and_dark() { - let mut fwc = A320FlightWarningComputer::new(); - let mut signals = A320SignalTable::new(); - signals.set_ess_lh_lg_compressed(DiscreteParameter::new(true)); - signals.set_norm_lh_lg_compressed(DiscreteParameter::new(true)); - signals.set_lh_lg_compressed_1(Arinc429Parameter::new(true)); - signals.set_lh_lg_compressed_2(Arinc429Parameter::new(true)); - fwc.update(&gnd_context(Duration::from_secs(1)), &signals); - //assert_eq!(fwc.flight_phases_ground.phase_1(), true); - } + #[cfg(test)] + mod flight_warning_computer_tests { + use super::*; + use crate::flight_warning::test::test_bed; + use uom::si::angle::radian; + + #[test] + fn when_spawning_cold_and_dark_is_phase_1() { + let mut fwc = A320FlightWarningComputer::new(1); + fwc.update( + &gnd_context(Duration::from_secs(1)), + &test_bed().on_ground().parameters(), + ); + fwc.assert_exact_flight_phase(1); + } + + #[test] + fn when_first_engine_running_for_30_sec_is_phase_2() { + let mut fwc = A320FlightWarningComputer::new(1); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed().on_ground().one_engine_running().parameters(), + ); + fwc.assert_exact_flight_phase(2); + } + + #[test] + fn when_engines_at_takeoff_power_is_phase_3() { + let mut fwc = A320FlightWarningComputer::new(1); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed() + .on_ground() + .engines_running() + .engines_at_takeoff_power() + .parameters(), + ); + fwc.assert_exact_flight_phase(3); + } - fn gnd_context(delta_time: Duration) -> UpdateContext { - UpdateContext::new( - delta_time, - Velocity::new::(0.), - Length::new::(0.), - ThermodynamicTemperature::new::(25.0), - true, - Acceleration::new::(0.), - ) + #[test] + fn when_above_80_knots_is_phase_4() { + let mut fwc = A320FlightWarningComputer::new(1); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed() + .on_ground() + .engines_running() + .engines_at_takeoff_power() + .computed_speeds( + Velocity::new::(85.0), + Velocity::new::(85.0), + Velocity::new::(85.0), + ) + .parameters(), + ); + fwc.assert_exact_flight_phase(4); + } + + #[test] + fn when_airborne_is_phase_5() { + let mut fwc = A320FlightWarningComputer::new(1); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed() + .engines_running() + .engines_at_takeoff_power() + .radio_heights(Length::new::(10.0), Length::new::(10.0)) + .computed_speeds( + Velocity::new::(157.0), + Velocity::new::(157.0), + Velocity::new::(157.0), + ) + .parameters(), + ); + fwc.assert_exact_flight_phase(5); + } + + #[test] + fn when_above_1500ft_is_phase_6() { + let mut fwc = A320FlightWarningComputer::new(1); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed() + .engines_running() + .engines_at_takeoff_power() + .radio_heights(Length::new::(1550.0), Length::new::(1550.0)) + .computed_speeds( + Velocity::new::(180.0), + Velocity::new::(180.0), + Velocity::new::(180.0), + ) + .parameters(), + ); + fwc.assert_exact_flight_phase(6); + } + + #[test] + fn when_below_800ft_is_phase_7() { + let mut fwc = A320FlightWarningComputer::new(1); + let mut test_bed = test_bed() + .engines_running() + .engines_at_takeoff_power() + .radio_heights(Length::new::(1550.0), Length::new::(1550.0)) + .computed_speeds( + Velocity::new::(180.0), + Velocity::new::(180.0), + Velocity::new::(180.0), + ); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed.parameters(), + ); + test_bed = test_bed + .engines_at_idle() + .radio_heights(Length::new::(750.0), Length::new::(750.0)); + fwc.update( + &gnd_context(Duration::from_secs(30)), + &test_bed.parameters(), + ); + fwc.assert_exact_flight_phase(7); + } + + fn gnd_context(delta_time: Duration) -> UpdateContext { + UpdateContext::new( + delta_time, + Velocity::new::(0.), + Length::new::(0.), + ThermodynamicTemperature::new::(25.0), + true, + Acceleration::new::(0.), + Acceleration::new::(0.), + Acceleration::new::(0.), + Angle::new::(0.), + Angle::new::(0.), + ) + } } } diff --git a/src/systems/a320_systems/src/flight_warning/signals.rs b/src/systems/a320_systems/src/flight_warning/parameters.rs similarity index 84% rename from src/systems/a320_systems/src/flight_warning/signals.rs rename to src/systems/a320_systems/src/flight_warning/parameters.rs index 5834ccda5dd9..a1c51e5800c9 100644 --- a/src/systems/a320_systems/src/flight_warning/signals.rs +++ b/src/systems/a320_systems/src/flight_warning/parameters.rs @@ -107,7 +107,29 @@ pub(super) trait Eng2ChannelInControl { fn eng2_channel_b_in_control(&self) -> &Arinc429Parameter; } -pub(super) struct A320SignalTable { +/// This struct contains the parameters acquired directly by the FWC (in other words: not through an +/// SDAC). They can be received via ARINC or as discretes. +pub struct A320FwcDirectParameterTable { + lh_lg_compressed_1: Arinc429Parameter, + lh_lg_compressed_2: Arinc429Parameter, + ess_lh_lg_compressed: DiscreteParameter, + norm_lh_lg_compressed: DiscreteParameter, + radio_height_1: Arinc429Parameter, + radio_height_2: Arinc429Parameter, + computed_speed_1: Arinc429Parameter, + computed_speed_2: Arinc429Parameter, + computed_speed_3: Arinc429Parameter, + eng1_master_lever_select_on: Arinc429Parameter, + eng2_master_lever_select_on: Arinc429Parameter, +} + +impl A320FwcDirectParameterTable {} + +pub struct A320FwcSdacParameterTable {} + +/// This struct represents the in-memory representation of the signals used by a Flight Warning +/// Computer to determine it's activations. +pub struct A320FWCParameterTable { lh_lg_compressed_1: Arinc429Parameter, lh_lg_compressed_2: Arinc429Parameter, ess_lh_lg_compressed: DiscreteParameter, @@ -146,7 +168,7 @@ pub(super) struct A320SignalTable { eng2_channel_a_in_control: Arinc429Parameter, eng2_channel_b_in_control: Arinc429Parameter, } -impl A320SignalTable { +impl A320FWCParameterTable { pub fn new() -> Self { Self { lh_lg_compressed_1: Arinc429Parameter::new_inv(false), @@ -284,8 +306,23 @@ impl A320SignalTable { pub(super) fn set_eng2_tla_b(&mut self, tla: Arinc429Parameter) { self.eng2_tla_b = tla } + + pub(super) fn set_eng1_channel_a_in_control(&mut self, in_control: Arinc429Parameter) { + self.eng1_channel_a_in_control = in_control; + } + + pub(super) fn set_eng1_channel_b_in_control(&mut self, in_control: Arinc429Parameter) { + self.eng1_channel_b_in_control = in_control; + } + + pub(super) fn set_eng2_channel_a_in_control(&mut self, in_control: Arinc429Parameter) { + self.eng2_channel_a_in_control = in_control; + } + pub(super) fn set_eng2_channel_b_in_control(&mut self, in_control: Arinc429Parameter) { + self.eng2_channel_b_in_control = in_control; + } } -impl LhLgCompressed for A320SignalTable { +impl LhLgCompressed for A320FWCParameterTable { fn lh_lg_compressed(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.lh_lg_compressed_1, @@ -294,17 +331,17 @@ impl LhLgCompressed for A320SignalTable { } } } -impl EssLhLgCompressed for A320SignalTable { +impl EssLhLgCompressed for A320FWCParameterTable { fn ess_lh_lg_compressed(&self) -> &DiscreteParameter { &self.ess_lh_lg_compressed } } -impl NormLhLgCompressed for A320SignalTable { +impl NormLhLgCompressed for A320FWCParameterTable { fn norm_lh_lg_compressed(&self) -> &DiscreteParameter { &self.norm_lh_lg_compressed } } -impl RadioHeight for A320SignalTable { +impl RadioHeight for A320FWCParameterTable { fn radio_height(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.radio_height_1, @@ -313,7 +350,7 @@ impl RadioHeight for A320SignalTable { } } } -impl ComputedSpeed for A320SignalTable { +impl ComputedSpeed for A320FWCParameterTable { fn computed_speed(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.computed_speed_1, @@ -323,19 +360,19 @@ impl ComputedSpeed for A320SignalTable { } } } -impl Eng1MasterLeverSelectOn for A320SignalTable { +impl Eng1MasterLeverSelectOn for A320FWCParameterTable { fn eng1_master_lever_select_on(&self) -> &Arinc429Parameter { &self.eng1_master_lever_select_on } } -impl Eng2MasterLeverSelectOn for A320SignalTable { +impl Eng2MasterLeverSelectOn for A320FWCParameterTable { fn eng2_master_lever_select_on(&self) -> &Arinc429Parameter { &self.eng2_master_lever_select_on } } -impl Eng1CoreSpeedAtOrAboveIdle for A320SignalTable { +impl Eng1CoreSpeedAtOrAboveIdle for A320FWCParameterTable { fn eng1_core_speed_at_or_above_idle(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng1_core_speed_at_or_above_idle_a, @@ -345,7 +382,7 @@ impl Eng1CoreSpeedAtOrAboveIdle for A320SignalTable { } } -impl Eng2CoreSpeedAtOrAboveIdle for A320SignalTable { +impl Eng2CoreSpeedAtOrAboveIdle for A320FWCParameterTable { fn eng2_core_speed_at_or_above_idle(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng2_core_speed_at_or_above_idle_a, @@ -355,19 +392,19 @@ impl Eng2CoreSpeedAtOrAboveIdle for A320SignalTable { } } -impl Eng1FirePbOut for A320SignalTable { +impl Eng1FirePbOut for A320FWCParameterTable { fn eng_1_fire_pb_out(&self) -> &DiscreteParameter { &self.eng_1_fire_pb_out } } -impl ToConfigTest for A320SignalTable { +impl ToConfigTest for A320FWCParameterTable { fn to_config_test(&self) -> &Arinc429Parameter { &self.to_config_test } } -impl Eng1Tla for A320SignalTable { +impl Eng1Tla for A320FWCParameterTable { fn eng1_tla(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng1_tla_a, @@ -377,7 +414,7 @@ impl Eng1Tla for A320SignalTable { } } -impl Eng2Tla for A320SignalTable { +impl Eng2Tla for A320FWCParameterTable { fn eng2_tla(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng2_tla_a, @@ -387,7 +424,7 @@ impl Eng2Tla for A320SignalTable { } } -impl Eng1TlaFto for A320SignalTable { +impl Eng1TlaFto for A320FWCParameterTable { fn eng1_tla_fto(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng1_tla_fto_a, @@ -397,7 +434,7 @@ impl Eng1TlaFto for A320SignalTable { } } -impl Eng2TlaFto for A320SignalTable { +impl Eng2TlaFto for A320FWCParameterTable { fn eng2_tla_fto(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng2_tla_fto_a, @@ -407,7 +444,7 @@ impl Eng2TlaFto for A320SignalTable { } } -impl Eng1N1SelectedActual for A320SignalTable { +impl Eng1N1SelectedActual for A320FWCParameterTable { fn eng1_n1_selected_actual(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng1_n1_selected_actual_a, @@ -416,7 +453,7 @@ impl Eng1N1SelectedActual for A320SignalTable { } } } -impl Eng2N1SelectedActual for A320SignalTable { +impl Eng2N1SelectedActual for A320FWCParameterTable { fn eng2_n1_selected_actual(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.eng2_n1_selected_actual_a, @@ -425,7 +462,7 @@ impl Eng2N1SelectedActual for A320SignalTable { } } } -impl Tla1IdlePwr for A320SignalTable { +impl Tla1IdlePwr for A320FWCParameterTable { fn tla1_idle_pwr(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.tla1_idle_pwr_a, @@ -434,7 +471,7 @@ impl Tla1IdlePwr for A320SignalTable { } } } -impl Tla2IdlePwr for A320SignalTable { +impl Tla2IdlePwr for A320FWCParameterTable { fn tla2_idle_pwr(&self, index: usize) -> &Arinc429Parameter { match index { 1 => &self.tla2_idle_pwr_a, @@ -443,7 +480,7 @@ impl Tla2IdlePwr for A320SignalTable { } } } -impl Eng1ChannelInControl for A320SignalTable { +impl Eng1ChannelInControl for A320FWCParameterTable { fn eng1_channel_a_in_control(&self) -> &Arinc429Parameter { &self.eng1_channel_a_in_control } @@ -452,7 +489,7 @@ impl Eng1ChannelInControl for A320SignalTable { &self.eng1_channel_b_in_control } } -impl Eng2ChannelInControl for A320SignalTable { +impl Eng2ChannelInControl for A320FWCParameterTable { fn eng2_channel_a_in_control(&self) -> &Arinc429Parameter { &self.eng2_channel_a_in_control } diff --git a/src/systems/a320_systems/src/flight_warning/test.rs b/src/systems/a320_systems/src/flight_warning/test.rs new file mode 100644 index 000000000000..bfdc198622aa --- /dev/null +++ b/src/systems/a320_systems/src/flight_warning/test.rs @@ -0,0 +1,227 @@ +use crate::flight_warning::parameters::A320FWCParameterTable; +use systems::flight_warning::parameters::*; +use uom::si::angle::degree; +use uom::si::f64::*; +use uom::si::length::foot; +use uom::si::ratio::percent; +use uom::si::velocity::knot; + +pub struct A320FWCParameterTestBed { + parameters: A320FWCParameterTable, +} +impl A320FWCParameterTestBed { + pub fn new() -> Self { + let mut signals = A320FWCParameterTable::new(); + Self { + parameters: signals, + } + } + + pub fn and(self) -> Self { + self + } + + pub fn parameters(&self) -> &A320FWCParameterTable { + &self.parameters + } + + pub fn takeoff_config_test_pressed(mut self) -> Self { + self.parameters.set_takeoff_config_test(true); + self + } + + pub fn on_ground(mut self) -> Self { + self.ess_lh_lg_compressed() + .norm_lh_lg_compressed() + .lh_lg_compressed(1) + .lh_lg_compressed(2) + .radio_heights(Length::new::(0.0), Length::new::(0.0)) + } + + pub fn one_engine_running(mut self) -> Self { + self.eng1_master_lever_select_on().eng1_at_or_above_idle() + } + + pub fn engines_running(mut self) -> Self { + self.eng1_master_lever_select_on() + .eng1_at_or_above_idle() + .eng2_master_lever_select_on() + .eng2_at_or_above_idle() + } + + pub fn engines_at_takeoff_power(mut self) -> Self { + self.eng1_tla(Angle::new::(45.0)) + .eng2_tla(Angle::new::(45.0)) + } + + pub fn engines_at_idle(mut self) -> Self { + self.eng1_tla(Angle::new::(0.0)) + .eng2_tla(Angle::new::(0.0)) + } + + pub fn computed_speeds(mut self, speed1: Velocity, speed2: Velocity, speed3: Velocity) -> Self { + self.parameters + .set_computed_speed_1(Arinc429Parameter::new(speed1)); + self.parameters + .set_computed_speed_2(Arinc429Parameter::new(speed2)); + self.parameters + .set_computed_speed_3(Arinc429Parameter::new(speed3)); + self + } + + pub fn computed_speed_1(mut self, speed: Velocity) -> Self { + self.parameters + .set_computed_speed_1(Arinc429Parameter::new(speed)); + self + } + + pub fn computed_speed_2(mut self, speed: Velocity) -> Self { + self.parameters + .set_computed_speed_2(Arinc429Parameter::new(speed)); + self + } + + pub fn computed_speed_3(mut self, speed: Velocity) -> Self { + self.parameters + .set_computed_speed_3(Arinc429Parameter::new(speed)); + self + } + + pub fn lh_lg_compressed(mut self, lgciu: usize) -> Self { + match lgciu { + 1 => self + .parameters + .set_lh_lg_compressed_1(Arinc429Parameter::new(true)), + 2 => self + .parameters + .set_lh_lg_compressed_2(Arinc429Parameter::new(true)), + _ => panic!(), + } + self + } + + pub fn lh_lg_extended(mut self, lgciu: usize) -> Self { + match lgciu { + 1 => self + .parameters + .set_lh_lg_compressed_1(Arinc429Parameter::new(false)), + 2 => self + .parameters + .set_lh_lg_compressed_2(Arinc429Parameter::new(false)), + _ => panic!(), + } + self + } + + pub fn ess_lh_lg_compressed(mut self) -> Self { + self.parameters + .set_ess_lh_lg_compressed(DiscreteParameter::new(true)); + self + } + + pub fn norm_lh_lg_compressed(mut self) -> Self { + self.parameters + .set_norm_lh_lg_compressed(DiscreteParameter::new(true)); + self + } + + pub fn radio_heights(mut self, height1: Length, height2: Length) -> Self { + self.parameters + .set_radio_height_1(Arinc429Parameter::new(height1)); + self.parameters + .set_radio_height_2(Arinc429Parameter::new(height2)); + self + } + + /// Simulates a flight at cruise, where the radio altimeters will not be able to receive a + /// valid ground return and mark their data as NCD. + pub fn radio_heights_at_cruise(mut self) -> Self { + self.parameters + .set_radio_height_1(Arinc429Parameter::new_ncd(Length::new::(10000.0))); + self.parameters + .set_radio_height_2(Arinc429Parameter::new_ncd(Length::new::(10000.0))); + self + } + + pub fn eng1_fire_pb_out(mut self) -> Self { + self.parameters + .set_eng_1_fire_pb_out(DiscreteParameter::new(true)); + self + } + + pub fn eng1_master_lever_select_on(mut self) -> Self { + self.parameters + .set_eng1_master_lever_select_on(Arinc429Parameter::new(true)); + self.parameters + .set_eng1_channel_a_in_control(Arinc429Parameter::new(true)); + self.parameters + .set_eng1_channel_b_in_control(Arinc429Parameter::new(false)); + self + } + + pub fn eng2_master_lever_select_on(mut self) -> Self { + self.parameters + .set_eng2_master_lever_select_on(Arinc429Parameter::new(true)); + self.parameters + .set_eng2_channel_a_in_control(Arinc429Parameter::new(true)); + self.parameters + .set_eng2_channel_b_in_control(Arinc429Parameter::new(false)); + self + } + + pub fn eng1_at_or_above_idle(mut self) -> Self { + self.parameters + .set_eng1_core_speed_at_or_above_idle_a(Arinc429Parameter::new(true)); + self.parameters + .set_eng1_core_speed_at_or_above_idle_b(Arinc429Parameter::new(true)); + self + } + + pub fn eng2_at_or_above_idle(mut self) -> Self { + self.parameters + .set_eng2_core_speed_at_or_above_idle_a(Arinc429Parameter::new(true)); + self.parameters + .set_eng2_core_speed_at_or_above_idle_b(Arinc429Parameter::new(true)); + self + } + + pub fn eng1_tla(mut self, tla: Angle) -> Self { + self.parameters.set_eng1_tla_a(Arinc429Parameter::new(tla)); + self.parameters.set_eng1_tla_b(Arinc429Parameter::new(tla)); + self + } + + pub fn eng1_tla_a(mut self, tla: Angle) -> Self { + self.parameters.set_eng1_tla_a(Arinc429Parameter::new(tla)); + self + } + + pub fn eng1_tla_b(mut self, tla: Angle) -> Self { + self.parameters.set_eng1_tla_b(Arinc429Parameter::new(tla)); + self + } + + pub fn eng2_tla(mut self, tla: Angle) -> Self { + self.parameters.set_eng2_tla_a(Arinc429Parameter::new(tla)); + self.parameters.set_eng2_tla_b(Arinc429Parameter::new(tla)); + self + } + + pub fn eng2_tla_a(mut self, tla: Angle) -> Self { + self.parameters.set_eng2_tla_a(Arinc429Parameter::new(tla)); + self + } + + pub fn eng2_tla_b(mut self, tla: Angle) -> Self { + self.parameters.set_eng2_tla_b(Arinc429Parameter::new(tla)); + self + } +} + +pub fn test_bed() -> A320FWCParameterTestBed { + A320FWCParameterTestBed::new() +} + +pub fn test_bed_with() -> A320FWCParameterTestBed { + test_bed() +} diff --git a/src/systems/a320_systems/src/flight_warning/warnings.rs b/src/systems/a320_systems/src/flight_warning/warnings.rs index 63cf10992f1a..4e6aea84ef1a 100644 --- a/src/systems/a320_systems/src/flight_warning/warnings.rs +++ b/src/systems/a320_systems/src/flight_warning/warnings.rs @@ -1,6 +1,7 @@ use std::time::Duration; -use super::signals::*; +use crate::flight_warning::parameters::*; +use crate::flight_warning::test::test_bed_with; use systems::flight_warning::logic::*; use systems::flight_warning::parameters::{SignStatusMatrix, Value}; use systems::flight_warning::utils::FwcSsm; @@ -525,15 +526,15 @@ pub(super) trait NeoEcu { fn eng_2_limit_mode_soft_ga(&self) -> bool; } -pub(super) struct NeoFlightPhasesDefActivation { +pub(super) struct NeoEcuActivation { eng_1_auto_toga: bool, eng_1_limit_mode_soft_ga: bool, eng_2_auto_toga: bool, eng_2_limit_mode_soft_ga: bool, } -impl NeoFlightPhasesDefActivation { - fn new() -> Self { +impl NeoEcuActivation { + pub fn new() -> Self { Self { eng_1_auto_toga: false, eng_1_limit_mode_soft_ga: false, @@ -558,7 +559,7 @@ impl NeoFlightPhasesDefActivation { } } -impl NeoEcu for NeoFlightPhasesDefActivation { +impl NeoEcu for NeoEcuActivation { fn eng_1_auto_toga(&self) -> bool { self.eng_1_auto_toga } @@ -595,7 +596,7 @@ pub(super) struct TlaAtMctOrFlexToCfmActivation { } impl TlaAtMctOrFlexToCfmActivation { - fn new() -> Self { + pub fn new() -> Self { Self { eng_1_tla_mct_cfm: false, eng_1_end_mct: false, @@ -708,7 +709,7 @@ pub(super) struct TlaPwrReverseActivation { } impl TlaPwrReverseActivation { - fn new() -> Self { + pub fn new() -> Self { Self { conf1: ConfirmationNode::new_falling(Duration::from_secs(10)), conf2: ConfirmationNode::new_falling(Duration::from_secs(10)), @@ -808,7 +809,7 @@ pub(super) struct TlaAtClCfmActivation { } impl TlaAtClCfmActivation { - fn new() -> Self { + pub fn new() -> Self { Self { eng_1_tla_cl_cfm: false, eng_12_mcl_cfm: false, @@ -903,7 +904,7 @@ impl CfmFlightPhasesDefActivation { tla_mct_or_flex_to: &impl TlaAtMctOrFlexToCfm, tla_pwr_reverse: &impl TlaPwrReverse, altitude_def: &impl AltitudeDef, - tla_mcl: &impl TlaAtClCfm, + tla_at_cl_cfm: &impl TlaAtClCfm, ) { let any_cfm = true; @@ -936,7 +937,7 @@ impl CfmFlightPhasesDefActivation { let conf1_out = self.conf1.update(context, conf1_in); let eng1_or_2_to_pwr_cond2 = - conf1_out && !altitude_def.h_gt_1500ft() && tla_mcl.eng_12_mcl_cfm(); + conf1_out && !altitude_def.h_gt_1500ft() && tla_at_cl_cfm.eng_12_mcl_cfm(); let eng1_or_2_to_pwr_cond = eng1_or_2_to_pwr_cond1 || eng1_or_2_to_pwr_cond2; @@ -1028,7 +1029,7 @@ impl AltitudeDef for AltitudeDefActivation { } } -pub(super) trait FlightPhases1 { +pub(super) trait FlightPhasesGround { fn phase_1(&self) -> bool; fn phase_2(&self) -> bool; fn phase_3(&self) -> bool; @@ -1038,7 +1039,7 @@ pub(super) trait FlightPhases1 { fn phase_10(&self) -> bool; } -pub(super) struct FlightPhasesGround { +pub(super) struct FlightPhasesGroundActivation { trans1: TransientDetectionNode, conf1: ConfirmationNode, mtrig1: MonostableTriggerNode, @@ -1059,7 +1060,7 @@ pub(super) struct FlightPhasesGround { phase10: bool, } -impl FlightPhasesGround { +impl FlightPhasesGroundActivation { pub fn new() -> Self { Self { trans1: TransientDetectionNode::new(false), @@ -1164,7 +1165,7 @@ impl FlightPhasesGround { } } -impl FlightPhases1 for FlightPhasesGround { +impl FlightPhasesGround for FlightPhasesGroundActivation { fn phase_1(&self) -> bool { self.phase1 } @@ -1194,13 +1195,13 @@ impl FlightPhases1 for FlightPhasesGround { } } -pub(super) trait FlightPhases2 { +pub(super) trait FlightPhasesAir { fn phase_5(&self) -> bool; fn phase_6(&self) -> bool; fn phase_7(&self) -> bool; } -pub(super) struct FlightPhasesAir { +pub(super) struct FlightPhasesAirActivation { conf1: ConfirmationNode, mtrig1: MonostableTriggerNode, mtrig2: MonostableTriggerNode, @@ -1212,7 +1213,7 @@ pub(super) struct FlightPhasesAir { phase7: bool, } -impl FlightPhasesAir { +impl FlightPhasesAirActivation { pub fn new() -> Self { Self { conf1: ConfirmationNode::new_leading(Duration::from_secs_f64(0.2)), @@ -1231,17 +1232,17 @@ impl FlightPhasesAir { &mut self, context: &UpdateContext, ground_sheet: &impl GroundDetection, - height_sheet: &impl AltitudeDef, - to_power: &impl CfmFlightPhasesDef, - flight_phases_1: &impl FlightPhases1, + altitude_sheet: &impl AltitudeDef, + cfm_flight_phases_sheet: &impl CfmFlightPhasesDef, + flight_phases_gnd_sheet: &impl FlightPhasesGround, ) { let mtrig3_in = ground_sheet.ground_immediate(); let ground_immediate = self.mtrig3.update(context, mtrig3_in) || mtrig3_in; - let eng_1_or_2_to_pwr = to_power.eng_1_or_2_to_pwr(); - let h_fail = height_sheet.h_fail(); - let h_gt_1500ft = height_sheet.h_gt_1500ft(); - let h_gt_800ft = height_sheet.h_gt_800ft(); + let eng_1_or_2_to_pwr = cfm_flight_phases_sheet.eng_1_or_2_to_pwr(); + let h_fail = altitude_sheet.h_fail(); + let h_gt_1500ft = altitude_sheet.h_gt_1500ft(); + let h_gt_800ft = altitude_sheet.h_gt_800ft(); let conf1_cond = self.trans1.update(h_gt_800ft); let pulse_cond = self.conf1.update(context, conf1_cond); @@ -1261,11 +1262,11 @@ impl FlightPhasesAir { self.phase5 = phase5_cond; self.phase6 = !phase5_cond && !ground_immediate && !phase7_cond; - self.phase7 = phase7_cond && !flight_phases_1.phase_8(); + self.phase7 = phase7_cond && !flight_phases_gnd_sheet.phase_8(); } } -impl FlightPhases2 for FlightPhasesAir { +impl FlightPhasesAir for FlightPhasesAirActivation { fn phase_5(&self) -> bool { self.phase5 } @@ -1281,6 +1282,7 @@ impl FlightPhases2 for FlightPhasesAir { #[cfg(test)] mod tests { + use uom::si::angle::radian; use uom::si::f64::*; use uom::si::{ acceleration::foot_per_second_squared, length::foot, @@ -1304,7 +1306,7 @@ mod tests { .ess_lh_lg_compressed() .lh_lg_compressed(2) .norm_lh_lg_compressed() - .signals(), + .parameters(), ); assert_eq!(sheet.new_ground, true); assert_eq!(sheet.lgciu_12_inv, false); @@ -1318,7 +1320,7 @@ mod tests { test_bed_with() .lh_lg_extended(1) .lh_lg_extended(2) - .signals(), + .parameters(), ); assert_eq!(sheet.new_ground, false); assert_eq!(sheet.lgciu_12_inv, false); @@ -1332,7 +1334,7 @@ mod tests { test_bed_with() .lh_lg_compressed(1) .lh_lg_extended(2) - .signals(), + .parameters(), ); assert_eq!(sheet.new_ground, false); assert_eq!(sheet.lgciu_12_inv, true); @@ -1375,11 +1377,11 @@ mod tests { .ess_lh_lg_compressed() .norm_lh_lg_compressed() .radio_heights(Length::new::(0.0), Length::new::(-1.0)) - .signals(), + .parameters(), &TestLgciuGroundDetection::new(true, false), ); - assert_eq!(sheet.ground_immediate, true); - assert_eq!(sheet.ground, true); + assert_eq!(sheet.ground_immediate(), true); + assert_eq!(sheet.ground(), true); } #[test] @@ -1391,22 +1393,37 @@ mod tests { .ess_lh_lg_compressed() .norm_lh_lg_compressed() .radio_heights(Length::new::(0.0), Length::new::(-1.0)) - .signals(), + .parameters(), &TestLgciuGroundDetection::new(true, false), ); - assert_eq!(sheet.ground_immediate, true); - assert_eq!(sheet.ground, false); + assert_eq!(sheet.ground_immediate(), true); + assert_eq!(sheet.ground(), false); sheet.update( &gnd_context(Duration::from_millis(500)), test_bed_with() .ess_lh_lg_compressed() .norm_lh_lg_compressed() .radio_heights(Length::new::(0.0), Length::new::(-1.0)) - .signals(), + .parameters(), + &TestLgciuGroundDetection::new(true, false), + ); + assert_eq!(sheet.ground_immediate(), true); + assert_eq!(sheet.ground(), true); + } + + #[test] + fn when_dual_ra_failure_on_ground() { + let mut sheet = GroundDetectionActivation::new(); + sheet.update( + &gnd_context(Duration::from_millis(500)), + test_bed_with() + .ess_lh_lg_compressed() + .norm_lh_lg_compressed() + .parameters(), &TestLgciuGroundDetection::new(true, false), ); - assert_eq!(sheet.ground_immediate, true); - assert_eq!(sheet.ground, true); + assert_eq!(sheet.ground_immediate(), true); + assert_eq!(sheet.ground(), false); } } @@ -1420,7 +1437,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .eng1_tla(Angle::new::(37.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.eng_1_tla_mct_cfm(), false); assert_eq!(sheet.eng_1_end_mct(), false); @@ -1434,7 +1451,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .eng1_tla(Angle::new::(36.65)) - .signals(), + .parameters(), ); assert_eq!(sheet.eng_1_tla_mct_cfm(), true); assert_eq!(sheet.eng_1_end_mct(), true); @@ -1448,7 +1465,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .eng1_tla(Angle::new::(33.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.eng_1_tla_mct_cfm(), false); assert_eq!(sheet.eng_1_end_mct(), false); @@ -1462,7 +1479,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .eng1_tla(Angle::new::(35.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.eng_1_tla_mct_cfm(), true); assert_eq!(sheet.eng_1_end_mct(), false); @@ -1485,7 +1502,7 @@ mod tests { Velocity::new::(0.0), Velocity::new::(0.0), ) - .signals(), + .parameters(), ); assert_eq!(sheet.ac_speed_above_80_kt(), false); } @@ -1501,7 +1518,7 @@ mod tests { Velocity::new::(250.0), Velocity::new::(250.0), ) - .signals(), + .parameters(), ); assert_eq!(sheet.ac_speed_above_80_kt(), true); } @@ -1517,7 +1534,7 @@ mod tests { Velocity::new::(0.0), Velocity::new::(0.0), ) - .signals(), + .parameters(), ); assert_eq!(sheet.ac_speed_above_80_kt(), false); } @@ -1529,7 +1546,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .computed_speed_1(Velocity::new::(250.0)) - .signals(), // todo ADC failures + .parameters(), // TODO ADC failures ); assert_eq!(sheet.ac_speed_above_80_kt(), true); } @@ -1545,7 +1562,7 @@ mod tests { Velocity::new::(0.0), Velocity::new::(250.0), ) - .signals(), + .parameters(), ); assert_eq!(sheet.ac_speed_above_80_kt(), true); } @@ -1561,7 +1578,7 @@ mod tests { Velocity::new::(49.0), Velocity::new::(49.0), ) - .signals(), + .parameters(), ); sheet.update( &gnd_context(Duration::from_secs_f64(0.5)), @@ -1571,7 +1588,7 @@ mod tests { Velocity::new::(84.0), Velocity::new::(84.0), ) - .signals(), + .parameters(), ); assert_eq!(sheet.ac_speed_above_80_kt(), false); } @@ -1587,7 +1604,7 @@ mod tests { Velocity::new::(49.0), Velocity::new::(49.0), ) - .signals(), + .parameters(), ); sheet.update( &gnd_context(Duration::from_secs(1)), @@ -1597,7 +1614,7 @@ mod tests { Velocity::new::(84.0), Velocity::new::(84.0), ) - .signals(), + .parameters(), ); assert_eq!(sheet.ac_speed_above_80_kt(), true); } @@ -1612,7 +1629,7 @@ mod tests { let mut sheet = EnginesNotRunning::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), ); assert_eq!(sheet.eng_1_not_running(), true); @@ -1624,7 +1641,7 @@ mod tests { let mut sheet = EnginesNotRunning::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().eng1_master_lever_select_on().signals(), + test_bed_with().eng1_master_lever_select_on().parameters(), &TestGroundDetection::new(true), ); assert_eq!(sheet.eng_1_not_running(), true); @@ -1639,7 +1656,7 @@ mod tests { test_bed_with() .eng1_master_lever_select_on() .eng1_at_or_above_idle() - .signals(), + .parameters(), &TestGroundDetection::new(true), ); assert_eq!(sheet.eng_1_not_running(), false); @@ -1651,7 +1668,7 @@ mod tests { let mut sheet = EnginesNotRunning::new(); sheet.update( &gnd_context(Duration::from_secs(30)), - test_bed_with().eng1_at_or_above_idle().signals(), + test_bed_with().eng1_at_or_above_idle().parameters(), &TestGroundDetection::new(true), ); assert_eq!(sheet.eng_1_not_running(), false); @@ -1667,7 +1684,7 @@ mod tests { // Engine 1 just turned on, we would need to wait 30s for "off" confirmation... sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().eng1_at_or_above_idle().signals(), + test_bed_with().eng1_at_or_above_idle().parameters(), &TestGroundDetection::new(false), ); assert_eq!(sheet.eng_1_not_running(), true); @@ -1678,7 +1695,7 @@ mod tests { test_bed_with() .eng1_at_or_above_idle() .eng1_fire_pb_out() - .signals(), + .parameters(), &TestGroundDetection::new(false), ); assert_eq!(sheet.eng_1_not_running(), false); @@ -1691,7 +1708,7 @@ mod tests { // Engine 2 just turned on, we would need to wait 30s for "off" confirmation... sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().eng1_at_or_above_idle().signals(), + test_bed_with().eng1_at_or_above_idle().parameters(), &TestGroundDetection::new(false), ); assert_eq!(sheet.eng_2_not_running(), true); @@ -1702,7 +1719,7 @@ mod tests { test_bed_with() .eng1_at_or_above_idle() .eng1_fire_pb_out() - .signals(), + .parameters(), &TestGroundDetection::new(false), ); assert_eq!(sheet.eng_2_not_running(), false); @@ -1720,7 +1737,7 @@ mod tests { let mut sheet = AltitudeDefActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().radio_heights_at_cruise().signals(), + test_bed_with().radio_heights_at_cruise().parameters(), ); assert_eq!(sheet.h_fail(), false); assert_eq!(sheet.h_gt_800ft, true); @@ -1734,7 +1751,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .radio_heights(Length::new::(1600.0), Length::new::(1600.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.h_fail(), false); assert_eq!(sheet.h_gt_800ft, true); @@ -1748,13 +1765,13 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .radio_heights(Length::new::(1501.0), Length::new::(1501.0)) - .signals(), + .parameters(), ); sheet.update( &gnd_context(Duration::from_secs(1)), test_bed_with() .radio_heights(Length::new::(1499.0), Length::new::(1499.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.h_fail(), false); assert_eq!(sheet.h_gt_800ft, true); @@ -1768,13 +1785,13 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .radio_heights(Length::new::(1501.0), Length::new::(1501.0)) - .signals(), + .parameters(), ); sheet.update( &gnd_context(Duration::from_secs(1)), test_bed_with() .radio_heights(Length::new::(799.0), Length::new::(799.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.h_fail(), false); assert_eq!(sheet.h_gt_800ft, false); @@ -1788,7 +1805,7 @@ mod tests { &gnd_context(Duration::from_secs(1)), test_bed_with() .radio_heights(Length::new::(0.0), Length::new::(0.0)) - .signals(), + .parameters(), ); assert_eq!(sheet.h_fail(), false); assert_eq!(sheet.h_gt_800ft, false); @@ -1797,119 +1814,162 @@ mod tests { } #[cfg(test)] - mod general_flight_phases_1 { + mod flight_phases_ground { use super::*; #[test] fn when_cold_and_dark_is_phase_1() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), &TestSpeedDetection::new(false), &TestEngRunning::new(false, false), &TestCfmFlightPhasesDef::new_below_flex(), ); - assert_eq!(sheet.phase_1(), true); + assert!(sheet.phase_1()); } #[test] fn when_one_eng_running_is_phase_2() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), &TestSpeedDetection::new(false), &TestEngRunning::new(true, false), &TestCfmFlightPhasesDef::new_below_flex(), ); - assert_eq!(sheet.phase_2(), true); + assert!(sheet.phase_2()); } #[test] fn when_at_flex_takeoff_is_phase_3() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), &TestSpeedDetection::new(false), &TestEngRunning::new(true, true), &TestCfmFlightPhasesDef::new_flex(), ); - assert_eq!(sheet.phase_3(), true); + assert!(sheet.phase_3()); } #[test] fn when_at_toga_takeoff_is_phase_3() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), &TestSpeedDetection::new(false), &TestEngRunning::new(true, true), &TestCfmFlightPhasesDef::new_toga(), ); - assert_eq!(sheet.phase_3(), true); + assert!(sheet.phase_3()); } #[test] fn when_at_toga_above_80_kt_is_phase_4() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), &TestSpeedDetection::new(true), &TestEngRunning::new(true, true), &TestCfmFlightPhasesDef::new_toga(), ); - assert_eq!(sheet.phase_4(), true); + assert!(sheet.phase_4()); + } + + #[test] + fn when_engine_failed_above_80_kt_is_phase_4() { + let mut sheet = FlightPhasesGroundActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + test_bed_with().parameters(), + &TestGroundDetection::new(true), + &TestSpeedDetection::new(true), + &TestEngRunning::new(false, true), + &TestCfmFlightPhasesDef::new_toga(), + ); + assert!(sheet.phase_4()); } #[test] fn when_below_flex_above_80_kt_is_phase_8() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().signals(), + test_bed_with().parameters(), &TestGroundDetection::new(true), &TestSpeedDetection::new(true), &TestEngRunning::new(true, true), &TestCfmFlightPhasesDef::new_below_flex(), ); - assert_eq!(sheet.phase_8(), true); + assert!(sheet.phase_8()); } - /* #[test] - fn after_high_speed_rto_below_80_knots_is_phase_9() { - let mut sheet = FlightPhasesGround::new(); - sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); - sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); - sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); - assert_eq!(sheet.get_phase(), 9); + fn after_rto_below_80_knots_is_phase_9() { + let mut sheet = FlightPhasesGroundActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + test_bed_with().parameters(), + &TestGroundDetection::new(true), + &TestSpeedDetection::new(false), + &TestEngRunning::new(true, true), + &TestCfmFlightPhasesDef::new_toga(), + ); + sheet.update( + &gnd_context(Duration::from_secs(1)), + test_bed_with().parameters(), + &TestGroundDetection::new(true), + &TestSpeedDetection::new(false), + &TestEngRunning::new(true, true), + &TestCfmFlightPhasesDef::new_below_flex(), + ); + assert!(sheet.phase_9()); } #[test] - fn after_high_speed_rto_below_80_knots_and_to_config_is_phase_2() { - let mut sheet = FlightPhasesGround::new(); - sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); - sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); - sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); + fn after_rto_below_80_knots_and_to_config_is_phase_2() { + let mut sheet = FlightPhasesGroundActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + test_bed_with().parameters(), + &TestGroundDetection::new(true), + &TestSpeedDetection::new(false), + &TestEngRunning::new(true, true), + &TestCfmFlightPhasesDef::new_toga(), + ); sheet.update( &gnd_context(Duration::from_secs(1)), - test_bed_with().takeoff_config_test_pressed().signals(), + test_bed_with().parameters(), + &TestGroundDetection::new(true), + &TestSpeedDetection::new(false), + &TestEngRunning::new(true, true), + &TestCfmFlightPhasesDef::new_below_flex(), ); - assert_eq!(sheet.get_phase(), 2); + sheet.update( + &gnd_context(Duration::from_secs(1)), + test_bed_with().takeoff_config_test_pressed().parameters(), + &TestGroundDetection::new(true), + &TestSpeedDetection::new(false), + &TestEngRunning::new(true, true), + &TestCfmFlightPhasesDef::new_below_flex(), + ); + assert!(sheet.phase_2()); } - #[test] + /*#[test] fn after_engine_shutdown_reset_to_phase_1() { - let mut sheet = FlightPhasesGround::new(); + let mut sheet = FlightPhasesGroundActivation::new(); sheet.update(&gnd_context(Duration::from_secs(1)), test_bed().signals()); assert_eq!(sheet.get_phase(), 3); sheet.update(&gnd_context(Duration::from_secs(30)), test_bed().signals()); @@ -1923,196 +1983,150 @@ mod tests { }*/ } - fn gnd_context(delta_time: Duration) -> UpdateContext { - UpdateContext::new( - delta_time, - Velocity::new::(0.), - Length::new::(0.), - ThermodynamicTemperature::new::(25.0), - true, - Acceleration::new::(0.), - ) - } - - struct A320SignalTestBed { - signals: A320SignalTable, - } - impl A320SignalTestBed { - fn new() -> Self { - let mut signals = A320SignalTable::new(); - Self { signals } - } - - fn and(self) -> Self { - self - } - - fn signals(&self) -> &A320SignalTable { - &self.signals - } - - fn takeoff_config_test_pressed(mut self) -> Self { - self.signals.set_takeoff_config_test(true); - self - } - - fn computed_speeds(mut self, speed1: Velocity, speed2: Velocity, speed3: Velocity) -> Self { - self.signals - .set_computed_speed_1(Arinc429Parameter::new(speed1)); - self.signals - .set_computed_speed_2(Arinc429Parameter::new(speed2)); - self.signals - .set_computed_speed_3(Arinc429Parameter::new(speed3)); - self - } - - fn computed_speed_1(mut self, speed: Velocity) -> Self { - self.signals - .set_computed_speed_1(Arinc429Parameter::new(speed)); - self - } - - fn computed_speed_2(mut self, speed: Velocity) -> Self { - self.signals - .set_computed_speed_2(Arinc429Parameter::new(speed)); - self - } - - fn computed_speed_3(mut self, speed: Velocity) -> Self { - self.signals - .set_computed_speed_3(Arinc429Parameter::new(speed)); - self - } - - fn lh_lg_compressed(mut self, lgciu: usize) -> Self { - match lgciu { - 1 => self - .signals - .set_lh_lg_compressed_1(Arinc429Parameter::new(true)), - 2 => self - .signals - .set_lh_lg_compressed_2(Arinc429Parameter::new(true)), - _ => panic!(), - } - self - } - - fn lh_lg_extended(mut self, lgciu: usize) -> Self { - match lgciu { - 1 => self - .signals - .set_lh_lg_compressed_1(Arinc429Parameter::new(false)), - 2 => self - .signals - .set_lh_lg_compressed_2(Arinc429Parameter::new(false)), - _ => panic!(), - } - self - } - - fn ess_lh_lg_compressed(mut self) -> Self { - self.signals - .set_ess_lh_lg_compressed(DiscreteParameter::new(true)); - self - } - - fn norm_lh_lg_compressed(mut self) -> Self { - self.signals - .set_norm_lh_lg_compressed(DiscreteParameter::new(true)); - self - } - - fn radio_heights(mut self, height1: Length, height2: Length) -> Self { - self.signals - .set_radio_height_1(Arinc429Parameter::new(height1)); - self.signals - .set_radio_height_2(Arinc429Parameter::new(height2)); - self - } - - /// Simulates a flight at cruise, where the radio altimeters will not be able to receive a - /// valid ground return and mark their data as NCD. - fn radio_heights_at_cruise(mut self) -> Self { - self.signals - .set_radio_height_1(Arinc429Parameter::new_ncd(Length::new::(10000.0))); - self.signals - .set_radio_height_2(Arinc429Parameter::new_ncd(Length::new::(10000.0))); - self - } - - fn eng1_fire_pb_out(mut self) -> Self { - self.signals - .set_eng_1_fire_pb_out(DiscreteParameter::new(true)); - self - } - - fn eng1_master_lever_select_on(mut self) -> Self { - self.signals - .set_eng1_master_lever_select_on(Arinc429Parameter::new(true)); - self - } + #[cfg(test)] + mod flight_phases_air { + use super::*; - fn eng2_master_lever_select_on(mut self) -> Self { - self.signals - .set_eng1_master_lever_select_on(Arinc429Parameter::new(true)); - self + #[test] + fn when_at_toga_below_1500ft_is_phase_5() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_takeoff(Length::new::(1499.0)), + &TestCfmFlightPhasesDef::new_toga(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_5()); } - fn eng1_at_or_above_idle(mut self) -> Self { - self.signals - .set_eng1_core_speed_at_or_above_idle_a(Arinc429Parameter::new(true)); - self.signals - .set_eng1_core_speed_at_or_above_idle_b(Arinc429Parameter::new(true)); - self + #[test] + fn when_at_flex_below_1500ft_is_phase_5() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_takeoff(Length::new::(1499.0)), + &TestCfmFlightPhasesDef::new_flex(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_5()); } - fn eng2_at_or_above_idle(mut self) -> Self { - self.signals - .set_eng2_core_speed_at_or_above_idle_a(Arinc429Parameter::new(true)); - self.signals - .set_eng2_core_speed_at_or_above_idle_b(Arinc429Parameter::new(true)); - self + #[test] + fn when_at_takeoff_power_above_1500ft_is_phase_6() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_takeoff(Length::new::(1500.1)), + &TestCfmFlightPhasesDef::new_toga(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_6()); } - fn eng1_tla(mut self, tla: Angle) -> Self { - self.signals.set_eng1_tla_a(Arinc429Parameter::new(tla)); - self.signals.set_eng1_tla_b(Arinc429Parameter::new(tla)); - self + #[test] + fn when_at_flex_above_1500ft_is_phase_6() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_takeoff(Length::new::(1500.1)), + &TestCfmFlightPhasesDef::new_flex(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_6()); } - fn eng1_tla_a(mut self, tla: Angle) -> Self { - self.signals.set_eng1_tla_a(Arinc429Parameter::new(tla)); - self + #[test] + fn when_at_below_flex_below_800ft_is_phase_7() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_landing(Length::new::(799.9)), + &TestCfmFlightPhasesDef::new_below_flex(), + &TestFlightPhasesGround::default(), + ); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_landing(Length::new::(799.9)), + &TestCfmFlightPhasesDef::new_below_flex(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_7()); } - fn eng1_tla_b(mut self, tla: Angle) -> Self { - self.signals.set_eng1_tla_b(Arinc429Parameter::new(tla)); - self + #[test] + fn when_at_flex_below_800ft_is_phase_5() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_landing(Length::new::(799.9)), + &TestCfmFlightPhasesDef::new_flex(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_5()); } - fn eng2_tla(mut self, tla: Angle) -> Self { - self.signals.set_eng2_tla_a(Arinc429Parameter::new(tla)); - self.signals.set_eng2_tla_b(Arinc429Parameter::new(tla)); - self + #[test] + fn when_at_toga_below_800ft_is_phase_5() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_landing(Length::new::(799.9)), + &TestCfmFlightPhasesDef::new_toga(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_5()); } - fn eng2_tla_a(mut self, tla: Angle) -> Self { - self.signals.set_eng2_tla_a(Arinc429Parameter::new(tla)); - self + #[test] + fn when_ra_failed_is_phase_6() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(false), + &TestAltitudeDef::new_failed(), + &TestCfmFlightPhasesDef::new_flex(), + &TestFlightPhasesGround::default(), + ); + assert!(sheet.phase_6()); } - fn eng2_tla_b(mut self, tla: Angle) -> Self { - self.signals.set_eng2_tla_b(Arinc429Parameter::new(tla)); - self + #[test] + fn when_on_ground_and_ra_failed_is_no_phase() { + let mut sheet = FlightPhasesAirActivation::new(); + sheet.update( + &gnd_context(Duration::from_secs(1)), + &TestGroundDetection::new(true), + &TestAltitudeDef::new_failed(), + &TestCfmFlightPhasesDef::new_below_flex(), + &TestFlightPhasesGround::default(), + ); + assert!(!sheet.phase_5()); + assert!(!sheet.phase_6()); + assert!(!sheet.phase_7()); } } - fn test_bed() -> A320SignalTestBed { - A320SignalTestBed::new() - } - - fn test_bed_with() -> A320SignalTestBed { - test_bed() + fn gnd_context(delta_time: Duration) -> UpdateContext { + UpdateContext::new( + delta_time, + Velocity::new::(0.), + Length::new::(0.), + ThermodynamicTemperature::new::(25.0), + true, + Acceleration::new::(0.), + Acceleration::new::(0.), + Acceleration::new::(0.), + Angle::new::(0.), + Angle::new::(0.), + ) } struct TestGroundDetection { @@ -2177,6 +2191,52 @@ mod tests { } } + struct TestAltitudeDef { + h_gt_800ft: bool, + h_gt_1500ft: bool, + h_fail: bool, + } + + impl TestAltitudeDef { + fn new_takeoff(height: Length) -> Self { + Self { + h_gt_800ft: height > Length::new::(1500.0), + h_gt_1500ft: height > Length::new::(1500.0), + h_fail: false, + } + } + + fn new_landing(height: Length) -> Self { + Self { + h_gt_800ft: height > Length::new::(800.0), + h_gt_1500ft: height > Length::new::(1500.0), + h_fail: false, + } + } + + fn new_failed() -> Self { + Self { + h_gt_800ft: false, + h_gt_1500ft: false, + h_fail: true, + } + } + } + + impl AltitudeDef for TestAltitudeDef { + fn h_gt_800ft(&self) -> bool { + self.h_gt_800ft + } + + fn h_gt_1500ft(&self) -> bool { + self.h_gt_1500ft + } + + fn h_fail(&self) -> bool { + self.h_fail + } + } + struct TestEngRunning { eng_1_and_2_not_running: bool, eng_1_or_2_running: bool, @@ -2250,4 +2310,50 @@ mod tests { self.eng_1_or_2_to_pwr } } + + struct TestFlightPhasesGround { + phase: usize, + } + + impl TestFlightPhasesGround { + fn new(phase: usize) -> Self { + Self { phase } + } + } + + impl FlightPhasesGround for TestFlightPhasesGround { + fn phase_1(&self) -> bool { + self.phase == 1 + } + + fn phase_2(&self) -> bool { + self.phase == 2 + } + + fn phase_3(&self) -> bool { + self.phase == 3 + } + + fn phase_4(&self) -> bool { + self.phase == 4 + } + + fn phase_8(&self) -> bool { + self.phase == 8 + } + + fn phase_9(&self) -> bool { + self.phase == 9 + } + + fn phase_10(&self) -> bool { + self.phase == 10 + } + } + + impl Default for TestFlightPhasesGround { + fn default() -> Self { + Self::new(0) + } + } } diff --git a/src/systems/systems/src/flight_warning/mod.rs b/src/systems/systems/src/flight_warning/mod.rs index 5b02b34f7a52..70bed2dc4b25 100644 --- a/src/systems/systems/src/flight_warning/mod.rs +++ b/src/systems/systems/src/flight_warning/mod.rs @@ -1,3 +1,28 @@ +use crate::flight_warning::parameters::SignStatusMatrix; + pub mod logic; pub mod parameters; pub mod utils; + +pub trait FwcReaderWriter { + fn read(&mut self, name: &str) -> f64; + fn write(&mut self, name: &str, value: f64); +} + +pub struct FwcWriter<'a> { + fwc_read_writer: &'a mut dyn FwcReaderWriter, +} + +impl<'a> FwcWriter<'a> { + pub fn new(fwc_read_writer: &'a mut dyn FwcReaderWriter) -> Self { + Self { fwc_read_writer } + } + + pub fn write_f64(&mut self, name: &str, value: f64) { + self.fwc_read_writer.write(name, value); + } +} + +pub fn is_inv(parameter: &impl SignStatusMatrix) -> bool { + parameter.is_fw() +} diff --git a/src/systems/systems/src/simulation/mod.rs b/src/systems/systems/src/simulation/mod.rs index d68e19eb8544..8a4dce6b902e 100644 --- a/src/systems/systems/src/simulation/mod.rs +++ b/src/systems/systems/src/simulation/mod.rs @@ -71,6 +71,7 @@ pub trait Aircraft: SimulationElement { { electricity.report_consumption_to(context, self); } + fn update_fwc(&mut self, _writer: &FwcWriter) {} } /// The [`Simulation`] runs across many different [`SimulationElement`]s. @@ -677,3 +678,22 @@ impl Write for T { value.as_secs_f64() } } + +pub trait FwcReaderWriter { + fn read(&mut self, name: &str) -> f64; + fn write(&mut self, name: &str, value: f64); +} + +pub struct FwcWriter<'a> { + fwc_read_writer: &'a mut dyn FwcReaderWriter, +} + +impl<'a> FwcWriter<'a> { + pub fn new(fwc_read_writer: &'a mut dyn FwcReaderWriter) -> Self { + Self { fwc_read_writer } + } + + pub fn write_f64(&mut self, name: &str, value: f64) { + self.fwc_read_writer.write(name, value); + } +}