diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/cabin_pressure_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/cabin_pressure_controller.rs index 2288138c2b2..f29cfcca6a3 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/cabin_pressure_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/cabin_pressure_controller.rs @@ -66,6 +66,8 @@ pub struct CabinPressureController { cabin_alt: Length, cabin_vertical_speed: Velocity, cabin_filtered_vertical_speed: LowPassFilter, + cabin_delta_p_rate: f64, + previous_cabin_delta_p: Option, cabin_target_vs: Velocity, outflow_valve_open_amount: Ratio, safety_valve_open_amount: Ratio, @@ -77,6 +79,7 @@ pub struct CabinPressureController { is_in_man_mode: bool, man_mode_duration: Duration, manual_to_auto_switch: bool, + low_diff_pressure_warning: bool, is_active: bool, is_initialised: bool, @@ -143,6 +146,8 @@ impl CabinPressureController { cabin_filtered_vertical_speed: LowPassFilter::new( Self::VERTICAL_SPEED_FILTER_TIME_CONSTANT, ), + cabin_delta_p_rate: 0., + previous_cabin_delta_p: None, cabin_target_vs: Velocity::default(), outflow_valve_open_amount: Ratio::new::(100.), safety_valve_open_amount: Ratio::new::(0.), @@ -154,6 +159,7 @@ impl CabinPressureController { is_in_man_mode: false, man_mode_duration: Duration::from_secs(0), manual_to_auto_switch: false, + low_diff_pressure_warning: false, is_active: false, is_initialised: false, @@ -209,6 +215,7 @@ impl CabinPressureController { self.cabin_alt = new_cabin_alt; self.reference_pressure = new_reference_pressure; self.departure_elevation = self.calculate_departure_elevation(); + self.update_cabin_delta_p_rate(context); if !self.has_fault() { self.outflow_valve_controller.update( @@ -246,6 +253,7 @@ impl CabinPressureController { )); } + self.update_low_diff_pressure_warning(); self.is_in_man_mode = press_overhead.is_in_man_mode(); self.is_active = is_active; } @@ -573,11 +581,50 @@ impl CabinPressureController { } pub fn is_low_diff_pressure(&self) -> bool { - // Only emit the signal if delta pressure information is valid - self.cabin_delta_p() < Pressure::new::(C::LOW_DIFFERENTIAL_PRESSURE_WARNING) - && self.cabin_alt > (self.landing_elevation + Length::new::(1500.)) - && self.exterior_vertical_speed.output() < Velocity::new::(-500.) + self.low_diff_pressure_warning + } + + fn update_cabin_delta_p_rate(&mut self, context: &UpdateContext) { + let delta_seconds = context.delta_as_secs_f64(); + let cabin_delta_p_psi = self.cabin_delta_p().get::(); + self.cabin_delta_p_rate = if delta_seconds > 0. { + self.previous_cabin_delta_p + .map(|previous| (cabin_delta_p_psi - previous) / delta_seconds) + .unwrap_or(0.) + } else { + 0. + }; + self.previous_cabin_delta_p = Some(cabin_delta_p_psi); + } + + fn update_low_diff_pressure_warning(&mut self) { + let in_descent = !matches!( + self.pressure_schedule_manager, + Some(PressureScheduleManager::DescentInternal(_)) + ); + let delta_p_psi = self.cabin_delta_p().get::(); + let delta_p_rate = self.cabin_delta_p_rate; + + let time_to_delta_p_zero_secs = delta_p_psi / -delta_p_rate; + + let cabin_vs_fpm = self.cabin_vertical_speed().get::(); + let cabin_alt_diff_ft = (self.cabin_alt - self.landing_elevation).get::(); + let time_to_cabin_landing_secs = cabin_alt_diff_ft / -cabin_vs_fpm * 60.; + + let altitude_above_landing_ft = + (self.exterior_flight_altitude - self.landing_elevation).get::(); + + let activation_condition = time_to_delta_p_zero_secs < 90. + && time_to_delta_p_zero_secs < time_to_cabin_landing_secs + 30. + && altitude_above_landing_ft > 3000. && self.adirs_data_is_valid + && in_descent; + + if activation_condition && altitude_above_landing_ft > 3000. { + self.low_diff_pressure_warning = true; + } else if !activation_condition { + self.low_diff_pressure_warning = false; + } } fn cabin_delta_p_out(&self) -> Pressure { @@ -2085,4 +2132,90 @@ mod tests { assert!(test_bed.query(|a| a.is_climb())); } + + #[test] + fn low_diff_pressure_triggers_in_descent() { + let mut test_bed = test_bed(); + + test_bed.command(|a| { + a.cpc.pressure_schedule_manager = + Some(PressureScheduleManager::DescentInternal(PressureSchedule { + timer: Duration::from_secs(0), + pressure_schedule: DescentInternal, + })); + a.cpc.adirs_data_is_valid = true; + a.cpc.landing_elevation = Length::new::(0.); + a.cpc.exterior_flight_altitude = Length::new::(4000.); + a.cpc.cabin_alt = Length::new::(6000.); + a.cpc + .cabin_filtered_vertical_speed + .reset(Velocity::new::(-1000.)); + a.cpc.cabin_pressure = Pressure::new::(11.); + a.cpc.exterior_pressure.reset(Pressure::new::(10.)); + a.cpc.cabin_delta_p_rate = -0.02; + a.cpc.low_diff_pressure_warning = false; + a.cpc.update_low_diff_pressure_warning(); + }); + + assert!(test_bed.query(|a| a.cpc.is_low_diff_pressure())); + } + + #[test] + fn low_diff_pressure_remains_below_3000_ft() { + let mut test_bed = test_bed(); + + test_bed.command(|a| { + a.cpc.pressure_schedule_manager = + Some(PressureScheduleManager::DescentInternal(PressureSchedule { + timer: Duration::from_secs(0), + pressure_schedule: DescentInternal, + })); + a.cpc.adirs_data_is_valid = true; + a.cpc.landing_elevation = Length::new::(0.); + a.cpc.exterior_flight_altitude = Length::new::(4000.); + a.cpc.cabin_alt = Length::new::(6000.); + a.cpc + .cabin_filtered_vertical_speed + .reset(Velocity::new::(-1000.)); + a.cpc.cabin_pressure = Pressure::new::(11.); + a.cpc.exterior_pressure.reset(Pressure::new::(10.)); + a.cpc.cabin_delta_p_rate = -0.02; + a.cpc.low_diff_pressure_warning = false; + a.cpc.update_low_diff_pressure_warning(); + }); + + test_bed.command(|a| { + a.cpc.exterior_flight_altitude = Length::new::(2500.); + a.cpc.update_low_diff_pressure_warning(); + }); + + assert!(test_bed.query(|a| a.cpc.is_low_diff_pressure())); + } + + #[test] + fn low_diff_pressure_rejected_when_time_to_zero_is_too_long() { + let mut test_bed = test_bed(); + + test_bed.command(|a| { + a.cpc.pressure_schedule_manager = + Some(PressureScheduleManager::DescentInternal(PressureSchedule { + timer: Duration::from_secs(0), + pressure_schedule: DescentInternal, + })); + a.cpc.adirs_data_is_valid = true; + a.cpc.landing_elevation = Length::new::(0.); + a.cpc.exterior_flight_altitude = Length::new::(4000.); + a.cpc.cabin_alt = Length::new::(6000.); + a.cpc + .cabin_filtered_vertical_speed + .reset(Velocity::new::(-1000.)); + a.cpc.cabin_pressure = Pressure::new::(11.); + a.cpc.exterior_pressure.reset(Pressure::new::(10.)); + a.cpc.cabin_delta_p_rate = -0.005; + a.cpc.low_diff_pressure_warning = false; + a.cpc.update_low_diff_pressure_warning(); + }); + + assert!(!test_bed.query(|a| a.cpc.is_low_diff_pressure())); + } }