From ae83d348235ec0f57397347ce2c7921f0e63421f Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Tue, 27 Jun 2023 21:59:44 +0100 Subject: [PATCH 01/18] feat: cabin fans failures --- fbw-a32nx/docs/a320-simvars.md | 7 +++ fbw-a32nx/src/systems/failures/src/a320.ts | 3 ++ .../instruments/src/Common/EWDMessages.tsx | 2 + .../EFB/failures-orchestrator-provider.tsx | 3 ++ .../systems/instruments/src/EWD/PseudoFWC.ts | 24 +++++++++- .../instruments/src/SD/Pages/Cond/Cond.tsx | 11 ++--- .../a320_systems/src/air_conditioning.rs | 5 ++- .../wasm/systems/a320_systems_wasm/src/lib.rs | 2 + .../a380_systems/src/air_conditioning/mod.rs | 5 ++- fbw-common/src/systems/shared/src/ata.ts | 1 + .../src/air_conditioning/acs_controller.rs | 44 ++++++++++++++++++- .../systems/src/air_conditioning/mod.rs | 24 ++++++++-- .../wasm/systems/systems/src/failures/mod.rs | 1 + 13 files changed, 118 insertions(+), 14 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index d5d028580db..9f414457287 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2716,6 +2716,13 @@ In the variables below, {number} should be replaced with one item in the set: { - 1 - 2 +- A32NX_VENT_CABIN_FAN_{id}_HAS_FAULT + - Bool + - True if the corresponding cabin fan is on and operating normally + - {number} + - 1 + - 2 + ## Pneumatic - A32NX_PNEU_ENG_{number}_IP_PRESSURE: diff --git a/fbw-a32nx/src/systems/failures/src/a320.ts b/fbw-a32nx/src/systems/failures/src/a320.ts index 0e3bcf1b031..3c63256e104 100644 --- a/fbw-a32nx/src/systems/failures/src/a320.ts +++ b/fbw-a32nx/src/systems/failures/src/a320.ts @@ -1,6 +1,9 @@ // One can rightfully argue that this constant shouldn't be located in @flybywiresim/failures. // Once we create an A320 specific package, such as @flybywiresim/a320, we can move it there. export const A320Failure = Object.freeze({ + CabinFan1Failure: 21000, + CabinFan2Failure: 21001, + Fac1Failure: 22000, Fac2Failure: 22001, TransformerRectifier1: 24000, diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index 3ea7a0b898a..d4d3c863a21 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -106,6 +106,8 @@ const EWDMessages = { '000056001': '\x1b<3mHI ALT SET', '000068001': '\x1b<3mADIRS SWTG', '000056701': '\x1b<3mVHF3 VOICE', + '210014001': '\x1b<4m\x1b4mCOND\x1bm L+R CAB FAN FAULT', + '210014002': '\x1b<5m -PACK FLOW...........HI', '213122101': '\x1b<2m\x1b4mCAB PR\x1bm EXCESS CAB ALT', '213122102': '\x1b<5m -CREW OXY MASKS.....USE', '213122103': '\x1b<5m -SIGNS...............ON', diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index 3c50fa51545..5a25646389e 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -15,6 +15,9 @@ interface FailuresOrchestratorContext { } const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ + [21, A320Failure.CabinFan1Failure, 'Cabin Fan 1'], + [21, A320Failure.CabinFan2Failure, 'Cabin Fan 2'], + [22, A320Failure.Fac1Failure, 'FAC 1'], [22, A320Failure.Fac2Failure, 'FAC 2'], diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index 99015685bb8..0af5c4a6806 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -92,7 +92,7 @@ export class PseudoFWC { this.fireActive, ); - /* PRESSURIZATION */ + /* 21 - AIR CONDITIONING AND PRESSURIZATION */ private readonly apuBleedValveOpen = Subject.create(false); @@ -104,6 +104,10 @@ export class PseudoFWC { private readonly cabAltSetResetState2 = Subject.create(false); + private readonly cabFanHasFault1 = Subject.create(false); + + private readonly cabFanHasFault2 = Subject.create(false); + private readonly excessPressure = Subject.create(false); private readonly packOffBleedAvailable1 = new NXLogicConfirmNode(5, false); @@ -1023,8 +1027,13 @@ export class PseudoFWC { this.ac2BusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_2_BUS_IS_POWERED', 'bool')); this.acESSBusPowered.set(SimVar.GetSimVarValue('L:A32NX_ELEC_AC_ESS_BUS_IS_POWERED', 'bool')); - /* AIR CONDITIONING */ + /* 21 - AIR CONDITIONING AND PRESSURIZATION */ + + /* CABIN FANS */ + this.cabFanHasFault1.set(SimVar.GetSimVarValue('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool')); + this.cabFanHasFault2.set(SimVar.GetSimVarValue('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool')); + /* BLEED AND PACKS */ const crossfeed = SimVar.GetSimVarValue('L:A32NX_PNEU_XBLEED_VALVE_OPEN', 'bool'); const eng1Bleed = SimVar.GetSimVarValue('A:BLEED AIR ENGINE:1', 'bool'); const eng1BleedPbFault = SimVar.GetSimVarValue('L:A32NX_OVHD_PNEU_ENG_1_BLEED_PB_HAS_FAULT', 'bool'); @@ -1035,6 +1044,7 @@ export class PseudoFWC { const pack1On = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_1_PB_IS_ON', 'bool'); const pack2On = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_2_PB_IS_ON', 'bool'); + /* CABIN PRESSURE */ this.excessPressure.set(SimVar.GetSimVarValue('L:A32NX_PRESS_EXCESS_CAB_ALT', 'bool')); this.cabAltSetResetState1.set( this.cabAltSetReset1.write(pressureAltitude > 10000 && this.excessPressure.get(), this.excessPressure.get() && [3, 10].includes(this.fwcFlightPhase.get())), @@ -2131,6 +2141,16 @@ export class PseudoFWC { sysPage: -1, side: 'LEFT', }, + 2100140: { // L+R CAB FAN FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: MappedSubject.create(([cabFanHasFault1, cabFanHasFault2]) => cabFanHasFault1 && cabFanHasFault2, this.cabFanHasFault1, this.cabFanHasFault2), + whichCodeToReturn: () => [0, 1], + codesToReturn: ['210014001', '210014002'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, 2131221: { // EXCESS CAB ALT flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], simVarIsActive: MappedSubject.create(([aircraftOnGround, excessPressure]) => !aircraftOnGround && excessPressure, this.aircraftOnGround, this.excessPressure), diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index b304aa5a2e8..83d40a7e997 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -28,6 +28,9 @@ export const CondPage = () => { const [hotAirOpen] = useSimVar('L:A32NX_HOT_AIR_VALVE_IS_OPEN', 'bool', 1000); const [hotAirEnabled] = useSimVar('L:A32NX_HOT_AIR_VALVE_IS_ENABLED', 'bool', 1000); + const [cabFanHasFault1] = useSimVar('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool', 1000); + const [cabFanHasFault2] = useSimVar('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool', 1000); + return ( {/* Title and unit */} @@ -36,11 +39,9 @@ export const CondPage = () => { TEMP : °C - { /* Not yet implemented - FAN - FAN - ALTN MODE - */} + FAN + FAN + {/* ALTN MODE */} {/* Plane shape */} diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index b2477091763..c91782933bf 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -264,7 +264,10 @@ impl A320AirConditioningSystem { ElectricalBusType::AlternatingCurrent(2), ], ), - cabin_fans: [CabinFan::new(ElectricalBusType::AlternatingCurrent(1)); 2], + cabin_fans: [ + CabinFan::new(context, 1, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(2)), + ], mixer_unit: MixerUnit::new(cabin_zones), packs: [AirConditioningPack::new(), AirConditioningPack::new()], trim_air_system: TrimAirSystem::new(context, cabin_zones), diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index c9cbaa75f82..6af8ba0a1a4 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -61,6 +61,8 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { ])? .with_auxiliary_power_unit(Variable::named("OVHD_APU_START_PB_IS_AVAILABLE"), 8)? .with_failures(vec![ + (21_000, FailureType::CabinFan(1)), + (21_001, FailureType::CabinFan(2)), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), (24_002, FailureType::TransformerRectifier(3)), diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index 762e134949c..2666fcb2298 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -345,7 +345,10 @@ impl A380AirConditioningSystem { ], ), ], - cabin_fans: [CabinFan::new(ElectricalBusType::AlternatingCurrent(1)); 2], + cabin_fans: [ + CabinFan::new(context, 1, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(1)), + ], mixer_unit: MixerUnit::new(cabin_zones), packs: [AirConditioningPack::new(), AirConditioningPack::new()], trim_air_system: TrimAirSystem::new(context, cabin_zones), diff --git a/fbw-common/src/systems/shared/src/ata.ts b/fbw-common/src/systems/shared/src/ata.ts index 6416b1873f4..623e7624430 100644 --- a/fbw-common/src/systems/shared/src/ata.ts +++ b/fbw-common/src/systems/shared/src/ata.ts @@ -93,6 +93,7 @@ export const AtaChaptersTitle = { }; export const AtaChaptersDescription = Object.freeze({ + 21: 'The air conditioning system regulates the temperature, air flow and pressure inside the aircraft. Its main function is to supply a high level of comfort to the passengers and crew and protect the aircraft systems like the avionics.', 22: 'The Autoflight systems are responsible for a multitude of functions, including but not limited to: Flight Guidance (AP, FD, A/THR), Flight Management, Flight Augmentation (yaw damper, etc.), and Flight Envelope (Speed scale, Alpha floor, etc.).', 24: 'All things related to the electrical system. The electrical system supplies power from the engines, APU, batteries, or emergency generator to all cockpit instruments.', 27: 'The flight controls contain the various systems used to control the aircraft in flight, such as control surfaces, but also flight control computers. Failure of these systems may lead to loss of control over the aircraft, and/or loss of information about the status of the flight controls.', diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 84ba3312af4..dbbe96283ba 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -1886,7 +1886,10 @@ mod acs_controller_tests { acs_overhead: TestAcsOverhead::new(context), adirs: TestAdirs::new(), air_conditioning_system: TestAirConditioningSystem::new(), - cabin_fans: [CabinFan::new(ElectricalBusType::AlternatingCurrent(1)); 2], + cabin_fans: [ + CabinFan::new(context, 1, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(2)), + ], engine_1: TestEngine::new(Ratio::default()), engine_2: TestEngine::new(Ratio::default()), engine_fire_push_buttons: TestEngineFirePushButtons::new(), @@ -2394,6 +2397,10 @@ mod acs_controller_tests { self.read_by_name("OVHD_COND_PACK_2_PB_HAS_FAULT") } + fn cabin_fan_has_fault(&mut self, fan_id: usize) -> bool { + self.read_by_name(format!("VENT_CABIN_FAN_{}_HAS_FAULT", fan_id).as_str()) + } + fn trim_air_system_controller_is_enabled(&self) -> bool { self.query(|a| { a.acsc.trim_air_system_controller.is_enabled() @@ -3404,6 +3411,8 @@ mod acs_controller_tests { } mod mixer_unit_tests { + use crate::failures::FailureType; + use super::*; #[test] @@ -3587,13 +3596,44 @@ mod acs_controller_tests { > MassRate::new::(0.1) ); - test_bed = test_bed.unpowered_ac_1_bus().iterate(50); + test_bed = test_bed + .unpowered_ac_1_bus() + .unpowered_ac_2_bus() + .iterate(50); assert!( (test_bed.mixer_unit_outlet_air().flow_rate() - test_bed.pack_flow()) < MassRate::new::(0.1) ) } + + #[test] + fn cabin_fans_dont_work_with_fault() { + let mut test_bed = test_bed() + .with() + .cab_fans_pb_on(true) + .and() + .both_packs_on() + .and() + .engine_idle() + .iterate(50); + + assert!( + (test_bed.mixer_unit_outlet_air().flow_rate() - test_bed.pack_flow()) + > MassRate::new::(0.1) + ); + + test_bed.fail(FailureType::CabinFan(1)); + test_bed.fail(FailureType::CabinFan(2)); + test_bed = test_bed.iterate(50); + + assert!( + (test_bed.mixer_unit_outlet_air().flow_rate() - test_bed.pack_flow()) + < MassRate::new::(0.1) + ); + assert!(test_bed.cabin_fan_has_fault(1)); + assert!(test_bed.cabin_fan_has_fault(2)); + } } mod trim_air_tests { diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index ba3a4ba1389..6e66b181955 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -3,6 +3,7 @@ use self::acs_controller::{ }; use crate::{ + failures::{Failure, FailureType}, pneumatic::{ valve::{DefaultValve, PneumaticExhaust}, ControllablePneumaticValve, PneumaticContainer, PneumaticPipe, PneumaticValveSignal, @@ -224,14 +225,16 @@ pub trait PressurizationConstants { const LOW_DIFFERENTIAL_PRESSURE_WARNING: f64; } -#[derive(Clone, Copy)] /// A320neo fan part number: VD3900-03 pub struct CabinFan { + cabin_fan_fault_id: VariableIdentifier, + is_on: bool, outlet_air: Air, is_powered: bool, powered_by: ElectricalBusType, + failure: Failure, } impl CabinFan { @@ -239,13 +242,16 @@ impl CabinFan { const PRESSURE_RISE_HPA: f64 = 22.; // hPa const FAN_EFFICIENCY: f64 = 0.75; // Ratio - so output matches AMM numbers - pub fn new(powered_by: ElectricalBusType) -> Self { + pub fn new(context: &mut InitContext, id: u8, powered_by: ElectricalBusType) -> Self { Self { + cabin_fan_fault_id: context.get_identifier(format!("VENT_CABIN_FAN_{}_HAS_FAULT", id)), + is_on: false, outlet_air: Air::new(), is_powered: false, powered_by, + failure: Failure::new(FailureType::CabinFan(id as usize)), } } @@ -259,7 +265,10 @@ impl CabinFan { self.outlet_air .set_temperature(cabin_simulation.cabin_temperature().iter().average()); - if !self.is_powered || !matches!(controller.signal(), Some(CabinFansSignal::On)) { + if !self.is_powered + || self.failure.is_active() + || !matches!(controller.signal(), Some(CabinFansSignal::On)) + { self.is_on = false; self.outlet_air .set_pressure(cabin_simulation.cabin_pressure()); @@ -290,6 +299,15 @@ impl OutletAir for CabinFan { } impl SimulationElement for CabinFan { + fn accept(&mut self, visitor: &mut T) { + self.failure.accept(visitor); + visitor.visit(self); + } + + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.cabin_fan_fault_id, self.failure.is_active()); + } + fn receive_power(&mut self, buses: &impl ElectricalBuses) { self.is_powered = buses.is_powered(self.powered_by); } diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index 529a9c5d44f..bc1f1c0c2a5 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -6,6 +6,7 @@ use crate::simulation::SimulationElement; #[derive(Clone, Copy, PartialEq, Eq)] pub enum FailureType { + CabinFan(usize), TransformerRectifier(usize), ReservoirLeak(HydraulicColor), ReservoirAirLeak(HydraulicColor), From d350743058de0134622e764652ad0cdb77f08117 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Fri, 30 Jun 2023 18:39:04 +0100 Subject: [PATCH 02/18] feat: hot air failure --- fbw-a32nx/docs/a320-simvars.md | 2 +- fbw-a32nx/src/systems/failures/src/a320.ts | 1 + .../instruments/src/Common/EWDMessages.tsx | 5 ++ .../EFB/failures-orchestrator-provider.tsx | 1 + .../systems/instruments/src/EWD/PseudoFWC.ts | 26 ++++++- .../instruments/src/SD/Pages/Cond/Cond.tsx | 7 +- .../wasm/systems/a320_systems_wasm/src/lib.rs | 1 + .../src/air_conditioning/acs_controller.rs | 68 ++++++++++++++----- .../wasm/systems/systems/src/failures/mod.rs | 1 + 9 files changed, 87 insertions(+), 25 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index 9f414457287..cbd1c05d434 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2718,7 +2718,7 @@ In the variables below, {number} should be replaced with one item in the set: { - A32NX_VENT_CABIN_FAN_{id}_HAS_FAULT - Bool - - True if the corresponding cabin fan is on and operating normally + - True if the corresponding cabin fan is faulted and not operating - {number} - 1 - 2 diff --git a/fbw-a32nx/src/systems/failures/src/a320.ts b/fbw-a32nx/src/systems/failures/src/a320.ts index 3c63256e104..c3e1b5bc947 100644 --- a/fbw-a32nx/src/systems/failures/src/a320.ts +++ b/fbw-a32nx/src/systems/failures/src/a320.ts @@ -3,6 +3,7 @@ export const A320Failure = Object.freeze({ CabinFan1Failure: 21000, CabinFan2Failure: 21001, + HotAir: 21002, Fac1Failure: 22000, Fac2Failure: 22001, diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index d4d3c863a21..cfbfd79b3c2 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -108,6 +108,11 @@ const EWDMessages = { '000056701': '\x1b<3mVHF3 VOICE', '210014001': '\x1b<4m\x1b4mCOND\x1bm L+R CAB FAN FAULT', '210014002': '\x1b<5m -PACK FLOW...........HI', + '210015001': '\x1b<4m\x1b4mCOND\x1bm HOT AIR FAULT', + '210015002': '\x1b<5m -HOT AIR............OFF', + '210015003': '\x1b<7m .IF HOT AIR STILL OPEN:', + '210015004': '\x1b<5m -PACK 1.............OFF', + '210015005': '\x1b<5m -PACK 2.............OFF', '213122101': '\x1b<2m\x1b4mCAB PR\x1bm EXCESS CAB ALT', '213122102': '\x1b<5m -CREW OXY MASKS.....USE', '213122103': '\x1b<5m -SIGNS...............ON', diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index 5a25646389e..f69562879a8 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -17,6 +17,7 @@ interface FailuresOrchestratorContext { const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ [21, A320Failure.CabinFan1Failure, 'Cabin Fan 1'], [21, A320Failure.CabinFan2Failure, 'Cabin Fan 2'], + [21, A320Failure.HotAir, 'Trim Air Pressure Regulating Valve'], [22, A320Failure.Fac1Failure, 'FAC 1'], [22, A320Failure.Fac2Failure, 'FAC 2'], diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index 0af5c4a6806..cdf5936e1bb 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -110,6 +110,12 @@ export class PseudoFWC { private readonly excessPressure = Subject.create(false); + private readonly hotAirEnabled = Subject.create(false); + + private readonly hotAirOpen = Subject.create(false); + + private readonly hotAirPbOn = Subject.create(false); + private readonly packOffBleedAvailable1 = new NXLogicConfirmNode(5, false); private readonly packOffBleedAvailable2 = new NXLogicConfirmNode(5, false); @@ -1029,11 +1035,13 @@ export class PseudoFWC { /* 21 - AIR CONDITIONING AND PRESSURIZATION */ - /* CABIN FANS */ this.cabFanHasFault1.set(SimVar.GetSimVarValue('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool')); this.cabFanHasFault2.set(SimVar.GetSimVarValue('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool')); - /* BLEED AND PACKS */ + this.hotAirEnabled.set(SimVar.GetSimVarValue('L:A32NX_HOT_AIR_VALVE_IS_ENABLED', 'bool')); + this.hotAirOpen.set(SimVar.GetSimVarValue('L:A32NX_HOT_AIR_VALVE_IS_OPEN', 'bool')); + this.hotAirPbOn.set(SimVar.GetSimVarValue('L:A32NX_OVHD_COND_HOT_AIR_PB_IS_ON', 'bool')); + const crossfeed = SimVar.GetSimVarValue('L:A32NX_PNEU_XBLEED_VALVE_OPEN', 'bool'); const eng1Bleed = SimVar.GetSimVarValue('A:BLEED AIR ENGINE:1', 'bool'); const eng1BleedPbFault = SimVar.GetSimVarValue('L:A32NX_OVHD_PNEU_ENG_1_BLEED_PB_HAS_FAULT', 'bool'); @@ -1044,7 +1052,6 @@ export class PseudoFWC { const pack1On = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_1_PB_IS_ON', 'bool'); const pack2On = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_2_PB_IS_ON', 'bool'); - /* CABIN PRESSURE */ this.excessPressure.set(SimVar.GetSimVarValue('L:A32NX_PRESS_EXCESS_CAB_ALT', 'bool')); this.cabAltSetResetState1.set( this.cabAltSetReset1.write(pressureAltitude > 10000 && this.excessPressure.get(), this.excessPressure.get() && [3, 10].includes(this.fwcFlightPhase.get())), @@ -2151,6 +2158,19 @@ export class PseudoFWC { sysPage: 7, side: 'LEFT', }, + 2100150: { // HOT AIR FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: MappedSubject.create(([hotAirEnabled, hotAirOpen]) => hotAirEnabled !== hotAirOpen, this.hotAirEnabled, this.hotAirOpen), + whichCodeToReturn: () => [0, + this.hotAirPbOn.get() ? 1 : null, + this.hotAirPbOn.get() ? 2 : null, + 3, 4, 5], // Fixme: Add check, 2-5 only returned if duct overheat + codesToReturn: ['210015001', '210015002', '210015003', '210015004', '210015005'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, 2131221: { // EXCESS CAB ALT flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], simVarIsActive: MappedSubject.create(([aircraftOnGround, excessPressure]) => !aircraftOnGround && excessPressure, this.aircraftOnGround, this.excessPressure), diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 83d40a7e997..f67c04b6326 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -27,6 +27,7 @@ export const CondPage = () => { const [hotAirOpen] = useSimVar('L:A32NX_HOT_AIR_VALVE_IS_OPEN', 'bool', 1000); const [hotAirEnabled] = useSimVar('L:A32NX_HOT_AIR_VALVE_IS_ENABLED', 'bool', 1000); + const [hotAirPb] = useSimVar('L:A32NX_OVHD_COND_HOT_AIR_PB_IS_ON', 'bool', 1000); const [cabFanHasFault1] = useSimVar('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool', 1000); const [cabFanHasFault2] = useSimVar('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool', 1000); @@ -64,9 +65,9 @@ export const CondPage = () => { HOT AIR - - - + + + ); diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index 6af8ba0a1a4..20e276d9398 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -63,6 +63,7 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { .with_failures(vec![ (21_000, FailureType::CabinFan(1)), (21_001, FailureType::CabinFan(2)), + (21_002, FailureType::HotAir(1)), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), (24_002, FailureType::TransformerRectifier(3)), diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index dbbe96283ba..67554479dff 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -1,4 +1,5 @@ use crate::{ + failures::{Failure, FailureType}, pneumatic::{EngineModeSelector, EngineState, PneumaticValveSignal}, shared::{ pid::PidController, CabinAltitude, CabinSimulation, ControllerSignal, ElectricalBusType, @@ -891,13 +892,13 @@ impl SimulationElement for PackFlowController { } } -#[derive(Clone, Copy)] struct TrimAirSystemController { hot_air_is_enabled_id: VariableIdentifier, hot_air_is_open_id: VariableIdentifier, is_enabled: bool, is_open: bool, + failure: Failure, trim_air_valve_controllers: [TrimAirValveController; ZONES], } @@ -910,6 +911,7 @@ impl TrimAirSystemController TrimAirSystemController, ) { - self.is_enabled = self - .trim_air_pressure_regulating_valve_status_determination(acs_overhead, operation_mode); - - self.is_open = self.trim_air_pressure_regulating_valve_is_open_determination( + // When there is a failure the overhead button (or any other condition) + // will not open or close the trim air pressure regulating valve + // If the valve was open before the failure, it will stay open + self.is_enabled = self.trim_air_pressure_regulating_valve_status_determination( + acs_overhead, + operation_mode, pack_flow_controller, pneumatic, - ) && self.is_enabled; + ); + + // When a failure is active the TAPRV will be unresponsive - it will stay in the same state as it was + if !self.failure.is_active() { + self.is_open = self.is_enabled; + } for (id, tav_controller) in self.trim_air_valve_controllers.iter_mut().enumerate() { tav_controller.update( context, - self.is_enabled && self.is_open, + self.is_open, trim_air_system.duct_temperature()[id], duct_demand_temperature[id], ) @@ -945,20 +954,16 @@ impl TrimAirSystemController bool { - // TODO: Add overheat protection - // TODO: If more than one TAV fails, the system should be off - acs_overhead.hot_air_pushbutton_is_on() && operation_mode == ACSCActiveComputer::Primary - } - - fn trim_air_pressure_regulating_valve_is_open_determination( - &self, pack_flow_controller: &[PackFlowController; 2], pneumatic: &impl PackFlowValveState, ) -> bool { - !pack_flow_controller - .iter() - .any(|pack| pack.pack_start_condition_determination(pneumatic)) + // TODO: Add overheat protection + // TODO: If more than one TAV fails, the system should be off + acs_overhead.hot_air_pushbutton_is_on() + && operation_mode == ACSCActiveComputer::Primary + && !pack_flow_controller + .iter() + .any(|pack| pack.pack_start_condition_determination(pneumatic)) && ((pneumatic.pack_flow_valve_is_open(1)) || (pneumatic.pack_flow_valve_is_open(2))) } @@ -982,6 +987,11 @@ impl SimulationElement writer.write(&self.hot_air_is_enabled_id, self.is_enabled()); writer.write(&self.hot_air_is_open_id, self.is_open()); } + + fn accept(&mut self, visitor: &mut T) { + self.failure.accept(visitor); + visitor.visit(self); + } } pub struct TrimAirValveSignal { @@ -3681,6 +3691,28 @@ mod acs_controller_tests { ); } + #[test] + fn trim_air_pressure_regulating_valve_is_unresponsive_when_failed() { + let mut test_bed = test_bed() + .with() + .hot_air_pb_on(true) + .and() + .engine_idle() + .command_fwd_selected_temperature(ThermodynamicTemperature::new::( + 30., + )) + .iterate(400); + + test_bed.fail(FailureType::HotAir(1)); + test_bed = test_bed.hot_air_pb_on(false).iterate(100); + + assert!((test_bed.trim_air_system_outlet_air(1).flow_rate()) > MassRate::default()); + assert!( + (test_bed.trim_air_system_outlet_air(1).temperature()) + > ThermodynamicTemperature::new::(25.) + ); + } + #[test] fn trim_valves_close_if_selected_temp_below_measured() { let mut test_bed = test_bed() diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index bc1f1c0c2a5..d4106ce1c0f 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -7,6 +7,7 @@ use crate::simulation::SimulationElement; #[derive(Clone, Copy, PartialEq, Eq)] pub enum FailureType { CabinFan(usize), + HotAir(usize), TransformerRectifier(usize), ReservoirLeak(HydraulicColor), ReservoirAirLeak(HydraulicColor), From e9e96eae5837643e6b312c89dd732ea6dee40eac Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sun, 16 Jul 2023 10:50:28 +0100 Subject: [PATCH 03/18] feat: trim valves and duct overheat failures --- fbw-a32nx/docs/a320-simvars.md | 16 ++ fbw-a32nx/src/systems/failures/src/a320.ts | 6 + .../instruments/src/Common/EWDMessages.tsx | 30 ++- .../EFB/failures-orchestrator-provider.tsx | 6 + .../systems/instruments/src/EWD/PseudoFWC.ts | 129 ++++++++++--- .../instruments/src/SD/Pages/Cond/Cond.tsx | 41 +++- .../a320_systems/src/air_conditioning.rs | 14 +- .../wasm/systems/a320_systems_wasm/src/lib.rs | 7 + .../a380_systems/src/air_conditioning/mod.rs | 8 +- .../src/air_conditioning/acs_controller.rs | 179 +++++++++++++++++- .../systems/src/air_conditioning/mod.rs | 119 +++++++++--- .../wasm/systems/systems/src/failures/mod.rs | 3 + 12 files changed, 485 insertions(+), 73 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index cbd1c05d434..a8c316e378f 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2594,6 +2594,14 @@ In the variables below, {number} should be replaced with one item in the set: { - FWD - AFT +- A32NX_COND_{id}_DUCT_OVHT + - Bool + - True when the duct temperature of the respective zone rises above 88 deg C + - {id} + - CKPT + - FWD + - AFT + - A32NX_COND_PACK_FLOW_VALVE_{index}_IS_OPEN - Bool - True if the respective {1 or 2} pack flow valve is open @@ -2610,6 +2618,14 @@ In the variables below, {number} should be replaced with one item in the set: { - FWD - AFT +- A32NX_COND_{id}_TRIM_AIR_VALVE_HAS_FAULT + - Bool + - The respective trim air valve is faulted + - {id} + - CKPT + - FWD + - AFT + - A32NX_HOT_AIR_VALVE_IS_ENABLED - Bool - True if the trim air system is enabled (pushbutton in auto and power supplied to system) diff --git a/fbw-a32nx/src/systems/failures/src/a320.ts b/fbw-a32nx/src/systems/failures/src/a320.ts index c3e1b5bc947..8f1785bb495 100644 --- a/fbw-a32nx/src/systems/failures/src/a320.ts +++ b/fbw-a32nx/src/systems/failures/src/a320.ts @@ -4,6 +4,12 @@ export const A320Failure = Object.freeze({ CabinFan1Failure: 21000, CabinFan2Failure: 21001, HotAir: 21002, + CkptTrimAirFailure: 21003, + FwdTrimAirFailure: 21004, + AftTrimAirFailure: 21005, + CkptDuctOvht: 21006, + FwdDuctOvht: 21007, + AftDuctOvht: 21008, Fac1Failure: 22000, Fac2Failure: 22001, diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index cfbfd79b3c2..d04afec5994 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -106,13 +106,6 @@ const EWDMessages = { '000056001': '\x1b<3mHI ALT SET', '000068001': '\x1b<3mADIRS SWTG', '000056701': '\x1b<3mVHF3 VOICE', - '210014001': '\x1b<4m\x1b4mCOND\x1bm L+R CAB FAN FAULT', - '210014002': '\x1b<5m -PACK FLOW...........HI', - '210015001': '\x1b<4m\x1b4mCOND\x1bm HOT AIR FAULT', - '210015002': '\x1b<5m -HOT AIR............OFF', - '210015003': '\x1b<7m .IF HOT AIR STILL OPEN:', - '210015004': '\x1b<5m -PACK 1.............OFF', - '210015005': '\x1b<5m -PACK 2.............OFF', '213122101': '\x1b<2m\x1b4mCAB PR\x1bm EXCESS CAB ALT', '213122102': '\x1b<5m -CREW OXY MASKS.....USE', '213122103': '\x1b<5m -SIGNS...............ON', @@ -131,6 +124,29 @@ const EWDMessages = { '213122116': '\x1b<5m -PAX OXY MASKS...MAN ON', '216120701': '\x1b<4m\x1b4mAIR\x1bm PACK 1 OFF', '216120801': '\x1b<4m\x1b4mAIR\x1bm PACK 2 OFF', + '216321001': '\x1b<4m\x1b4mCOND\x1bm CKPT DUCT OVHT', + '216321002': '\x1b<7m .WHEN DUCT TEMP<70 DEG C:', + '216321003': '\x1b<7m .WHEN DUCT TEMP<158 DEG F:', + '216321004': '\x1b<5m -HOT AIR....OFF THEN ON', + '216321101': '\x1b<4m\x1b4mCOND\x1bm FWD CAB DUCT OVHT', + '216321102': '\x1b<7m .WHEN DUCT TEMP<70 DEG C:', + '216321103': '\x1b<7m .WHEN DUCT TEMP<158 DEG F:', + '216321104': '\x1b<5m -HOT AIR....OFF THEN ON', + '216321201': '\x1b<4m\x1b4mCOND\x1bm AFT CAB DUCT OVHT', + '216321202': '\x1b<7m .WHEN DUCT TEMP<70 DEG C:', + '216321203': '\x1b<7m .WHEN DUCT TEMP<158 DEG F:', + '216321204': '\x1b<5m -HOT AIR....OFF THEN ON', + '216321801': '\x1b<4m\x1b4mCOND\x1bm L+R CAB FAN FAULT', + '216321802': '\x1b<5m -PACK FLOW...........HI', + '216329001': '\x1b<4m\x1b4mCOND\x1bm HOT AIR FAULT', + '216329002': '\x1b<5m -HOT AIR............OFF', + '216329003': '\x1b<7m .IF HOT AIR STILL OPEN:', + '216329004': '\x1b<5m -PACK 1.............OFF', + '216329005': '\x1b<5m -PACK 2.............OFF', + '216330501': '\x1b<4m\x1b4mCOND\x1bm TRIM AIR SYS FAULT', + '216330502': '\x1b<4m -CPKT TRIM VALVE', + '216330503': '\x1b<4m -FWD CAB TRIM VALVE', + '216330504': '\x1b<4m -AFT CAB TRIM VALVE', '221070001': '\x1b<4m\x1b4mT.O\x1bm SPEEDS TOO LOW', '221070002': '\x1b<5m -TOW AND T.O DATA.CHECK', '221071001': '\x1b<4m\x1b4mT.O\x1bm V1/VR/V2 DISAGREE', diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index f69562879a8..a010aa2edb5 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -18,6 +18,12 @@ const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ [21, A320Failure.CabinFan1Failure, 'Cabin Fan 1'], [21, A320Failure.CabinFan2Failure, 'Cabin Fan 2'], [21, A320Failure.HotAir, 'Trim Air Pressure Regulating Valve'], + [21, A320Failure.CkptTrimAirFailure, 'Cockpit Trim Air Valve'], + [21, A320Failure.FwdTrimAirFailure, 'Forward Zone Trim Air Valve'], + [21, A320Failure.AftTrimAirFailure, 'Aft Zone Trim Air Valve'], + [21, A320Failure.CkptDuctOvht, 'Cockpit Duct Overheat'], + [21, A320Failure.FwdDuctOvht, 'Forward Zone Duct Overheat'], + [21, A320Failure.AftDuctOvht, 'Aft Zone Duct Overheat'], [22, A320Failure.Fac1Failure, 'FAC 1'], [22, A320Failure.Fac2Failure, 'FAC 2'], diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index cdf5936e1bb..f1c88228b66 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -116,6 +116,24 @@ export class PseudoFWC { private readonly hotAirPbOn = Subject.create(false); + private readonly ckptTrimFault = Subject.create(false); + + private readonly fwdTrimFault = Subject.create(false); + + private readonly aftTrimFault = Subject.create(false); + + private readonly ckptDuctOvht = Subject.create(false); + + private readonly fwdDuctOvht = Subject.create(false); + + private readonly aftDuctOvht = Subject.create(false); + + private readonly anyDuctOvht = Subject.create(false); + + private readonly pack1On = Subject.create(false); + + private readonly pack2On = Subject.create(false); + private readonly packOffBleedAvailable1 = new NXLogicConfirmNode(5, false); private readonly packOffBleedAvailable2 = new NXLogicConfirmNode(5, false); @@ -1042,6 +1060,15 @@ export class PseudoFWC { this.hotAirOpen.set(SimVar.GetSimVarValue('L:A32NX_HOT_AIR_VALVE_IS_OPEN', 'bool')); this.hotAirPbOn.set(SimVar.GetSimVarValue('L:A32NX_OVHD_COND_HOT_AIR_PB_IS_ON', 'bool')); + this.ckptTrimFault.set(SimVar.GetSimVarValue('L:A32NX_COND_CKPT_TRIM_AIR_VALVE_HAS_FAULT', 'bool')); + this.fwdTrimFault.set(SimVar.GetSimVarValue('L:A32NX_COND_FWD_TRIM_AIR_VALVE_HAS_FAULT', 'bool')); + this.aftTrimFault.set(SimVar.GetSimVarValue('L:A32NX_COND_AFT_TRIM_AIR_VALVE_HAS_FAULT', 'bool')); + + this.ckptDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_CKPT_DUCT_OVHT', 'bool')); + this.fwdDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_FWD_DUCT_OVHT', 'bool')); + this.aftDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_AFT_DUCT_OVHT', 'bool')); + this.anyDuctOvht.set(this.ckptDuctOvht.get() || this.fwdDuctOvht.get() || this.aftDuctOvht.get()); + const crossfeed = SimVar.GetSimVarValue('L:A32NX_PNEU_XBLEED_VALVE_OPEN', 'bool'); const eng1Bleed = SimVar.GetSimVarValue('A:BLEED AIR ENGINE:1', 'bool'); const eng1BleedPbFault = SimVar.GetSimVarValue('L:A32NX_OVHD_PNEU_ENG_1_BLEED_PB_HAS_FAULT', 'bool'); @@ -1049,8 +1076,8 @@ export class PseudoFWC { const eng2BleedPbFault = SimVar.GetSimVarValue('L:A32NX_OVHD_PNEU_ENG_2_BLEED_PB_HAS_FAULT', 'bool'); const pack1Fault = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_1_PB_HAS_FAULT', 'bool'); const pack2Fault = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_2_PB_HAS_FAULT', 'bool'); - const pack1On = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_1_PB_IS_ON', 'bool'); - const pack2On = SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_2_PB_IS_ON', 'bool'); + this.pack1On.set(SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_1_PB_IS_ON', 'bool')); + this.pack2On.set(SimVar.GetSimVarValue('L:A32NX_OVHD_COND_PACK_2_PB_IS_ON', 'bool')); this.excessPressure.set(SimVar.GetSimVarValue('L:A32NX_PRESS_EXCESS_CAB_ALT', 'bool')); this.cabAltSetResetState1.set( @@ -1061,8 +1088,8 @@ export class PseudoFWC { ); this.packOffBleedAvailable1.write((eng1Bleed === 1 && !eng1BleedPbFault) || crossfeed === 1, deltaTime); this.packOffBleedAvailable2.write((eng2Bleed === 1 && !eng2BleedPbFault) || crossfeed === 1, deltaTime); - this.packOffNotFailed1Status.set(this.packOffNotFailed1.write(!pack1On && !pack1Fault && this.packOffBleedAvailable1.read() && this.fwcFlightPhase.get() === 6, deltaTime)); - this.packOffNotFailed2Status.set(this.packOffNotFailed2.write(!pack2On && !pack2Fault && this.packOffBleedAvailable2.read() && this.fwcFlightPhase.get() === 6, deltaTime)); + this.packOffNotFailed1Status.set(this.packOffNotFailed1.write(!this.pack1On.get() && !pack1Fault && this.packOffBleedAvailable1.read() && this.fwcFlightPhase.get() === 6, deltaTime)); + this.packOffNotFailed2Status.set(this.packOffNotFailed2.write(!this.pack2On.get() && !pack2Fault && this.packOffBleedAvailable2.read() && this.fwcFlightPhase.get() === 6, deltaTime)); /* OTHER STUFF */ @@ -2148,29 +2175,6 @@ export class PseudoFWC { sysPage: -1, side: 'LEFT', }, - 2100140: { // L+R CAB FAN FAULT - flightPhaseInhib: [3, 4, 5, 7, 8], - simVarIsActive: MappedSubject.create(([cabFanHasFault1, cabFanHasFault2]) => cabFanHasFault1 && cabFanHasFault2, this.cabFanHasFault1, this.cabFanHasFault2), - whichCodeToReturn: () => [0, 1], - codesToReturn: ['210014001', '210014002'], - memoInhibit: () => false, - failure: 2, - sysPage: 7, - side: 'LEFT', - }, - 2100150: { // HOT AIR FAULT - flightPhaseInhib: [3, 4, 5, 7, 8], - simVarIsActive: MappedSubject.create(([hotAirEnabled, hotAirOpen]) => hotAirEnabled !== hotAirOpen, this.hotAirEnabled, this.hotAirOpen), - whichCodeToReturn: () => [0, - this.hotAirPbOn.get() ? 1 : null, - this.hotAirPbOn.get() ? 2 : null, - 3, 4, 5], // Fixme: Add check, 2-5 only returned if duct overheat - codesToReturn: ['210015001', '210015002', '210015003', '210015004', '210015005'], - memoInhibit: () => false, - failure: 2, - sysPage: 7, - side: 'LEFT', - }, 2131221: { // EXCESS CAB ALT flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], simVarIsActive: MappedSubject.create(([aircraftOnGround, excessPressure]) => !aircraftOnGround && excessPressure, this.aircraftOnGround, this.excessPressure), @@ -2201,6 +2205,77 @@ export class PseudoFWC { sysPage: 2, side: 'LEFT', }, + 2163210: { // CKPT DUCT OVHT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.ckptDuctOvht, + whichCodeToReturn: () => [0, 1, null, 3], // TODO: Add support for Fahrenheit + codesToReturn: ['216321001', '216321002', '216321003', '216321004'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, + 2163211: { // FWD DUCT OVHT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.fwdDuctOvht, + whichCodeToReturn: () => [0, 1, null, 3], // TODO: Add support for Fahrenheit + codesToReturn: ['216321101', '216321102', '216321103', '216321104'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, + 2163212: { // AFT DUCT OVHT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.aftDuctOvht, + whichCodeToReturn: () => [0, 1, null, 3], // TODO: Add support for Fahrenheit + codesToReturn: ['216321201', '216321202', '216321203', '216321204'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, + 2163218: { // L+R CAB FAN FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: MappedSubject.create(([cabFanHasFault1, cabFanHasFault2]) => cabFanHasFault1 && cabFanHasFault2, this.cabFanHasFault1, this.cabFanHasFault2), + whichCodeToReturn: () => [0, 1], + codesToReturn: ['216321801', '216321802'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, + 2163290: { // HOT AIR FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: MappedSubject.create(([hotAirEnabled, hotAirOpen]) => hotAirEnabled !== hotAirOpen, this.hotAirEnabled, this.hotAirOpen), + whichCodeToReturn: () => [0, + this.hotAirPbOn.get() ? 1 : null, + (this.anyDuctOvht.get() && this.hotAirPbOn.get()) ? 2 : null, + (this.anyDuctOvht.get() && this.pack1On.get()) ? 3 : null, + (this.anyDuctOvht.get() && this.pack2On.get()) ? 4 : null, + ], + codesToReturn: ['216329001', '216329002', '216329003', '216329004', '216329005'], + memoInhibit: () => false, + failure: 2, + sysPage: 7, + side: 'LEFT', + }, + 216330: { // TRIM AIR SYS FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: MappedSubject.create( + ([ckptTrimFault, fwdTrimFault, aftTrimFault]) => ckptTrimFault || fwdTrimFault || aftTrimFault, this.ckptTrimFault, this.fwdTrimFault, this.aftTrimFault, + ), + whichCodeToReturn: () => [0, + this.ckptTrimFault.get() ? 1 : null, + this.fwdTrimFault.get() ? 2 : null, + this.aftTrimFault.get() ? 3 : null, + ], + codesToReturn: ['216330501', '216330502', '216330503', '216330504'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, 2600150: { // SMOKE FWD CARGO SMOKE flightPhaseInhib: [4, 5, 7, 8], simVarIsActive: this.cargoFireTest, diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index f67c04b6326..6cd8fc7de87 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -51,13 +51,40 @@ export const CondPage = () => { {/* Cockpit */} - + {/* Fwd */} - + {/* Aft */} - + {/* Valve and tubes */} @@ -81,24 +108,26 @@ type CondUnitProps = { x: number, y: number, offset: number, - hotAir: number + hotAir: boolean } const CondUnit = ({ title, trimAirValve, cabinTemp, trimTemp, x, y, offset, hotAir } : CondUnitProps) => { const rotateTemp = offset + (trimAirValve * 86 / 100); + const overheat = trimTemp > 80; return ( {title} {cabinTemp.toFixed(0)} - {trimTemp.toFixed(0)} + {trimTemp.toFixed(0)} C H - + {/* TODO: When Trim valves are failed the gauge should be replaced by amber XX */} + diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index c91782933bf..03e026e5174 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -323,11 +323,17 @@ impl A320AirConditioningSystem { } self.mixer_unit.update(mixer_intakes); - self.trim_air_system - .update(context, &self.mixer_unit, &self.acsc); + self.trim_air_system.update( + context, + self.acsc.trim_air_pressure_regulating_valve_is_open(), + &self.mixer_unit, + &self.acsc, + ); self.air_conditioning_overhead .set_pack_pushbutton_fault(self.pack_fault_determination(pneumatic)); + self.air_conditioning_overhead + .set_hot_air_pushbutton_fault(self.acsc.hot_air_pb_fault_light_determination()); } pub fn pack_fault_determination(&self, pneumatic: &impl PackFlowValveState) -> [bool; 2] { @@ -419,6 +425,10 @@ impl A320AirConditioningSystemOverhead { .enumerate() .for_each(|(index, pushbutton)| pushbutton.set_fault(pb_has_fault[index])); } + + fn set_hot_air_pushbutton_fault(&mut self, hot_air_pb_has_fault: bool) { + self.hot_air_pb.set_fault(hot_air_pb_has_fault); + } } impl AirConditioningOverheadShared diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index 20e276d9398..da43f18fc8c 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -24,6 +24,7 @@ use reversers::reversers; use rudder::rudder; use spoilers::spoilers; use std::error::Error; +use systems::air_conditioning::ZoneType; use systems::failures::FailureType; use systems::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, ElectricalBusType, GearActuatorId, @@ -64,6 +65,12 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { (21_000, FailureType::CabinFan(1)), (21_001, FailureType::CabinFan(2)), (21_002, FailureType::HotAir(1)), + (21_003, FailureType::TrimAirFault(ZoneType::Cockpit)), + (21_004, FailureType::TrimAirFault(ZoneType::Cabin(1))), + (21_005, FailureType::TrimAirFault(ZoneType::Cabin(2))), + (21_006, FailureType::TrimAirOverheat(ZoneType::Cockpit)), + (21_007, FailureType::TrimAirOverheat(ZoneType::Cabin(1))), + (21_008, FailureType::TrimAirOverheat(ZoneType::Cabin(2))), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), (24_002, FailureType::TransformerRectifier(3)), diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index 2666fcb2298..bf77a21b124 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -420,8 +420,12 @@ impl A380AirConditioningSystem { } self.mixer_unit.update(mixer_intakes); - self.trim_air_system - .update(context, &self.mixer_unit, &self.acsc); + self.trim_air_system.update( + context, + self.acsc.trim_air_pressure_regulating_valve_is_open(), + &self.mixer_unit, + &self.acsc, + ); self.air_conditioning_overhead .set_pack_pushbutton_fault(self.pack_fault_determination()); diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 67554479dff..706e03e5843 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -181,6 +181,14 @@ impl AirConditioningSystemController bool { + self.trim_air_system_controller.is_open() + } + + pub fn hot_air_pb_fault_light_determination(&self) -> bool { + self.trim_air_system_controller.should_turn_on_fault_light() + } } impl PackFlow @@ -899,6 +907,8 @@ struct TrimAirSystemController { is_enabled: bool, is_open: bool, failure: Failure, + overheat_event: bool, + previous_pb_state: bool, trim_air_valve_controllers: [TrimAirValveController; ZONES], } @@ -910,8 +920,10 @@ impl TrimAirSystemController TrimAirSystemController TrimAirSystemController; 2], pneumatic: &impl PackFlowValveState, ) -> bool { - // TODO: Add overheat protection - // TODO: If more than one TAV fails, the system should be off acs_overhead.hot_air_pushbutton_is_on() && operation_mode == ACSCActiveComputer::Primary && !pack_flow_controller .iter() .any(|pack| pack.pack_start_condition_determination(pneumatic)) && ((pneumatic.pack_flow_valve_is_open(1)) || (pneumatic.pack_flow_valve_is_open(2))) + && !self.overheat_event + && !any_tav_has_fault } fn trim_air_valve_controllers(&self, zone_id: usize) -> TrimAirValveController { @@ -978,6 +997,31 @@ impl TrimAirSystemController bool { self.is_open } + + fn hot_air_fault_determination( + &self, + acs_overhead: &impl AirConditioningOverheadShared, + duct_temperature: Vec, + ) -> bool { + if duct_temperature + .iter() + .any(|temp| *temp > ThermodynamicTemperature::new::(88.)) + { + true + } else if duct_temperature + .iter() + .all(|temp| *temp < ThermodynamicTemperature::new::(70.)) + && self.overheat_event + { + self.previous_pb_state || !acs_overhead.hot_air_pushbutton_is_on() + } else { + self.overheat_event + } + } + + fn should_turn_on_fault_light(&self) -> bool { + self.overheat_event + } } impl SimulationElement @@ -2078,8 +2122,12 @@ mod acs_controller_tests { } self.mixer_unit.update(mixer_intakes); - self.trim_air_system - .update(context, &self.mixer_unit, &self.acsc); + self.trim_air_system.update( + context, + self.acsc.trim_air_pressure_regulating_valve_is_open(), + &self.mixer_unit, + &self.acsc, + ); self.acs_overhead .set_pack_pushbutton_fault(self.acsc.pack_fault_determination(&self.pneumatic)); @@ -2098,6 +2146,7 @@ mod acs_controller_tests { self.cabin_air_simulation.accept(visitor); self.pneumatic.accept(visitor); self.pressurization_overhead.accept(visitor); + self.trim_air_system.accept(visitor); accept_iterable!(self.cabin_fans, visitor); visitor.visit(self); @@ -3735,6 +3784,126 @@ mod acs_controller_tests { ); } + #[test] + fn trim_air_valves_are_unresponsive_when_failed() { + let mut test_bed = test_bed() + .with() + .engine_idle() + .both_packs_on() + .and() + .command_fwd_selected_temperature(ThermodynamicTemperature::new::( + 30., + )); + + test_bed.command_measured_temperature( + [ThermodynamicTemperature::new::(15.); 2], + ); + + test_bed = test_bed.iterate(100); + + assert!((test_bed.trim_air_valves_open_amount()[1]) > Ratio::default()); + + let initial_open = test_bed.trim_air_valves_open_amount()[1]; + + test_bed = test_bed.command_fwd_selected_temperature(ThermodynamicTemperature::new::< + degree_celsius, + >(18.)); + + test_bed.fail(FailureType::TrimAirFault(ZoneType::Cabin(1))); + + test_bed.command_measured_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + + test_bed = test_bed.iterate(100); + + assert!((test_bed.trim_air_valves_open_amount()[1]) > Ratio::default()); + assert_eq!(test_bed.trim_air_valves_open_amount()[1], initial_open); + } + + #[test] + fn trim_air_system_delivers_overheat_air_if_overheat() { + let mut test_bed = test_bed() + .with() + .hot_air_pb_on(true) + .and() + .engine_idle() + .command_fwd_selected_temperature(ThermodynamicTemperature::new::( + 30., + )) + .iterate(500); + + assert!((test_bed.trim_air_system_outlet_air(1).flow_rate()) > MassRate::default()); + assert!( + (test_bed.trim_air_system_outlet_air(1).temperature()) + > ThermodynamicTemperature::new::(25.) + ); + + test_bed.fail(FailureType::TrimAirOverheat(ZoneType::Cabin(1))); + + test_bed = test_bed.iterate(1); + + assert!( + (test_bed.duct_temperature()[1]) + > ThermodynamicTemperature::new::(88.) + ); + } + + #[test] + fn hot_air_closes_if_overheat() { + let mut test_bed = test_bed() + .with() + .hot_air_pb_on(true) + .and() + .engine_idle() + .command_fwd_selected_temperature(ThermodynamicTemperature::new::( + 30., + )) + .iterate(500); + + test_bed.command_measured_temperature( + [ThermodynamicTemperature::new::(15.); 2], + ); + assert!((test_bed.trim_air_system_outlet_air(1).flow_rate()) > MassRate::default()); + test_bed.fail(FailureType::TrimAirOverheat(ZoneType::Cabin(1))); + + test_bed = test_bed.iterate(500); + + assert!( + (test_bed.trim_air_system_outlet_air(1).flow_rate()) + < MassRate::new::(0.001) + ); + } + + #[test] + fn hot_air_closes_if_one_tav_failed() { + let mut test_bed = test_bed() + .with() + .hot_air_pb_on(true) + .and() + .engine_idle() + .command_fwd_selected_temperature(ThermodynamicTemperature::new::( + 30., + )); + + test_bed.command_measured_temperature([ + ThermodynamicTemperature::new::(25.), + ThermodynamicTemperature::new::(15.), + ]); + + test_bed = test_bed.iterate(500); + + assert!((test_bed.duct_temperature()[1] > test_bed.duct_temperature()[0])); + test_bed.fail(FailureType::TrimAirFault(ZoneType::Cabin(1))); + + test_bed = test_bed.iterate(100); + + assert_eq!( + test_bed.duct_temperature()[0], + test_bed.duct_temperature()[1] + ); + } + #[test] fn trim_valves_react_to_only_one_pack_operative() { let mut test_bed = test_bed() diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 6e66b181955..b31aae4b583 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -98,6 +98,7 @@ pub trait PressurizationOverheadShared { /// Cabin Zones with double digit IDs are specific to the A380 /// 1X is main deck, 2X is upper deck +#[derive(Clone, Copy, Eq, PartialEq)] pub enum ZoneType { Cockpit, Cabin(u8), @@ -461,42 +462,36 @@ impl Default for AirConditioningPack { pub struct TrimAirSystem { duct_temperature_id: [VariableIdentifier; ZONES], + duct_overheat_id: [VariableIdentifier; ZONES], + trim_air_valves: [TrimAirValve; ZONES], // These are not a real components of the system, but a tool to simulate the mixing of air pack_mixer_container: PneumaticPipe, trim_air_mixers: [MixerUnit<1>; ZONES], + duct_overheat: [bool; ZONES], outlet_air: Air, } impl TrimAirSystem { pub fn new(context: &mut InitContext, cabin_zone_ids: &[ZoneType; ZONES]) -> Self { - let duct_temperature_id = cabin_zone_ids - .iter() - .map(|id| context.get_identifier(format!("COND_{}_DUCT_TEMP", id))) - .collect::>() - .try_into() - .unwrap_or_else(|v: Vec| { - panic!("Expected a Vec of length {} but it was {}", ZONES, v.len()) - }); - - let trim_air_valves = cabin_zone_ids - .iter() - .map(|id| TrimAirValve::new(context, id)) - .collect::>() - .try_into() - .unwrap_or_else(|v: Vec| { - panic!("Expected a Vec of length {} but it was {}", ZONES, v.len()) - }); + let duct_temperature_id = + cabin_zone_ids.map(|id| context.get_identifier(format!("COND_{}_DUCT_TEMP", id))); + let duct_overheat_id = + cabin_zone_ids.map(|id| context.get_identifier(format!("COND_{}_DUCT_OVHT", id))); Self { duct_temperature_id, - trim_air_valves, + duct_overheat_id, + + trim_air_valves: cabin_zone_ids.map(|id| TrimAirValve::new(context, &id)), pack_mixer_container: PneumaticPipe::new( Volume::new::(4.), Pressure::new::(14.7), ThermodynamicTemperature::new::(15.), ), trim_air_mixers: [MixerUnit::new(&[ZoneType::Cabin(1)]); ZONES], + + duct_overheat: [false; ZONES], outlet_air: Air::new(), } } @@ -504,12 +499,14 @@ impl TrimAirSystem { pub fn update( &mut self, context: &UpdateContext, + trim_air_pressure_regulating_valve_open: bool, mixer_air: &MixerUnit, tav_controller: &AirConditioningSystemController, ) { for (id, tav) in self.trim_air_valves.iter_mut().enumerate() { tav.update( context, + trim_air_pressure_regulating_valve_open, &mut self.pack_mixer_container, tav_controller.trim_air_valve_controllers(id), ); @@ -525,6 +522,18 @@ impl TrimAirSystem { self.outlet_air.set_flow_rate(total_flow); self.outlet_air .set_pressure(self.trim_air_outlet_pressure()); + + self.duct_overheat = (0..ZONES) + .map(|id| { + self.duct_overheat_determination(id) + || (self.duct_overheat[id] + && tav_controller.hot_air_pb_fault_light_determination()) + }) + .collect::>() + .try_into() + .unwrap_or_else(|v: Vec| { + panic!("Expected a Vec of length {} but it was {}", ZONES, v.len()) + }); } pub fn mix_packs_air_update(&mut self, pack_container: &mut [impl PneumaticContainer; 2]) { @@ -554,6 +563,17 @@ impl TrimAirSystem { .average() } + fn any_trim_air_valve_has_fault(&self) -> bool { + self.trim_air_valves + .iter() + .any(|tav| tav.trim_air_valve_has_fault()) + } + + fn duct_overheat_determination(&self, zone_id: usize) -> bool { + self.trim_air_mixers[zone_id].outlet_air().temperature() + > ThermodynamicTemperature::new::(88.) + } + #[cfg(test)] fn trim_air_valves_open_amount(&self) -> [Ratio; ZONES] { self.trim_air_valves @@ -584,18 +604,28 @@ impl SimulationElement for TrimAirSyst } fn write(&self, writer: &mut SimulatorWriter) { - for (id, var) in self.duct_temperature_id.iter().enumerate() { - writer.write(var, self.duct_temperature()[id]) + for (id, var) in self + .duct_temperature_id + .iter() + .zip(self.duct_overheat_id.iter()) + .enumerate() + { + writer.write(var.0, self.duct_temperature()[id]); + writer.write(var.1, self.duct_overheat[id]); } } } struct TrimAirValve { trim_air_valve_id: VariableIdentifier, + trim_air_valve_failure_id: VariableIdentifier, + trim_air_valve: DefaultValve, trim_air_container: PneumaticPipe, exhaust: PneumaticExhaust, outlet_air: Air, + failure: Failure, + overheat: Failure, } impl TrimAirValve { @@ -605,6 +635,9 @@ impl TrimAirValve { Self { trim_air_valve_id: context .get_identifier(format!("COND_{}_TRIM_AIR_VALVE_POSITION", zone_id)), + trim_air_valve_failure_id: context + .get_identifier(format!("COND_{}_TRIM_AIR_VALVE_HAS_FAULT", zone_id)), + trim_air_valve: DefaultValve::new_closed(), trim_air_container: PneumaticPipe::new( Volume::new::(0.03), // Based on references @@ -613,26 +646,44 @@ impl TrimAirValve { ), exhaust: PneumaticExhaust::new(5., 1., Pressure::new::(0.)), outlet_air: Air::new(), + failure: Failure::new(FailureType::TrimAirFault(*zone_id)), + overheat: Failure::new(FailureType::TrimAirOverheat(*zone_id)), } } fn update( &mut self, context: &UpdateContext, + trim_air_pressure_regulating_valve_open: bool, from: &mut impl PneumaticContainer, tav_controller: TrimAirValveController, ) { - self.trim_air_valve.update_open_amount(&tav_controller); + if !self.failure.is_active() { + self.trim_air_valve.update_open_amount(&tav_controller); + } self.trim_air_valve .update_move_fluid(context, from, &mut self.trim_air_container); self.exhaust .update_move_fluid(context, &mut self.trim_air_container); - self.outlet_air.set_temperature(from.temperature()); + if self.overheat.is_active() && trim_air_pressure_regulating_valve_open { + // When forcing overheat we inject high pressure high temperature air + self.outlet_air + .set_temperature(ThermodynamicTemperature::new::(200.)); + self.outlet_air + .set_flow_rate(MassRate::new::(0.8)); + } else { + self.outlet_air.set_temperature(from.temperature()); + self.outlet_air + .set_flow_rate(self.trim_air_valve_air_flow()); + } + self.outlet_air .set_pressure(self.trim_air_container.pressure()); - self.outlet_air - .set_flow_rate(self.trim_air_valve_air_flow()); + + if !trim_air_pressure_regulating_valve_open { + self.outlet_air.set_flow_rate(MassRate::default()); + } } fn trim_air_valve_open_amount(&self) -> Ratio { @@ -642,6 +693,10 @@ impl TrimAirValve { fn trim_air_valve_air_flow(&self) -> MassRate { self.trim_air_valve.fluid_flow() } + + fn trim_air_valve_has_fault(&self) -> bool { + self.failure.is_active() + } } impl OutletAir for TrimAirValve { @@ -653,6 +708,16 @@ impl OutletAir for TrimAirValve { impl SimulationElement for TrimAirValve { fn write(&self, writer: &mut SimulatorWriter) { writer.write(&self.trim_air_valve_id, self.trim_air_valve_open_amount()); + writer.write( + &self.trim_air_valve_failure_id, + self.trim_air_valve_has_fault(), + ); + } + + fn accept(&mut self, visitor: &mut T) { + self.failure.accept(visitor); + self.overheat.accept(visitor); + visitor.visit(self); } } @@ -711,6 +776,12 @@ impl Air { } } +impl OutletAir for Air { + fn outlet_air(&self) -> Air { + *self + } +} + impl Default for Air { fn default() -> Self { Self::new() diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index d4106ce1c0f..51757fbafeb 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -1,3 +1,4 @@ +use crate::air_conditioning::ZoneType; use crate::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId, @@ -8,6 +9,8 @@ use crate::simulation::SimulationElement; pub enum FailureType { CabinFan(usize), HotAir(usize), + TrimAirOverheat(ZoneType), + TrimAirFault(ZoneType), TransformerRectifier(usize), ReservoirLeak(HydraulicColor), ReservoirAirLeak(HydraulicColor), From e2f438a6bd516a18fe90cb51789977d2551ea1da Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sun, 16 Jul 2023 18:14:40 +0100 Subject: [PATCH 04/18] feat: zone controller failures --- fbw-a32nx/docs/a320-simvars.md | 12 ++ fbw-a32nx/src/systems/failures/src/a320.ts | 3 + .../instruments/src/Common/EWDMessages.tsx | 2 + .../EFB/failures-orchestrator-provider.tsx | 3 + .../systems/instruments/src/EWD/PseudoFWC.ts | 27 +++ .../instruments/src/SD/Pages/Cond/Cond.tsx | 8 +- .../a320_systems/src/air_conditioning.rs | 11 +- .../wasm/systems/a320_systems_wasm/src/lib.rs | 11 +- .../a380_systems/src/air_conditioning/mod.rs | 11 +- .../src/air_conditioning/acs_controller.rs | 190 +++++++++++++++++- .../systems/src/air_conditioning/mod.rs | 31 ++- .../wasm/systems/systems/src/failures/mod.rs | 4 +- 12 files changed, 289 insertions(+), 24 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index fa6a5080407..1b7135a8903 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2647,6 +2647,14 @@ In the variables below, {number} should be replaced with one item in the set: { - FWD - AFT +- A32NX_COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT + - Bool + - True when the primary channel of the zone controller is faulted + +- A32NX_COND_ZONE_CONTROLLER_BOTH_CHANNEL_HAS_FAULT + - Bool + - True when the both the primary and secondary channels of the zone controller have failed + - A32NX_HOT_AIR_VALVE_IS_ENABLED - Bool - True if the trim air system is enabled (pushbutton in auto and power supplied to system) @@ -2760,6 +2768,10 @@ In the variables below, {number} should be replaced with one item in the set: { - 1 - 2 +- A32NX_VENT_LAB_GALLEY_FAN_HAS_FAULT + - Bool + - True when the lavatory and galley extraction fan has failed + ## Pneumatic - A32NX_PNEU_ENG_{number}_IP_PRESSURE: diff --git a/fbw-a32nx/src/systems/failures/src/a320.ts b/fbw-a32nx/src/systems/failures/src/a320.ts index 8f1785bb495..e7792894e37 100644 --- a/fbw-a32nx/src/systems/failures/src/a320.ts +++ b/fbw-a32nx/src/systems/failures/src/a320.ts @@ -10,6 +10,9 @@ export const A320Failure = Object.freeze({ CkptDuctOvht: 21006, FwdDuctOvht: 21007, AftDuctOvht: 21008, + LabGalleyFan: 21009, + ZoneControllerPrimary: 21010, + ZoneControllerSecondary: 21011, Fac1Failure: 22000, Fac2Failure: 22001, diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index d04afec5994..c602cc8fdee 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -138,11 +138,13 @@ const EWDMessages = { '216321204': '\x1b<5m -HOT AIR....OFF THEN ON', '216321801': '\x1b<4m\x1b4mCOND\x1bm L+R CAB FAN FAULT', '216321802': '\x1b<5m -PACK FLOW...........HI', + '216326001': '\x1b<4m\x1b4mCOND\x1bm LAV+GALLEY FAN FAULT', '216329001': '\x1b<4m\x1b4mCOND\x1bm HOT AIR FAULT', '216329002': '\x1b<5m -HOT AIR............OFF', '216329003': '\x1b<7m .IF HOT AIR STILL OPEN:', '216329004': '\x1b<5m -PACK 1.............OFF', '216329005': '\x1b<5m -PACK 2.............OFF', + '216330001': '\x1b<4m\x1b4mCOND\x1bm ZONE REGUL FAULT', '216330501': '\x1b<4m\x1b4mCOND\x1bm TRIM AIR SYS FAULT', '216330502': '\x1b<4m -CPKT TRIM VALVE', '216330503': '\x1b<4m -FWD CAB TRIM VALVE', diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index a010aa2edb5..ae8428ace4b 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -24,6 +24,9 @@ const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ [21, A320Failure.CkptDuctOvht, 'Cockpit Duct Overheat'], [21, A320Failure.FwdDuctOvht, 'Forward Zone Duct Overheat'], [21, A320Failure.AftDuctOvht, 'Aft Zone Duct Overheat'], + [21, A320Failure.LabGalleyFan, 'Extraction Fan of lavatory and galley'], + [21, A320Failure.ZoneControllerPrimary, 'Zone Controller primary channel'], + [21, A320Failure.ZoneControllerSecondary, 'Zone Controller secondary channel'], [22, A320Failure.Fac1Failure, 'FAC 1'], [22, A320Failure.Fac2Failure, 'FAC 2'], diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index f1c88228b66..1b1fbbf4b9b 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -130,6 +130,10 @@ export class PseudoFWC { private readonly anyDuctOvht = Subject.create(false); + private readonly lavGalleyFanFault = Subject.create(false); + + private readonly zoneControllerPrimaryFault = Subject.create(false); + private readonly pack1On = Subject.create(false); private readonly pack2On = Subject.create(false); @@ -1069,6 +1073,9 @@ export class PseudoFWC { this.aftDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_AFT_DUCT_OVHT', 'bool')); this.anyDuctOvht.set(this.ckptDuctOvht.get() || this.fwdDuctOvht.get() || this.aftDuctOvht.get()); + this.lavGalleyFanFault.set(SimVar.GetSimVarValue('L:A32NX_VENT_LAB_GALLEY_FAN_HAS_FAULT', 'bool')); + this.zoneControllerPrimaryFault.set(SimVar.GetSimVarValue('L:A32NX_COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT', 'bool')); + const crossfeed = SimVar.GetSimVarValue('L:A32NX_PNEU_XBLEED_VALVE_OPEN', 'bool'); const eng1Bleed = SimVar.GetSimVarValue('A:BLEED AIR ENGINE:1', 'bool'); const eng1BleedPbFault = SimVar.GetSimVarValue('L:A32NX_OVHD_PNEU_ENG_1_BLEED_PB_HAS_FAULT', 'bool'); @@ -2245,6 +2252,16 @@ export class PseudoFWC { sysPage: 7, side: 'LEFT', }, + 2163260: { // LAV+GALLEY FAN FAULT + flightPhaseInhib: [3, 4, 5, 7, 8, 9], + simVarIsActive: this.lavGalleyFanFault, + whichCodeToReturn: () => [0], + codesToReturn: ['216326001'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, 2163290: { // HOT AIR FAULT flightPhaseInhib: [3, 4, 5, 7, 8], simVarIsActive: MappedSubject.create(([hotAirEnabled, hotAirOpen]) => hotAirEnabled !== hotAirOpen, this.hotAirEnabled, this.hotAirOpen), @@ -2260,6 +2277,16 @@ export class PseudoFWC { sysPage: 7, side: 'LEFT', }, + 2163300: { // ZONE REGUL FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.zoneControllerPrimaryFault, + whichCodeToReturn: () => [0], + codesToReturn: ['216330001'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, 216330: { // TRIM AIR SYS FAULT flightPhaseInhib: [3, 4, 5, 7, 8], simVarIsActive: MappedSubject.create( diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 6cd8fc7de87..0145974d0b1 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -32,6 +32,11 @@ export const CondPage = () => { const [cabFanHasFault1] = useSimVar('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool', 1000); const [cabFanHasFault2] = useSimVar('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool', 1000); + const [zoneControllerPrimaryFault] = useSimVar('L:A32NX_COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT', 'bool', 1000); + const [zoneControllerBothChannelFault] = useSimVar('L:A32NX_COND_ZONE_CONTROLLER_BOTH_CHANNEL_HAS_FAULT', 'bool', 1000); + + const altnMode = zoneControllerPrimaryFault && !zoneControllerBothChannelFault; + return ( {/* Title and unit */} @@ -42,7 +47,8 @@ export const CondPage = () => { °C FAN FAN - {/* ALTN MODE */} + ALTN MODE + PACK REG {/* Plane shape */} diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index 03e026e5174..1bee4fdba9a 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -269,7 +269,10 @@ impl A320AirConditioningSystem { CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(2)), ], mixer_unit: MixerUnit::new(cabin_zones), - packs: [AirConditioningPack::new(), AirConditioningPack::new()], + packs: [ + AirConditioningPack::new(Pack(1)), + AirConditioningPack::new(Pack(2)), + ], trim_air_system: TrimAirSystem::new(context, cabin_zones), air_conditioning_overhead: A320AirConditioningSystemOverhead::new(context, cabin_zones), @@ -314,7 +317,11 @@ impl A320AirConditioningSystem { ]; let duct_demand_temperature = self.acsc.duct_demand_temperature(); for (id, pack) in self.packs.iter_mut().enumerate() { - pack.update(pack_flow[id], &duct_demand_temperature) + pack.update( + pack_flow[id], + &duct_demand_temperature, + self.acsc.zone_controller_failure(), + ) } let mut mixer_intakes: Vec<&dyn OutletAir> = vec![&self.packs[0], &self.packs[1]]; diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index da43f18fc8c..a728af5eea7 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -24,7 +24,7 @@ use reversers::reversers; use rudder::rudder; use spoilers::spoilers; use std::error::Error; -use systems::air_conditioning::ZoneType; +use systems::air_conditioning::{acs_controller::ZoneControllerChannel, ZoneType}; use systems::failures::FailureType; use systems::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, ElectricalBusType, GearActuatorId, @@ -71,6 +71,15 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { (21_006, FailureType::TrimAirOverheat(ZoneType::Cockpit)), (21_007, FailureType::TrimAirOverheat(ZoneType::Cabin(1))), (21_008, FailureType::TrimAirOverheat(ZoneType::Cabin(2))), + (21_009, FailureType::GalleyFans), + ( + 21_010, + FailureType::ZoneController(ZoneControllerChannel::Primary), + ), + ( + 21_011, + FailureType::ZoneController(ZoneControllerChannel::Secondary), + ), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), (24_002, FailureType::TransformerRectifier(3)), diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index bf77a21b124..39ecd06e84b 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -350,7 +350,10 @@ impl A380AirConditioningSystem { CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(1)), ], mixer_unit: MixerUnit::new(cabin_zones), - packs: [AirConditioningPack::new(), AirConditioningPack::new()], + packs: [ + AirConditioningPack::new(Pack(1)), + AirConditioningPack::new(Pack(2)), + ], trim_air_system: TrimAirSystem::new(context, cabin_zones), air_conditioning_overhead: A380AirConditioningSystemOverhead::new(context), @@ -411,7 +414,11 @@ impl A380AirConditioningSystem { ]; let duct_demand_temperature = self.acsc.duct_demand_temperature(); for (id, pack) in self.packs.iter_mut().enumerate() { - pack.update(pack_flow[id], &duct_demand_temperature) + pack.update( + pack_flow[id], + &duct_demand_temperature, + self.acsc.zone_controller_failure(), + ) } let mut mixer_intakes: Vec<&dyn OutletAir> = vec![&self.packs[0], &self.packs[1]]; diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 706e03e5843..26652ca402d 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -57,7 +57,7 @@ impl AirConditioningSystemController Self { let zone_controller = cabin_zone_ids .iter() - .map(ZoneController::new) + .map(|id| ZoneController::new(context, id)) .collect::>>(); Self { aircraft_state: AirConditioningStateManager::new(), @@ -131,6 +131,9 @@ impl AirConditioningSystemController AirConditioningSystemController bool { self.trim_air_system_controller.should_turn_on_fault_light() } + + pub fn zone_controller_failure(&self) -> bool { + self.zone_controller + .iter() + .all(|zone| zone.zone_controller_total_failure()) + } } impl PackFlow @@ -214,6 +223,7 @@ impl SimulationElement { fn accept(&mut self, visitor: &mut T) { accept_iterable!(self.pack_flow_controller, visitor); + accept_iterable!(self.zone_controller, visitor); self.trim_air_system_controller.accept(visitor); visitor.visit(self); @@ -467,11 +477,25 @@ impl AirConditioningState { transition!(EndLanding, OnGround); +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ZoneControllerChannel { + Primary, + Secondary, +} + struct ZoneController { + galley_fan_failure_id: VariableIdentifier, + zone_controller_primary_failure_id: VariableIdentifier, + zone_controller_both_channels_failure_id: VariableIdentifier, + zone_id: usize, duct_demand_temperature: ThermodynamicTemperature, zone_selected_temperature: ThermodynamicTemperature, pid_controller: PidController, + + galley_fan_failure: Failure, + zone_regulation_primary_failure: Failure, + zone_regulation_secondary_failure: Failure, } impl ZoneController { @@ -490,7 +514,7 @@ impl ZoneController { const KP_DUCT_DEMAND_CABIN: f64 = 3.5; const KP_DUCT_DEMAND_COCKPIT: f64 = 2.; - fn new(zone_type: &ZoneType) -> Self { + fn new(context: &mut InitContext, zone_type: &ZoneType) -> Self { let pid_controller = match zone_type { ZoneType::Cockpit => { PidController::new( @@ -514,10 +538,25 @@ impl ZoneController { ), }; Self { + galley_fan_failure_id: context + .get_identifier("VENT_LAB_GALLEY_FAN_HAS_FAULT".to_owned()), + zone_controller_primary_failure_id: context + .get_identifier("COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT".to_owned()), + zone_controller_both_channels_failure_id: context + .get_identifier("COND_ZONE_CONTROLLER_BOTH_CHANNEL_HAS_FAULT".to_owned()), + zone_id: zone_type.id(), duct_demand_temperature: ThermodynamicTemperature::new::(24.), zone_selected_temperature: ThermodynamicTemperature::new::(24.), pid_controller, + + galley_fan_failure: Failure::new(FailureType::GalleyFans), + zone_regulation_primary_failure: Failure::new(FailureType::ZoneController( + ZoneControllerChannel::Primary, + )), + zone_regulation_secondary_failure: Failure::new(FailureType::ZoneController( + ZoneControllerChannel::Secondary, + )), } } @@ -530,14 +569,18 @@ impl ZoneController { operation_mode: &ACSCActiveComputer, ) { self.zone_selected_temperature = if matches!(operation_mode, ACSCActiveComputer::Secondary) + || self.zone_regulation_primary_failure.is_active() { - // If the Zone controller is working on secondary power, the zones are controlled to - // 24 degrees by the secondary computer + // If the Zone controller is working on secondary power or the primary channel has failed, + // the zones are controlled to 24 degrees by the secondary computer ThermodynamicTemperature::new::(24.) } else { acs_overhead.selected_cabin_temperature(self.zone_id) }; - self.duct_demand_temperature = if matches!(operation_mode, ACSCActiveComputer::None) { + self.duct_demand_temperature = if matches!(operation_mode, ACSCActiveComputer::None) + || (self.zone_regulation_primary_failure.is_active() + && self.zone_regulation_secondary_failure.is_active()) + { // If unpowered or failed, the pack controller would take over and deliver a fixed 20deg // for the cockpit and 10 for the cabin // Simulated here until packs are modelled @@ -546,6 +589,10 @@ impl ZoneController { } else { 10. }) + } else if self.galley_fan_failure.is_active() && self.zone_id != 0 { + // Cabin zone temperature sensors are ventilated by air extracted by this fan, cabin temperature regulation is lost + // Cabin inlet duct is constant at 15C, cockpit air is unnafected + ThermodynamicTemperature::new::(15.) } else { self.calculate_duct_temp_demand(context, pressurization, zone_measured_temperature) }; @@ -648,6 +695,43 @@ impl ZoneController { fn duct_demand_temperature(&self) -> ThermodynamicTemperature { self.duct_demand_temperature } + + fn zone_controller_primary_fault(&self) -> bool { + self.zone_regulation_primary_failure.is_active() + } + + fn zone_controller_total_failure(&self) -> bool { + self.zone_regulation_primary_failure.is_active() + && self.zone_regulation_secondary_failure.is_active() + } +} + +impl SimulationElement for ZoneController { + fn write(&self, writer: &mut SimulatorWriter) { + if self.zone_id == 0 { + // To avoid overwriting the same values for each zone + writer.write( + &self.galley_fan_failure_id, + self.galley_fan_failure.is_active(), + ); + writer.write( + &self.zone_controller_primary_failure_id, + self.zone_regulation_primary_failure.is_active(), + ); + writer.write( + &self.zone_controller_both_channels_failure_id, + self.zone_regulation_primary_failure.is_active() + && self.zone_regulation_secondary_failure.is_active(), + ); + } + } + + fn accept(&mut self, visitor: &mut T) { + self.galley_fan_failure.accept(visitor); + self.zone_regulation_primary_failure.accept(visitor); + self.zone_regulation_secondary_failure.accept(visitor); + visitor.visit(self); + } } #[derive(Clone, Copy)] @@ -936,6 +1020,7 @@ impl TrimAirSystemController, + zone_controller_fault: bool, ) { // When there is a failure the overhead button (or any other condition) // will not open or close the trim air pressure regulating valve @@ -946,6 +1031,7 @@ impl TrimAirSystemController TrimAirSystemController; 2], pneumatic: &impl PackFlowValveState, + zone_controller_fault: bool, ) -> bool { acs_overhead.hot_air_pushbutton_is_on() && operation_mode == ACSCActiveComputer::Primary @@ -984,6 +1071,7 @@ impl TrimAirSystemController TrimAirValveController { @@ -1949,7 +2037,10 @@ mod acs_controller_tests { engine_fire_push_buttons: TestEngineFirePushButtons::new(), mixer_unit: MixerUnit::new(&cabin_zones), number_of_passengers: 0, - packs: [AirConditioningPack::new(), AirConditioningPack::new()], + packs: [ + AirConditioningPack::new(Pack(1)), + AirConditioningPack::new(Pack(2)), + ], pneumatic: TestPneumatic::new(context), pneumatic_overhead: TestPneumaticOverhead::new(context), pressurization: TestPressurization::new(), @@ -2108,7 +2199,11 @@ mod acs_controller_tests { ]; let duct_demand_temperature = self.acsc.duct_demand_temperature(); for (id, pack) in self.packs.iter_mut().enumerate() { - pack.update(pack_flow[id], &duct_demand_temperature) + pack.update( + pack_flow[id], + &duct_demand_temperature, + self.acsc.zone_controller_failure(), + ) } for fan in self.cabin_fans.iter_mut() { fan.update( @@ -3006,6 +3101,87 @@ mod acs_controller_tests { ); } + #[test] + fn failing_primary_sets_the_zones_at_24c() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + + test_bed.fail(FailureType::ZoneController(ZoneControllerChannel::Primary)); + + test_bed = test_bed.iterate(1000); + + assert!((test_bed.duct_temperature()[1].get::() - 24.).abs() < 1.); + assert_eq!( + (test_bed.trim_air_valves_open_amount()[1]), + Ratio::default() + ); + } + + #[test] + fn failing_both_channels_sets_duct_demand_to_packs() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2) + .and() + .command_selected_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + + test_bed.fail(FailureType::ZoneController(ZoneControllerChannel::Primary)); + test_bed.fail(FailureType::ZoneController( + ZoneControllerChannel::Secondary, + )); + + test_bed = test_bed.iterate(1000); + + assert!( + (test_bed.duct_demand_temperature()[0].get::() - 20.).abs() < 1. + ); + assert!( + (test_bed.duct_demand_temperature()[1].get::() - 10.).abs() < 1. + ); + assert_eq!( + (test_bed.trim_air_valves_open_amount()[1]), + Ratio::default() + ); + } + + #[test] + fn failing_galley_fans_sets_duct_demand_to_15c() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .iterate(2) + .and() + .command_selected_temperature( + [ThermodynamicTemperature::new::(30.); 2], + ); + + test_bed.fail(FailureType::GalleyFans); + + test_bed = test_bed.iterate(1000); + + assert!( + (test_bed.duct_demand_temperature()[1].get::() - 15.).abs() < 1. + ); + assert_eq!( + (test_bed.trim_air_valves_open_amount()[1]), + Ratio::default() + ); + } + #[test] fn unpowering_and_repowering_primary_behaves_as_expected() { let mut test_bed = test_bed() diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index b31aae4b583..71f9db9bc71 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -425,26 +425,43 @@ impl OutletAir for MixerUnitOutlet { /// Temporary struct until packs are fully simulated pub struct AirConditioningPack { + pack_id: Pack, outlet_air: Air, } impl AirConditioningPack { - pub fn new() -> Self { + pub fn new(pack_id: Pack) -> Self { Self { + pack_id, outlet_air: Air::new(), } } /// Takes the minimum duct demand temperature as the pack outlet temperature. This is accurate to real world behaviour but /// this is a placeholder until the packs are modelled - pub fn update(&mut self, pack_flow: MassRate, duct_demand: &[ThermodynamicTemperature]) { + pub fn update( + &mut self, + pack_flow: MassRate, + duct_demand: &[ThermodynamicTemperature], + zone_controller_failure: bool, + ) { self.outlet_air.set_flow_rate(pack_flow); let min_temp = duct_demand .iter() .fold(f64::INFINITY, |acc, &t| acc.min(t.get::())); - self.outlet_air - .set_temperature(ThermodynamicTemperature::new::(min_temp)); + if zone_controller_failure { + if matches!(self.pack_id, Pack(1)) { + self.outlet_air + .set_temperature(ThermodynamicTemperature::new::(20.)); + } else { + self.outlet_air + .set_temperature(ThermodynamicTemperature::new::(10.)); + } + } else { + self.outlet_air + .set_temperature(ThermodynamicTemperature::new::(min_temp)); + } } } @@ -454,12 +471,6 @@ impl OutletAir for AirConditioningPack { } } -impl Default for AirConditioningPack { - fn default() -> Self { - Self::new() - } -} - pub struct TrimAirSystem { duct_temperature_id: [VariableIdentifier; ZONES], duct_overheat_id: [VariableIdentifier; ZONES], diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index 51757fbafeb..31ceb214528 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -1,4 +1,4 @@ -use crate::air_conditioning::ZoneType; +use crate::air_conditioning::{acs_controller::ZoneControllerChannel, ZoneType}; use crate::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId, @@ -11,6 +11,8 @@ pub enum FailureType { HotAir(usize), TrimAirOverheat(ZoneType), TrimAirFault(ZoneType), + GalleyFans, + ZoneController(ZoneControllerChannel), TransformerRectifier(usize), ReservoirLeak(HydraulicColor), ReservoirAirLeak(HydraulicColor), From 3a0d1263559ecae4193b29afdb630b071dbad85b Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Mon, 24 Jul 2023 22:17:12 +0100 Subject: [PATCH 05/18] feat: introduce arinc discrete words --- fbw-a32nx/docs/a320-simvars.md | 51 +++++++++ .../systems/instruments/src/EWD/PseudoFWC.ts | 37 ++++--- .../instruments/src/SD/Pages/Cond/Cond.tsx | 31 +++--- .../a320_systems/src/air_conditioning.rs | 101 +++++++++++++++++- .../src/air_conditioning/acs_controller.rs | 37 ++++++- .../systems/src/air_conditioning/mod.rs | 14 ++- 6 files changed, 233 insertions(+), 38 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index 1b7135a8903..977d81d2f8d 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2599,6 +2599,57 @@ In the variables below, {number} should be replaced with one item in the set: { ## Air Conditioning / Pressurisation / Ventilation +- A32NX_COND_ACSC_DISCRETE_WORD_1 + - Discrete Data word 1 of the ACSC bus output + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------------------:| + | 11 | Duct overheat F/D warning | + | 12 | Duct overheat FWD warning | + | 13 | Duct overheat AFT warning | + | 14 | Not used | + | 15 | Not used | + | 16 | Not used | + | 17 | Spare | + | 18 | Trim air pressure high | + | 19 | Spare | + | 20 | TAPRV status - close | + | 21 | Main zone control inop | + | 22 | Zone secondary control inop | + | 23 | Hot air switch position on | + | 24 | G + T fan off/fault | + | 25 | Recirc fan LH fault/OVHT | + | 26 | Recirc fan RH fault/OVHT | + | 27 | TAPRV disagree | + | 28 | Trim air system inop | + | 29 | Spare | + +- A32NX_COND_ACSC_DISCRETE_WORD_2 + - Discrete Data word 2 of the ACSC bus output + - Bits with * not yet implemented + - Arinc429 + - | Bit | Description | + |:---:|:----------------------------------------------------:| + | 11 | Spare | + | 12 | *K1 half wing anti-ice on | + | 13 | *K2 full wing anti-ice on | + | 14 | *K3 nacelle anti-ice on | + | 15 | *K4 air cond with two packs on | + | 16 | *K5 air cond with one pack on | + | 17 | *K6 air cond with two packs and one engine on | + | 18 | Trim valve F/D inop | + | 19 | Trim valve FWD inop | + | 20 | Trim valve AFT inop | + | 21 | Not used | + | 22 | Not used | + | 23 | Spare | + | 24 | Spare | + | 25 | Spare | + | 26 | Spare | + | 27 | *Nacelle anti-ice eng 2 open | + | 28 | *Nacelle anti-ice eng 1 open | + | 29 | Spare | + - A32NX_COND_{id}_TEMP - Degree Celsius - Temperature as measured in each of the cabin zones and cockpit diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index 1b1fbbf4b9b..07892772f63 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -94,6 +94,10 @@ export class PseudoFWC { /* 21 - AIR CONDITIONING AND PRESSURIZATION */ + private readonly acscDiscreteWord1 = Arinc429Register.empty(); + + private readonly acscDiscreteWord2 = Arinc429Register.empty(); + private readonly apuBleedValveOpen = Subject.create(false); private readonly cabAltSetReset1 = new NXLogicMemoryNode(); @@ -110,7 +114,7 @@ export class PseudoFWC { private readonly excessPressure = Subject.create(false); - private readonly hotAirEnabled = Subject.create(false); + private readonly hotAirDisagrees = Subject.create(false); private readonly hotAirOpen = Subject.create(false); @@ -1057,24 +1061,27 @@ export class PseudoFWC { /* 21 - AIR CONDITIONING AND PRESSURIZATION */ - this.cabFanHasFault1.set(SimVar.GetSimVarValue('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool')); - this.cabFanHasFault2.set(SimVar.GetSimVarValue('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool')); + this.acscDiscreteWord1.setFromSimVar('L:A32NX_COND_ACSC_DISCRETE_WORD_1'); + this.acscDiscreteWord2.setFromSimVar('L:A32NX_COND_ACSC_DISCRETE_WORD_2'); + + this.cabFanHasFault1.set(this.acscDiscreteWord1.bitValueOr(25, false)); + this.cabFanHasFault2.set(this.acscDiscreteWord1.bitValueOr(26, false)); - this.hotAirEnabled.set(SimVar.GetSimVarValue('L:A32NX_HOT_AIR_VALVE_IS_ENABLED', 'bool')); - this.hotAirOpen.set(SimVar.GetSimVarValue('L:A32NX_HOT_AIR_VALVE_IS_OPEN', 'bool')); - this.hotAirPbOn.set(SimVar.GetSimVarValue('L:A32NX_OVHD_COND_HOT_AIR_PB_IS_ON', 'bool')); + this.hotAirDisagrees.set(this.acscDiscreteWord1.bitValueOr(27, false)); + this.hotAirOpen.set(!this.acscDiscreteWord1.bitValueOr(20, false)); + this.hotAirPbOn.set(this.acscDiscreteWord1.bitValueOr(23, false)); - this.ckptTrimFault.set(SimVar.GetSimVarValue('L:A32NX_COND_CKPT_TRIM_AIR_VALVE_HAS_FAULT', 'bool')); - this.fwdTrimFault.set(SimVar.GetSimVarValue('L:A32NX_COND_FWD_TRIM_AIR_VALVE_HAS_FAULT', 'bool')); - this.aftTrimFault.set(SimVar.GetSimVarValue('L:A32NX_COND_AFT_TRIM_AIR_VALVE_HAS_FAULT', 'bool')); + this.ckptTrimFault.set(this.acscDiscreteWord2.bitValueOr(18, false)); + this.fwdTrimFault.set(this.acscDiscreteWord2.bitValueOr(19, false)); + this.aftTrimFault.set(this.acscDiscreteWord2.bitValueOr(20, false)); - this.ckptDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_CKPT_DUCT_OVHT', 'bool')); - this.fwdDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_FWD_DUCT_OVHT', 'bool')); - this.aftDuctOvht.set(SimVar.GetSimVarValue('L:A32NX_COND_AFT_DUCT_OVHT', 'bool')); + this.ckptDuctOvht.set(this.acscDiscreteWord1.bitValueOr(11, false)); + this.fwdDuctOvht.set(this.acscDiscreteWord1.bitValueOr(12, false)); + this.aftDuctOvht.set(this.acscDiscreteWord1.bitValueOr(13, false)); this.anyDuctOvht.set(this.ckptDuctOvht.get() || this.fwdDuctOvht.get() || this.aftDuctOvht.get()); - this.lavGalleyFanFault.set(SimVar.GetSimVarValue('L:A32NX_VENT_LAB_GALLEY_FAN_HAS_FAULT', 'bool')); - this.zoneControllerPrimaryFault.set(SimVar.GetSimVarValue('L:A32NX_COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT', 'bool')); + this.lavGalleyFanFault.set(this.acscDiscreteWord1.bitValueOr(24, false)); + this.zoneControllerPrimaryFault.set(this.acscDiscreteWord1.bitValueOr(21, false)); const crossfeed = SimVar.GetSimVarValue('L:A32NX_PNEU_XBLEED_VALVE_OPEN', 'bool'); const eng1Bleed = SimVar.GetSimVarValue('A:BLEED AIR ENGINE:1', 'bool'); @@ -2264,7 +2271,7 @@ export class PseudoFWC { }, 2163290: { // HOT AIR FAULT flightPhaseInhib: [3, 4, 5, 7, 8], - simVarIsActive: MappedSubject.create(([hotAirEnabled, hotAirOpen]) => hotAirEnabled !== hotAirOpen, this.hotAirEnabled, this.hotAirOpen), + simVarIsActive: this.hotAirDisagrees, whichCodeToReturn: () => [0, this.hotAirPbOn.get() ? 1 : null, (this.anyDuctOvht.get() && this.hotAirPbOn.get()) ? 2 : null, diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 0145974d0b1..4cb4395d055 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -3,7 +3,7 @@ // SPDX-License-Identifier: GPL-3.0 import React from 'react'; -import { useSimVar } from '@flybywiresim/fbw-sdk'; +import { useArinc429Var, useSimVar } from '@flybywiresim/fbw-sdk'; import { SvgGroup } from '../../Common/SvgGroup'; import Valve from './Valve'; @@ -13,6 +13,9 @@ export const CondPage = () => { // Display trim valve position for each zone const gaugeOffset = -43; // Gauges range is from -43 degree to +43 degree + const AcscDiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_DISCRETE_WORD_1'); + // TODO: If the Sign Status is Failure Warning or No Computed Data, the whole page should display XX's + const [cockpitTrimAirValve] = useSimVar('L:A32NX_COND_CKPT_TRIM_AIR_VALVE_POSITION', 'number', 1000); const [cockpitTrimTemp] = useSimVar('L:A32NX_COND_CKPT_DUCT_TEMP', 'celsius', 1000); const [cockpitCabinTemp] = useSimVar('L:A32NX_COND_CKPT_TEMP', 'celsius', 1000); @@ -25,15 +28,15 @@ export const CondPage = () => { const [aftTrimTemp] = useSimVar('L:A32NX_COND_AFT_DUCT_TEMP', 'celsius', 1000); const [aftCabinTemp] = useSimVar('L:A32NX_COND_AFT_TEMP', 'celsius', 1000); - const [hotAirOpen] = useSimVar('L:A32NX_HOT_AIR_VALVE_IS_OPEN', 'bool', 1000); - const [hotAirEnabled] = useSimVar('L:A32NX_HOT_AIR_VALVE_IS_ENABLED', 'bool', 1000); - const [hotAirPb] = useSimVar('L:A32NX_OVHD_COND_HOT_AIR_PB_IS_ON', 'bool', 1000); + const hotAirOpen = !AcscDiscreteWord1.getBitValueOr(20, false); + const hotAirPositionDisagrees = AcscDiscreteWord1.getBitValueOr(27, false); + const hotAirPb = AcscDiscreteWord1.getBitValueOr(23, false); - const [cabFanHasFault1] = useSimVar('L:A32NX_VENT_CABIN_FAN_1_HAS_FAULT', 'bool', 1000); - const [cabFanHasFault2] = useSimVar('L:A32NX_VENT_CABIN_FAN_2_HAS_FAULT', 'bool', 1000); + const cabFanHasFault1 = AcscDiscreteWord1.getBitValueOr(25, false); + const cabFanHasFault2 = AcscDiscreteWord1.getBitValueOr(26, false); - const [zoneControllerPrimaryFault] = useSimVar('L:A32NX_COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT', 'bool', 1000); - const [zoneControllerBothChannelFault] = useSimVar('L:A32NX_COND_ZONE_CONTROLLER_BOTH_CHANNEL_HAS_FAULT', 'bool', 1000); + const zoneControllerPrimaryFault = AcscDiscreteWord1.getBitValueOr(21, false); + const zoneControllerBothChannelFault = AcscDiscreteWord1.getBitValueOr(21, false) && AcscDiscreteWord1.getBitValueOr(22, false); const altnMode = zoneControllerPrimaryFault && !zoneControllerBothChannelFault; @@ -65,7 +68,7 @@ export const CondPage = () => { x={153} y={105} offset={gaugeOffset} - hotAir={(hotAirOpen !== hotAirEnabled) || !hotAirPb} + hotAir={hotAirPositionDisagrees || !hotAirPb} /> {/* Fwd */} @@ -77,7 +80,7 @@ export const CondPage = () => { x={324} y={105} offset={gaugeOffset} - hotAir={(hotAirOpen !== hotAirEnabled) || !hotAirPb} + hotAir={hotAirPositionDisagrees || !hotAirPb} /> {/* Aft */} @@ -89,7 +92,7 @@ export const CondPage = () => { x={494} y={105} offset={gaugeOffset} - hotAir={(hotAirOpen !== hotAirEnabled) || !hotAirPb} + hotAir={hotAirPositionDisagrees || !hotAirPb} /> {/* Valve and tubes */} @@ -98,9 +101,9 @@ export const CondPage = () => { HOT AIR - - - + + + ); diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index 1bee4fdba9a..fe440bf494f 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -1,7 +1,7 @@ use systems::{ accept_iterable, air_conditioning::{ - acs_controller::{AirConditioningSystemController, Pack}, + acs_controller::{ACSCActiveComputer, AirConditioningSystemController, Pack}, cabin_air::CabinAirSimulation, cabin_pressure_controller::CabinPressureController, pressure_valve::{OutflowValve, SafetyValve}, @@ -16,10 +16,12 @@ use systems::{ }, pneumatic::PneumaticContainer, shared::{ - random_number, update_iterator::MaxStepLoop, AverageExt, CabinAltitude, CabinSimulation, - ControllerSignal, ElectricalBusType, EngineBleedPushbutton, EngineCorrectedN1, - EngineFirePushButtons, EngineStartState, LgciuWeightOnWheels, PackFlowValveState, - PneumaticBleed, + arinc429::{Arinc429Word, SignStatus}, + random_number, + update_iterator::MaxStepLoop, + AverageExt, CabinAltitude, CabinSimulation, ControllerSignal, ElectricalBusType, + EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, EngineStartState, + LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{ InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -239,6 +241,7 @@ impl SimulationElement for A320Cabin { } pub struct A320AirConditioningSystem { + acs_interface: AirConditioningSystemInterfaceUnit, acsc: AirConditioningSystemController<3, 2>, cabin_fans: [CabinFan; 2], mixer_unit: MixerUnit<3>, @@ -252,6 +255,7 @@ pub struct A320AirConditioningSystem { impl A320AirConditioningSystem { pub(crate) fn new(context: &mut InitContext, cabin_zones: &[ZoneType; 3]) -> Self { Self { + acs_interface: AirConditioningSystemInterfaceUnit::new(context, cabin_zones), acsc: AirConditioningSystemController::new( context, cabin_zones, @@ -341,6 +345,13 @@ impl A320AirConditioningSystem { .set_pack_pushbutton_fault(self.pack_fault_determination(pneumatic)); self.air_conditioning_overhead .set_hot_air_pushbutton_fault(self.acsc.hot_air_pb_fault_light_determination()); + + self.acs_interface.update( + &self.air_conditioning_overhead, + &self.acsc, + &self.cabin_fans, + &self.trim_air_system, + ); } pub fn pack_fault_determination(&self, pneumatic: &impl PackFlowValveState) -> [bool; 2] { @@ -382,6 +393,7 @@ impl OutletAir for A320AirConditioningSystem { impl SimulationElement for A320AirConditioningSystem { fn accept(&mut self, visitor: &mut V) { + self.acs_interface.accept(visitor); self.acsc.accept(visitor); self.trim_air_system.accept(visitor); accept_iterable!(self.cabin_fans, visitor); @@ -392,6 +404,85 @@ impl SimulationElement for A320AirConditioningSystem { } } +/// Not sure this is a real subsystem of the ACSC but to keep it tidy we separate it +struct AirConditioningSystemInterfaceUnit { + discrete_word_1_id: VariableIdentifier, + discrete_word_2_id: VariableIdentifier, + + cabin_zones: [ZoneType; 3], + discrete_word_1: Arinc429Word, + discrete_word_2: Arinc429Word, +} + +impl AirConditioningSystemInterfaceUnit { + fn new(context: &mut InitContext, cabin_zones: &[ZoneType; 3]) -> Self { + Self { + discrete_word_1_id: context.get_identifier("COND_ACSC_DISCRETE_WORD_1".to_owned()), + discrete_word_2_id: context.get_identifier("COND_ACSC_DISCRETE_WORD_2".to_owned()), + + cabin_zones: *cabin_zones, + discrete_word_1: Arinc429Word::new(0, SignStatus::NoComputedData), + discrete_word_2: Arinc429Word::new(0, SignStatus::NoComputedData), + } + } + + fn update( + &mut self, + acs_overhead: &impl AirConditioningOverheadShared, + acsc: &AirConditioningSystemController<3, 2>, + cabin_fans: &[CabinFan; 2], + trim_air_system: &TrimAirSystem<3, 2>, + ) { + let duct_overheat = self + .cabin_zones + .map(|zone| trim_air_system.duct_overheat(zone.id())); + let trim_air_valve_fault = self + .cabin_zones + .map(|zone| trim_air_system.trim_air_valve_has_fault(zone.id())); + + if matches!( + acsc.operation_mode_determination(), + ACSCActiveComputer::None + ) { + self.discrete_word_1 = Arinc429Word::new(0, SignStatus::FailureWarning); + self.discrete_word_2 = Arinc429Word::new(0, SignStatus::FailureWarning); + } else { + self.discrete_word_1 = Arinc429Word::new(0, SignStatus::NormalOperation); + self.discrete_word_2 = Arinc429Word::new(0, SignStatus::NormalOperation); + + self.discrete_word_1.set_bit(11, duct_overheat[0]); + self.discrete_word_1.set_bit(12, duct_overheat[1]); + self.discrete_word_1.set_bit(13, duct_overheat[2]); + self.discrete_word_1.set_bit(18, false); // TODO: Implement trim air high pressure + self.discrete_word_1 + .set_bit(20, !acsc.trim_air_pressure_regulating_valve_is_open()); + self.discrete_word_1 + .set_bit(21, acsc.zone_controller_primary_fault()); + self.discrete_word_1 + .set_bit(22, acsc.zone_controller_secondary_fault()); + self.discrete_word_1 + .set_bit(23, acs_overhead.hot_air_pushbutton_is_on()); + self.discrete_word_1.set_bit(24, acsc.galley_fan_fault()); + self.discrete_word_1.set_bit(25, cabin_fans[0].has_fault()); + self.discrete_word_1.set_bit(26, cabin_fans[1].has_fault()); + self.discrete_word_1 + .set_bit(27, acsc.taprv_position_disagrees()); + self.discrete_word_1.set_bit(28, false); // TODO: Implement trim air inop + + self.discrete_word_2.set_bit(18, trim_air_valve_fault[0]); + self.discrete_word_2.set_bit(19, trim_air_valve_fault[1]); + self.discrete_word_2.set_bit(20, trim_air_valve_fault[2]); + } + } +} + +impl SimulationElement for AirConditioningSystemInterfaceUnit { + fn write(&self, writer: &mut SimulatorWriter) { + writer.write(&self.discrete_word_1_id, self.discrete_word_1); + writer.write(&self.discrete_word_2_id, self.discrete_word_2); + } +} + pub(crate) struct A320AirConditioningSystemOverhead { flow_selector_id: VariableIdentifier, diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 26652ca402d..038013f2b98 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -29,8 +29,8 @@ use uom::si::{ velocity::knot, }; -#[derive(PartialEq, Clone, Copy)] -enum ACSCActiveComputer { +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum ACSCActiveComputer { Primary, Secondary, None, @@ -140,7 +140,7 @@ impl AirConditioningSystemController ACSCActiveComputer { + pub fn operation_mode_determination(&self) -> ACSCActiveComputer { // TODO: Add failures if self.primary_is_powered { ACSCActiveComputer::Primary @@ -193,6 +193,28 @@ impl AirConditioningSystemController bool { + self.zone_controller + .iter() + .any(|zone| zone.zone_controller_primary_fault()) + } + + pub fn zone_controller_secondary_fault(&self) -> bool { + self.zone_controller + .iter() + .any(|zone| zone.zone_controller_secondary_fault()) + } + + pub fn galley_fan_fault(&self) -> bool { + self.zone_controller + .iter() + .any(|zone| zone.galley_fan_fault()) + } + + pub fn taprv_position_disagrees(&self) -> bool { + self.trim_air_system_controller.is_enabled() != self.trim_air_system_controller.is_open() + } + pub fn zone_controller_failure(&self) -> bool { self.zone_controller .iter() @@ -700,6 +722,15 @@ impl ZoneController { self.zone_regulation_primary_failure.is_active() } + fn zone_controller_secondary_fault(&self) -> bool { + self.zone_regulation_secondary_failure.is_active() + } + + fn galley_fan_fault(&self) -> bool { + self.galley_fan_failure.is_active() + } + + // TODO: Remove fn zone_controller_total_failure(&self) -> bool { self.zone_regulation_primary_failure.is_active() && self.zone_regulation_secondary_failure.is_active() diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 71f9db9bc71..247d5690387 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -106,7 +106,7 @@ pub enum ZoneType { } impl ZoneType { - fn id(&self) -> usize { + pub fn id(&self) -> usize { match self { ZoneType::Cockpit => 0, ZoneType::Cabin(number) => { @@ -291,6 +291,10 @@ impl CabinFan { / (Air::R * self.outlet_air.temperature().get::()); MassRate::new::(mass_flow) } + + pub fn has_fault(&self) -> bool { + self.failure.is_active() + } } impl OutletAir for CabinFan { @@ -574,6 +578,10 @@ impl TrimAirSystem { .average() } + pub fn trim_air_valve_has_fault(&self, tav_id: usize) -> bool { + self.trim_air_valves[tav_id].trim_air_valve_has_fault() + } + fn any_trim_air_valve_has_fault(&self) -> bool { self.trim_air_valves .iter() @@ -585,6 +593,10 @@ impl TrimAirSystem { > ThermodynamicTemperature::new::(88.) } + pub fn duct_overheat(&self, zone_id: usize) -> bool { + self.duct_overheat[zone_id] + } + #[cfg(test)] fn trim_air_valves_open_amount(&self) -> [Ratio; ZONES] { self.trim_air_valves From 796b31a13fed3d23430c66ca93010a9848e35d52 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Fri, 28 Jul 2023 22:37:53 +0100 Subject: [PATCH 06/18] chore: remove unused lvars after arinc --- fbw-a32nx/docs/a320-simvars.md | 35 ------------ .../a320_systems/src/air_conditioning.rs | 4 +- .../a380_systems/src/air_conditioning/mod.rs | 4 +- .../src/air_conditioning/acs_controller.rs | 54 ++----------------- .../systems/src/air_conditioning/mod.rs | 31 ++--------- 5 files changed, 12 insertions(+), 116 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index 977d81d2f8d..6625827bee6 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2666,14 +2666,6 @@ In the variables below, {number} should be replaced with one item in the set: { - FWD - AFT -- A32NX_COND_{id}_DUCT_OVHT - - Bool - - True when the duct temperature of the respective zone rises above 88 deg C - - {id} - - CKPT - - FWD - - AFT - - A32NX_COND_PACK_FLOW_VALVE_{index}_IS_OPEN - Bool - True if the respective {1 or 2} pack flow valve is open @@ -2690,22 +2682,6 @@ In the variables below, {number} should be replaced with one item in the set: { - FWD - AFT -- A32NX_COND_{id}_TRIM_AIR_VALVE_HAS_FAULT - - Bool - - The respective trim air valve is faulted - - {id} - - CKPT - - FWD - - AFT - -- A32NX_COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT - - Bool - - True when the primary channel of the zone controller is faulted - -- A32NX_COND_ZONE_CONTROLLER_BOTH_CHANNEL_HAS_FAULT - - Bool - - True when the both the primary and secondary channels of the zone controller have failed - - A32NX_HOT_AIR_VALVE_IS_ENABLED - Bool - True if the trim air system is enabled (pushbutton in auto and power supplied to system) @@ -2812,17 +2788,6 @@ In the variables below, {number} should be replaced with one item in the set: { - 1 - 2 -- A32NX_VENT_CABIN_FAN_{id}_HAS_FAULT - - Bool - - True if the corresponding cabin fan is faulted and not operating - - {number} - - 1 - - 2 - -- A32NX_VENT_LAB_GALLEY_FAN_HAS_FAULT - - Bool - - True when the lavatory and galley extraction fan has failed - ## Pneumatic - A32NX_PNEU_ENG_{number}_IP_PRESSURE: diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index fe440bf494f..f5ed1c15794 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -269,8 +269,8 @@ impl A320AirConditioningSystem { ], ), cabin_fans: [ - CabinFan::new(context, 1, ElectricalBusType::AlternatingCurrent(1)), - CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(2)), + CabinFan::new(1, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(2, ElectricalBusType::AlternatingCurrent(2)), ], mixer_unit: MixerUnit::new(cabin_zones), packs: [ diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index 39ecd06e84b..c61a1bcad05 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -346,8 +346,8 @@ impl A380AirConditioningSystem { ), ], cabin_fans: [ - CabinFan::new(context, 1, ElectricalBusType::AlternatingCurrent(1)), - CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(1, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(2, ElectricalBusType::AlternatingCurrent(1)), ], mixer_unit: MixerUnit::new(cabin_zones), packs: [ diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 038013f2b98..c07190e641d 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -57,7 +57,7 @@ impl AirConditioningSystemController Self { let zone_controller = cabin_zone_ids .iter() - .map(|id| ZoneController::new(context, id)) + .map(ZoneController::new) .collect::>>(); Self { aircraft_state: AirConditioningStateManager::new(), @@ -216,9 +216,7 @@ impl AirConditioningSystemController bool { - self.zone_controller - .iter() - .all(|zone| zone.zone_controller_total_failure()) + self.zone_controller_primary_fault() && self.zone_controller_secondary_fault() } } @@ -506,10 +504,6 @@ pub enum ZoneControllerChannel { } struct ZoneController { - galley_fan_failure_id: VariableIdentifier, - zone_controller_primary_failure_id: VariableIdentifier, - zone_controller_both_channels_failure_id: VariableIdentifier, - zone_id: usize, duct_demand_temperature: ThermodynamicTemperature, zone_selected_temperature: ThermodynamicTemperature, @@ -536,7 +530,7 @@ impl ZoneController { const KP_DUCT_DEMAND_CABIN: f64 = 3.5; const KP_DUCT_DEMAND_COCKPIT: f64 = 2.; - fn new(context: &mut InitContext, zone_type: &ZoneType) -> Self { + fn new(zone_type: &ZoneType) -> Self { let pid_controller = match zone_type { ZoneType::Cockpit => { PidController::new( @@ -560,13 +554,6 @@ impl ZoneController { ), }; Self { - galley_fan_failure_id: context - .get_identifier("VENT_LAB_GALLEY_FAN_HAS_FAULT".to_owned()), - zone_controller_primary_failure_id: context - .get_identifier("COND_ZONE_CONTROLLER_PRIMARY_CHANNEL_HAS_FAULT".to_owned()), - zone_controller_both_channels_failure_id: context - .get_identifier("COND_ZONE_CONTROLLER_BOTH_CHANNEL_HAS_FAULT".to_owned()), - zone_id: zone_type.id(), duct_demand_temperature: ThermodynamicTemperature::new::(24.), zone_selected_temperature: ThermodynamicTemperature::new::(24.), @@ -729,34 +716,9 @@ impl ZoneController { fn galley_fan_fault(&self) -> bool { self.galley_fan_failure.is_active() } - - // TODO: Remove - fn zone_controller_total_failure(&self) -> bool { - self.zone_regulation_primary_failure.is_active() - && self.zone_regulation_secondary_failure.is_active() - } } impl SimulationElement for ZoneController { - fn write(&self, writer: &mut SimulatorWriter) { - if self.zone_id == 0 { - // To avoid overwriting the same values for each zone - writer.write( - &self.galley_fan_failure_id, - self.galley_fan_failure.is_active(), - ); - writer.write( - &self.zone_controller_primary_failure_id, - self.zone_regulation_primary_failure.is_active(), - ); - writer.write( - &self.zone_controller_both_channels_failure_id, - self.zone_regulation_primary_failure.is_active() - && self.zone_regulation_secondary_failure.is_active(), - ); - } - } - fn accept(&mut self, visitor: &mut T) { self.galley_fan_failure.accept(visitor); self.zone_regulation_primary_failure.accept(visitor); @@ -2060,8 +2022,8 @@ mod acs_controller_tests { adirs: TestAdirs::new(), air_conditioning_system: TestAirConditioningSystem::new(), cabin_fans: [ - CabinFan::new(context, 1, ElectricalBusType::AlternatingCurrent(1)), - CabinFan::new(context, 2, ElectricalBusType::AlternatingCurrent(2)), + CabinFan::new(1, ElectricalBusType::AlternatingCurrent(1)), + CabinFan::new(2, ElectricalBusType::AlternatingCurrent(2)), ], engine_1: TestEngine::new(Ratio::default()), engine_2: TestEngine::new(Ratio::default()), @@ -2582,10 +2544,6 @@ mod acs_controller_tests { self.read_by_name("OVHD_COND_PACK_2_PB_HAS_FAULT") } - fn cabin_fan_has_fault(&mut self, fan_id: usize) -> bool { - self.read_by_name(format!("VENT_CABIN_FAN_{}_HAS_FAULT", fan_id).as_str()) - } - fn trim_air_system_controller_is_enabled(&self) -> bool { self.query(|a| { a.acsc.trim_air_system_controller.is_enabled() @@ -3897,8 +3855,6 @@ mod acs_controller_tests { (test_bed.mixer_unit_outlet_air().flow_rate() - test_bed.pack_flow()) < MassRate::new::(0.1) ); - assert!(test_bed.cabin_fan_has_fault(1)); - assert!(test_bed.cabin_fan_has_fault(2)); } } diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 247d5690387..3b2d31866c9 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -228,8 +228,6 @@ pub trait PressurizationConstants { /// A320neo fan part number: VD3900-03 pub struct CabinFan { - cabin_fan_fault_id: VariableIdentifier, - is_on: bool, outlet_air: Air, @@ -243,10 +241,8 @@ impl CabinFan { const PRESSURE_RISE_HPA: f64 = 22.; // hPa const FAN_EFFICIENCY: f64 = 0.75; // Ratio - so output matches AMM numbers - pub fn new(context: &mut InitContext, id: u8, powered_by: ElectricalBusType) -> Self { + pub fn new(id: u8, powered_by: ElectricalBusType) -> Self { Self { - cabin_fan_fault_id: context.get_identifier(format!("VENT_CABIN_FAN_{}_HAS_FAULT", id)), - is_on: false, outlet_air: Air::new(), @@ -309,10 +305,6 @@ impl SimulationElement for CabinFan { visitor.visit(self); } - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.cabin_fan_fault_id, self.failure.is_active()); - } - fn receive_power(&mut self, buses: &impl ElectricalBuses) { self.is_powered = buses.is_powered(self.powered_by); } @@ -477,7 +469,6 @@ impl OutletAir for AirConditioningPack { pub struct TrimAirSystem { duct_temperature_id: [VariableIdentifier; ZONES], - duct_overheat_id: [VariableIdentifier; ZONES], trim_air_valves: [TrimAirValve; ZONES], // These are not a real components of the system, but a tool to simulate the mixing of air @@ -491,12 +482,9 @@ impl TrimAirSystem { pub fn new(context: &mut InitContext, cabin_zone_ids: &[ZoneType; ZONES]) -> Self { let duct_temperature_id = cabin_zone_ids.map(|id| context.get_identifier(format!("COND_{}_DUCT_TEMP", id))); - let duct_overheat_id = - cabin_zone_ids.map(|id| context.get_identifier(format!("COND_{}_DUCT_OVHT", id))); Self { duct_temperature_id, - duct_overheat_id, trim_air_valves: cabin_zone_ids.map(|id| TrimAirValve::new(context, &id)), pack_mixer_container: PneumaticPipe::new( @@ -627,21 +615,14 @@ impl SimulationElement for TrimAirSyst } fn write(&self, writer: &mut SimulatorWriter) { - for (id, var) in self - .duct_temperature_id - .iter() - .zip(self.duct_overheat_id.iter()) - .enumerate() - { - writer.write(var.0, self.duct_temperature()[id]); - writer.write(var.1, self.duct_overheat[id]); + for (id, var) in self.duct_temperature_id.iter().enumerate() { + writer.write(var, self.duct_temperature()[id]) } } } struct TrimAirValve { trim_air_valve_id: VariableIdentifier, - trim_air_valve_failure_id: VariableIdentifier, trim_air_valve: DefaultValve, trim_air_container: PneumaticPipe, @@ -658,8 +639,6 @@ impl TrimAirValve { Self { trim_air_valve_id: context .get_identifier(format!("COND_{}_TRIM_AIR_VALVE_POSITION", zone_id)), - trim_air_valve_failure_id: context - .get_identifier(format!("COND_{}_TRIM_AIR_VALVE_HAS_FAULT", zone_id)), trim_air_valve: DefaultValve::new_closed(), trim_air_container: PneumaticPipe::new( @@ -731,10 +710,6 @@ impl OutletAir for TrimAirValve { impl SimulationElement for TrimAirValve { fn write(&self, writer: &mut SimulatorWriter) { writer.write(&self.trim_air_valve_id, self.trim_air_valve_open_amount()); - writer.write( - &self.trim_air_valve_failure_id, - self.trim_air_valve_has_fault(), - ); } fn accept(&mut self, visitor: &mut T) { From 5da756ec30c6ea06b1a473443b3f806fc38da471 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Wed, 2 Aug 2023 09:17:10 +0100 Subject: [PATCH 07/18] feat: trim air high pressure failure --- fbw-a32nx/docs/a320-simvars.md | 2 +- fbw-a32nx/src/systems/failures/src/a320.ts | 1 + .../instruments/src/Common/EWDMessages.tsx | 1 + .../EFB/failures-orchestrator-provider.tsx | 1 + .../systems/instruments/src/EWD/PseudoFWC.ts | 13 +++++-- .../a320_systems/src/air_conditioning.rs | 8 +++- .../wasm/systems/a320_systems_wasm/src/lib.rs | 1 + .../src/air_conditioning/acs_controller.rs | 38 +++++++++++++++++++ .../systems/src/air_conditioning/mod.rs | 17 ++++++++- .../wasm/systems/systems/src/failures/mod.rs | 1 + 10 files changed, 74 insertions(+), 9 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index 6625827bee6..67670a8e787 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2621,7 +2621,7 @@ In the variables below, {number} should be replaced with one item in the set: { | 25 | Recirc fan LH fault/OVHT | | 26 | Recirc fan RH fault/OVHT | | 27 | TAPRV disagree | - | 28 | Trim air system inop | + | 28 | Trim air system fault | | 29 | Spare | - A32NX_COND_ACSC_DISCRETE_WORD_2 diff --git a/fbw-a32nx/src/systems/failures/src/a320.ts b/fbw-a32nx/src/systems/failures/src/a320.ts index e7792894e37..5be77ad08ca 100644 --- a/fbw-a32nx/src/systems/failures/src/a320.ts +++ b/fbw-a32nx/src/systems/failures/src/a320.ts @@ -13,6 +13,7 @@ export const A320Failure = Object.freeze({ LabGalleyFan: 21009, ZoneControllerPrimary: 21010, ZoneControllerSecondary: 21011, + TrimAirHighPressure: 21012, Fac1Failure: 22000, Fac2Failure: 22001, diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index c602cc8fdee..389612c7dfc 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -149,6 +149,7 @@ const EWDMessages = { '216330502': '\x1b<4m -CPKT TRIM VALVE', '216330503': '\x1b<4m -FWD CAB TRIM VALVE', '216330504': '\x1b<4m -AFT CAB TRIM VALVE', + '216330505': '\x1b<4m -TRIM AIR HI PR', '221070001': '\x1b<4m\x1b4mT.O\x1bm SPEEDS TOO LOW', '221070002': '\x1b<5m -TOW AND T.O DATA.CHECK', '221071001': '\x1b<4m\x1b4mT.O\x1bm V1/VR/V2 DISAGREE', diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index ae8428ace4b..a8ba6567a73 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -27,6 +27,7 @@ const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ [21, A320Failure.LabGalleyFan, 'Extraction Fan of lavatory and galley'], [21, A320Failure.ZoneControllerPrimary, 'Zone Controller primary channel'], [21, A320Failure.ZoneControllerSecondary, 'Zone Controller secondary channel'], + [21, A320Failure.TrimAirHighPressure, 'Trim Air System High Pressure'], [22, A320Failure.Fac1Failure, 'FAC 1'], [22, A320Failure.Fac2Failure, 'FAC 2'], diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index 07892772f63..c3e5ba26819 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -120,12 +120,16 @@ export class PseudoFWC { private readonly hotAirPbOn = Subject.create(false); + private readonly trimAirFault = Subject.create(false); + private readonly ckptTrimFault = Subject.create(false); private readonly fwdTrimFault = Subject.create(false); private readonly aftTrimFault = Subject.create(false); + private readonly trimAirHighPressure = Subject.create(false); + private readonly ckptDuctOvht = Subject.create(false); private readonly fwdDuctOvht = Subject.create(false); @@ -1071,9 +1075,11 @@ export class PseudoFWC { this.hotAirOpen.set(!this.acscDiscreteWord1.bitValueOr(20, false)); this.hotAirPbOn.set(this.acscDiscreteWord1.bitValueOr(23, false)); + this.trimAirFault.set(this.acscDiscreteWord1.bitValueOr(28, false)); this.ckptTrimFault.set(this.acscDiscreteWord2.bitValueOr(18, false)); this.fwdTrimFault.set(this.acscDiscreteWord2.bitValueOr(19, false)); this.aftTrimFault.set(this.acscDiscreteWord2.bitValueOr(20, false)); + this.trimAirHighPressure.set(this.acscDiscreteWord1.bitValueOr(18, false)); this.ckptDuctOvht.set(this.acscDiscreteWord1.bitValueOr(11, false)); this.fwdDuctOvht.set(this.acscDiscreteWord1.bitValueOr(12, false)); @@ -2296,15 +2302,14 @@ export class PseudoFWC { }, 216330: { // TRIM AIR SYS FAULT flightPhaseInhib: [3, 4, 5, 7, 8], - simVarIsActive: MappedSubject.create( - ([ckptTrimFault, fwdTrimFault, aftTrimFault]) => ckptTrimFault || fwdTrimFault || aftTrimFault, this.ckptTrimFault, this.fwdTrimFault, this.aftTrimFault, - ), + simVarIsActive: this.trimAirFault, whichCodeToReturn: () => [0, this.ckptTrimFault.get() ? 1 : null, this.fwdTrimFault.get() ? 2 : null, this.aftTrimFault.get() ? 3 : null, + this.trimAirHighPressure.get() ? 4 : null, ], - codesToReturn: ['216330501', '216330502', '216330503', '216330504'], + codesToReturn: ['216330501', '216330502', '216330503', '216330504', '216330505'], memoInhibit: () => false, failure: 1, sysPage: -1, diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index f5ed1c15794..4f52075a63a 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -453,7 +453,8 @@ impl AirConditioningSystemInterfaceUnit { self.discrete_word_1.set_bit(11, duct_overheat[0]); self.discrete_word_1.set_bit(12, duct_overheat[1]); self.discrete_word_1.set_bit(13, duct_overheat[2]); - self.discrete_word_1.set_bit(18, false); // TODO: Implement trim air high pressure + self.discrete_word_1 + .set_bit(18, trim_air_system.trim_air_high_pressure()); self.discrete_word_1 .set_bit(20, !acsc.trim_air_pressure_regulating_valve_is_open()); self.discrete_word_1 @@ -467,7 +468,10 @@ impl AirConditioningSystemInterfaceUnit { self.discrete_word_1.set_bit(26, cabin_fans[1].has_fault()); self.discrete_word_1 .set_bit(27, acsc.taprv_position_disagrees()); - self.discrete_word_1.set_bit(28, false); // TODO: Implement trim air inop + self.discrete_word_1.set_bit( + 28, + trim_air_valve_fault.iter().any(|&t| t) || trim_air_system.trim_air_high_pressure(), + ); self.discrete_word_2.set_bit(18, trim_air_valve_fault[0]); self.discrete_word_2.set_bit(19, trim_air_valve_fault[1]); diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index a728af5eea7..d3bafc4e573 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -80,6 +80,7 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { 21_011, FailureType::ZoneController(ZoneControllerChannel::Secondary), ), + (21_012, FailureType::TrimAirHighPressure), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), (24_002, FailureType::TransformerRectifier(3)), diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index c07190e641d..de05cf6b72a 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -2559,6 +2559,10 @@ mod acs_controller_tests { self.query(|a| a.trim_air_system.duct_temperature()[0]) } + fn trim_air_system_outlet_pressure(&self) -> Pressure { + self.query(|a| a.trim_air_system.outlet_air.pressure()) + } + fn trim_air_valves_open_amount(&self) -> [Ratio; 2] { self.query(|a| a.trim_air_system.trim_air_valves_open_amount()) } @@ -2570,6 +2574,10 @@ mod acs_controller_tests { fn mixer_unit_outlet_air(&self) -> Air { self.query(|a| a.mixer_unit.outlet_air()) } + + fn trim_air_high_pressure(&self) -> bool { + self.query(|a| a.trim_air_system.trim_air_high_pressure()) + } } impl TestBed for ACSCTestBed { @@ -4067,6 +4075,36 @@ mod acs_controller_tests { ); } + #[test] + fn trim_increases_pressure_if_overpressure() { + let mut test_bed = test_bed() + .with() + .engine_idle() + .and() + .command_fwd_selected_temperature(ThermodynamicTemperature::new::( + 30., + )); + + test_bed.command_measured_temperature( + [ThermodynamicTemperature::new::(15.); 2], + ); + + test_bed = test_bed.iterate(50); + + assert!( + (test_bed.trim_air_system_outlet_air(1).flow_rate()) + > MassRate::new::(0.01) + ); + assert!((test_bed.trim_air_system_outlet_pressure()) < Pressure::new::(20.)); + + test_bed.fail(FailureType::TrimAirHighPressure); + + test_bed = test_bed.iterate(50); + + assert!((test_bed.trim_air_system_outlet_pressure()) > Pressure::new::(20.)); + assert!(test_bed.trim_air_high_pressure()); + } + #[test] fn trim_valves_react_to_only_one_pack_operative() { let mut test_bed = test_bed() diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 3b2d31866c9..8603e9b002a 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -474,6 +474,8 @@ pub struct TrimAirSystem { // These are not a real components of the system, but a tool to simulate the mixing of air pack_mixer_container: PneumaticPipe, trim_air_mixers: [MixerUnit<1>; ZONES], + + duct_high_pressure: Failure, duct_overheat: [bool; ZONES], outlet_air: Air, } @@ -494,6 +496,7 @@ impl TrimAirSystem { ), trim_air_mixers: [MixerUnit::new(&[ZoneType::Cabin(1)]); ZONES], + duct_high_pressure: Failure::new(FailureType::TrimAirHighPressure), duct_overheat: [false; ZONES], outlet_air: Air::new(), } @@ -523,8 +526,13 @@ impl TrimAirSystem { .map(|tam| tam.outlet_air.flow_rate()) .sum(); self.outlet_air.set_flow_rate(total_flow); - self.outlet_air - .set_pressure(self.trim_air_outlet_pressure()); + + if self.duct_high_pressure.is_active() { + self.outlet_air.set_pressure(Pressure::new::(22.)); + } else { + self.outlet_air + .set_pressure(self.trim_air_outlet_pressure()); + } self.duct_overheat = (0..ZONES) .map(|id| { @@ -585,6 +593,10 @@ impl TrimAirSystem { self.duct_overheat[zone_id] } + pub fn trim_air_high_pressure(&self) -> bool { + self.outlet_air.pressure() > Pressure::new::(20.) + } + #[cfg(test)] fn trim_air_valves_open_amount(&self) -> [Ratio; ZONES] { self.trim_air_valves @@ -610,6 +622,7 @@ impl DuctTemperature for TrimAirSystem impl SimulationElement for TrimAirSystem { fn accept(&mut self, visitor: &mut V) { accept_iterable!(self.trim_air_valves, visitor); + self.duct_high_pressure.accept(visitor); visitor.visit(self); } diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index 31ceb214528..95370413f73 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -11,6 +11,7 @@ pub enum FailureType { HotAir(usize), TrimAirOverheat(ZoneType), TrimAirFault(ZoneType), + TrimAirHighPressure, GalleyFans, ZoneController(ZoneControllerChannel), TransformerRectifier(usize), From 0cae75555a8b0cac8440055726cf290b4abb2518 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Thu, 10 Aug 2023 21:31:59 +0100 Subject: [PATCH 08/18] feat: timers for overheat and taprv failure --- .../a320_systems/src/air_conditioning.rs | 2 +- .../src/air_conditioning/acs_controller.rs | 155 +++++++++++++++--- .../systems/src/air_conditioning/mod.rs | 23 --- 3 files changed, 133 insertions(+), 47 deletions(-) diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index 4f52075a63a..a9eb9bd66af 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -435,7 +435,7 @@ impl AirConditioningSystemInterfaceUnit { ) { let duct_overheat = self .cabin_zones - .map(|zone| trim_air_system.duct_overheat(zone.id())); + .map(|zone| acsc.duct_overheat(zone.id())); let trim_air_valve_fault = self .cabin_zones .map(|zone| trim_air_system.trim_air_valve_has_fault(zone.id())); diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index de05cf6b72a..3a5131b40e5 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -189,8 +189,12 @@ impl AirConditioningSystemController bool { + self.trim_air_system_controller.duct_overheat(zone_id) + } + pub fn hot_air_pb_fault_light_determination(&self) -> bool { - self.trim_air_system_controller.should_turn_on_fault_light() + self.trim_air_system_controller.duct_overheat_monitor() } pub fn zone_controller_primary_fault(&self) -> bool { @@ -212,7 +216,8 @@ impl AirConditioningSystemController bool { - self.trim_air_system_controller.is_enabled() != self.trim_air_system_controller.is_open() + self.trim_air_system_controller + .taprv_disagree_status_monitor() } pub fn zone_controller_failure(&self) -> bool { @@ -981,25 +986,39 @@ struct TrimAirSystemController { hot_air_is_enabled_id: VariableIdentifier, hot_air_is_open_id: VariableIdentifier, + duct_overheat: [bool; ZONES], is_enabled: bool, is_open: bool, failure: Failure, - overheat_event: bool, - previous_pb_state: bool, + overheat_timer: [Duration; ZONES], + taprv_open_disagrees: bool, + taprv_open_timer: Duration, + taprv_closed_disagrees: bool, + taprv_closed_timer: Duration, trim_air_valve_controllers: [TrimAirValveController; ZONES], } impl TrimAirSystemController { + const DUCT_OVERHEAT_SET_LIMIT: f64 = 88.; // Deg C + const DUCT_OVERHEAT_RESET_LIMIT: f64 = 70.; // Deg C + const TAPRV_OPEN_COMMAND_DISAGREE_TIMER: f64 = 30.; // seconds + const TAPRV_CLOSE_COMMAND_DISAGREE_TIMER: f64 = 14.; // seconds + const TIMER_RESET: f64 = 1.2; // seconds + fn new(context: &mut InitContext) -> Self { Self { hot_air_is_enabled_id: context.get_identifier("HOT_AIR_VALVE_IS_ENABLED".to_owned()), hot_air_is_open_id: context.get_identifier("HOT_AIR_VALVE_IS_OPEN".to_owned()), + duct_overheat: [false; ZONES], is_enabled: false, is_open: false, failure: Failure::new(FailureType::HotAir(1)), // Only one hot air valve in the A320 - overheat_event: false, - previous_pb_state: false, + overheat_timer: [Duration::default(); ZONES], + taprv_open_disagrees: false, + taprv_open_timer: Duration::default(), + taprv_closed_disagrees: false, + taprv_closed_timer: Duration::default(), trim_air_valve_controllers: [TrimAirValveController::new(); ZONES], } } @@ -1041,10 +1060,23 @@ impl TrimAirSystemController>() + .try_into() + .unwrap_or_else(|v: Vec| { + panic!("Expected a Vec of length {} but it was {}", ZONES, v.len()) + }); - self.previous_pb_state = acs_overhead.hot_air_pushbutton_is_on(); + self.taprv_open_disagrees = self.taprv_open_command_disagree_monitor(context); + self.taprv_closed_disagrees = self.taprv_closed_command_disagree_monitor(context); } fn trim_air_pressure_regulating_valve_status_determination( @@ -1062,7 +1094,7 @@ impl TrimAirSystemController TrimAirSystemController, + zone_id: usize, ) -> bool { - if duct_temperature - .iter() - .any(|temp| *temp > ThermodynamicTemperature::new::(88.)) + if duct_temperature[zone_id] + > ThermodynamicTemperature::new::(Self::DUCT_OVERHEAT_SET_LIMIT) + { + if self.overheat_timer[zone_id] > Duration::from_secs_f64(Self::TIMER_RESET) { + true + } else { + self.overheat_timer[zone_id] += context.delta(); + false + } + } else if self.duct_overheat[zone_id] + && ((duct_temperature[zone_id] + > ThermodynamicTemperature::new::(Self::DUCT_OVERHEAT_RESET_LIMIT)) + || (duct_temperature[zone_id] + <= ThermodynamicTemperature::new::( + Self::DUCT_OVERHEAT_RESET_LIMIT, + ) + && acs_overhead.hot_air_pushbutton_is_on())) { true - } else if duct_temperature - .iter() - .all(|temp| *temp < ThermodynamicTemperature::new::(70.)) - && self.overheat_event + } else if self.duct_overheat[zone_id] + && duct_temperature[zone_id] + <= ThermodynamicTemperature::new::(Self::DUCT_OVERHEAT_RESET_LIMIT) + && !acs_overhead.hot_air_pushbutton_is_on() { - self.previous_pb_state || !acs_overhead.hot_air_pushbutton_is_on() + self.overheat_timer[zone_id] = Duration::default(); + false + } else { + self.duct_overheat[zone_id] + } + } + + fn taprv_open_command_disagree_monitor(&mut self, context: &UpdateContext) -> bool { + if !self.is_enabled { + false + } else if !self.is_open && !self.taprv_open_disagrees { + if self.taprv_open_timer + > Duration::from_secs_f64(Self::TAPRV_OPEN_COMMAND_DISAGREE_TIMER) + { + self.taprv_open_timer = Duration::default(); + true + } else { + self.taprv_open_timer += context.delta(); + false + } + } else if self.is_open && self.taprv_open_disagrees { + if self.taprv_open_timer > Duration::from_secs_f64(Self::TIMER_RESET) { + self.taprv_open_timer = Duration::default(); + false + } else { + self.taprv_open_timer += context.delta(); + true + } } else { - self.overheat_event + self.taprv_open_disagrees } } - fn should_turn_on_fault_light(&self) -> bool { - self.overheat_event + fn taprv_closed_command_disagree_monitor(&mut self, context: &UpdateContext) -> bool { + if self.is_enabled { + false + } else if self.is_open && !self.taprv_closed_disagrees { + if self.taprv_closed_timer + > Duration::from_secs_f64(Self::TAPRV_CLOSE_COMMAND_DISAGREE_TIMER) + { + self.taprv_closed_timer = Duration::default(); + true + } else { + self.taprv_closed_timer += context.delta(); + false + } + } else if !self.is_open && self.taprv_closed_disagrees { + if self.taprv_closed_timer > Duration::from_secs_f64(Self::TIMER_RESET) { + self.taprv_closed_timer = Duration::default(); + false + } else { + self.taprv_closed_timer += context.delta(); + true + } + } else { + self.taprv_closed_disagrees + } + } + + fn duct_overheat(&self, zone_id: usize) -> bool { + self.duct_overheat[zone_id] + } + + fn duct_overheat_monitor(&self) -> bool { + self.duct_overheat.iter().any(|&overheat| overheat) + } + + fn taprv_disagree_status_monitor(&self) -> bool { + self.taprv_open_disagrees || self.taprv_closed_disagrees } } diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 8603e9b002a..3463a3e7690 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -476,7 +476,6 @@ pub struct TrimAirSystem { trim_air_mixers: [MixerUnit<1>; ZONES], duct_high_pressure: Failure, - duct_overheat: [bool; ZONES], outlet_air: Air, } @@ -497,7 +496,6 @@ impl TrimAirSystem { trim_air_mixers: [MixerUnit::new(&[ZoneType::Cabin(1)]); ZONES], duct_high_pressure: Failure::new(FailureType::TrimAirHighPressure), - duct_overheat: [false; ZONES], outlet_air: Air::new(), } } @@ -533,18 +531,6 @@ impl TrimAirSystem { self.outlet_air .set_pressure(self.trim_air_outlet_pressure()); } - - self.duct_overheat = (0..ZONES) - .map(|id| { - self.duct_overheat_determination(id) - || (self.duct_overheat[id] - && tav_controller.hot_air_pb_fault_light_determination()) - }) - .collect::>() - .try_into() - .unwrap_or_else(|v: Vec| { - panic!("Expected a Vec of length {} but it was {}", ZONES, v.len()) - }); } pub fn mix_packs_air_update(&mut self, pack_container: &mut [impl PneumaticContainer; 2]) { @@ -584,15 +570,6 @@ impl TrimAirSystem { .any(|tav| tav.trim_air_valve_has_fault()) } - fn duct_overheat_determination(&self, zone_id: usize) -> bool { - self.trim_air_mixers[zone_id].outlet_air().temperature() - > ThermodynamicTemperature::new::(88.) - } - - pub fn duct_overheat(&self, zone_id: usize) -> bool { - self.duct_overheat[zone_id] - } - pub fn trim_air_high_pressure(&self) -> bool { self.outlet_air.pressure() > Pressure::new::(20.) } From 5884c5d4c44622e9d438be5ecbc67924763d93fd Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sun, 13 Aug 2023 20:38:50 +0100 Subject: [PATCH 09/18] refactor: acsc split --- fbw-a32nx/docs/a320-simvars.md | 32 +- .../instruments/src/Common/EWDMessages.tsx | 1 - .../systems/instruments/src/EWD/PseudoFWC.ts | 56 +- .../instruments/src/SD/Pages/Cond/Cond.tsx | 7 +- .../a320_systems/src/air_conditioning.rs | 196 ++-- .../wasm/systems/a320_systems_wasm/src/lib.rs | 18 +- .../a380_systems/src/air_conditioning/mod.rs | 23 +- .../src/air_conditioning/acs_controller.rs | 843 +++++++++--------- .../systems/src/air_conditioning/mod.rs | 146 ++- .../wasm/systems/systems/src/failures/mod.rs | 3 +- 10 files changed, 760 insertions(+), 565 deletions(-) diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index 67670a8e787..cecfefec3ba 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2599,8 +2599,9 @@ In the variables below, {number} should be replaced with one item in the set: { ## Air Conditioning / Pressurisation / Ventilation -- A32NX_COND_ACSC_DISCRETE_WORD_1 - - Discrete Data word 1 of the ACSC bus output +- A32NX_COND_ACSC_{number}_DISCRETE_WORD_1 + - Number 1 or 2 + - Discrete Data word 1 of the ACSC bus output (label 060) - Arinc429 - | Bit | Description | |:---:|:----------------------------------------------------:| @@ -2612,20 +2613,21 @@ In the variables below, {number} should be replaced with one item in the set: { | 16 | Not used | | 17 | Spare | | 18 | Trim air pressure high | - | 19 | Spare | + | 19 | ACSC Lane 1 Active | | 20 | TAPRV status - close | - | 21 | Main zone control inop | - | 22 | Zone secondary control inop | + | 21 | ACSC Lane 1 INOP | + | 22 | ACSC Lane 2 INOP | | 23 | Hot air switch position on | | 24 | G + T fan off/fault | | 25 | Recirc fan LH fault/OVHT | | 26 | Recirc fan RH fault/OVHT | | 27 | TAPRV disagree | | 28 | Trim air system fault | - | 29 | Spare | + | 29 | ACSC Installed | -- A32NX_COND_ACSC_DISCRETE_WORD_2 - - Discrete Data word 2 of the ACSC bus output +- A32NX_COND_ACSC_{number}_DISCRETE_WORD_2 + - Number 1 or 2 + - Discrete Data word 2 of the ACSC bus output (label 061) - Bits with * not yet implemented - Arinc429 - | Bit | Description | @@ -2642,9 +2644,9 @@ In the variables below, {number} should be replaced with one item in the set: { | 20 | Trim valve AFT inop | | 21 | Not used | | 22 | Not used | - | 23 | Spare | - | 24 | Spare | - | 25 | Spare | + | 23 | FCV status (Both pakcs off) | + | 24 | One pack operation | + | 25 | FCV status (Both pakcs on) | | 26 | Spare | | 27 | *Nacelle anti-ice eng 2 open | | 28 | *Nacelle anti-ice eng 1 open | @@ -2682,14 +2684,6 @@ In the variables below, {number} should be replaced with one item in the set: { - FWD - AFT -- A32NX_HOT_AIR_VALVE_IS_ENABLED - - Bool - - True if the trim air system is enabled (pushbutton in auto and power supplied to system) - -- A32NX_HOT_AIR_VALVE_IS_OPEN - - Bool - - True if the trim air system is enabled and the hot air valve is open - - A32NX_OVHD_COND_{id}_SELECTOR_KNOB - Percentage - Percent rotation of the overhead temperature selectors for each of the cabin zones diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index 389612c7dfc..5613f2bd262 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -144,7 +144,6 @@ const EWDMessages = { '216329003': '\x1b<7m .IF HOT AIR STILL OPEN:', '216329004': '\x1b<5m -PACK 1.............OFF', '216329005': '\x1b<5m -PACK 2.............OFF', - '216330001': '\x1b<4m\x1b4mCOND\x1bm ZONE REGUL FAULT', '216330501': '\x1b<4m\x1b4mCOND\x1bm TRIM AIR SYS FAULT', '216330502': '\x1b<4m -CPKT TRIM VALVE', '216330503': '\x1b<4m -FWD CAB TRIM VALVE', diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index c3e5ba26819..53dd34f4dce 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -94,9 +94,13 @@ export class PseudoFWC { /* 21 - AIR CONDITIONING AND PRESSURIZATION */ - private readonly acscDiscreteWord1 = Arinc429Register.empty(); + private readonly acsc1DiscreteWord1 = Arinc429Register.empty(); - private readonly acscDiscreteWord2 = Arinc429Register.empty(); + private readonly acsc1DiscreteWord2 = Arinc429Register.empty(); + + private readonly acsc2DiscreteWord1 = Arinc429Register.empty(); + + private readonly acsc2DiscreteWord2 = Arinc429Register.empty(); private readonly apuBleedValveOpen = Subject.create(false); @@ -140,8 +144,6 @@ export class PseudoFWC { private readonly lavGalleyFanFault = Subject.create(false); - private readonly zoneControllerPrimaryFault = Subject.create(false); - private readonly pack1On = Subject.create(false); private readonly pack2On = Subject.create(false); @@ -1065,29 +1067,31 @@ export class PseudoFWC { /* 21 - AIR CONDITIONING AND PRESSURIZATION */ - this.acscDiscreteWord1.setFromSimVar('L:A32NX_COND_ACSC_DISCRETE_WORD_1'); - this.acscDiscreteWord2.setFromSimVar('L:A32NX_COND_ACSC_DISCRETE_WORD_2'); + // FIXME: Should take both words + this.acsc1DiscreteWord1.setFromSimVar('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); + this.acsc1DiscreteWord2.setFromSimVar('L:A32NX_COND_ACSC_1_DISCRETE_WORD_2'); + this.acsc2DiscreteWord1.setFromSimVar('L:A32NX_COND_ACSC_2_DISCRETE_WORD_1'); + this.acsc2DiscreteWord2.setFromSimVar('L:A32NX_COND_ACSC_2_DISCRETE_WORD_2'); - this.cabFanHasFault1.set(this.acscDiscreteWord1.bitValueOr(25, false)); - this.cabFanHasFault2.set(this.acscDiscreteWord1.bitValueOr(26, false)); + this.cabFanHasFault1.set(this.acsc1DiscreteWord1.bitValueOr(25, false) || this.acsc2DiscreteWord1.bitValueOr(25, false)); + this.cabFanHasFault2.set(this.acsc1DiscreteWord1.bitValueOr(26, false) || this.acsc2DiscreteWord1.bitValueOr(26, false)); - this.hotAirDisagrees.set(this.acscDiscreteWord1.bitValueOr(27, false)); - this.hotAirOpen.set(!this.acscDiscreteWord1.bitValueOr(20, false)); - this.hotAirPbOn.set(this.acscDiscreteWord1.bitValueOr(23, false)); + this.hotAirDisagrees.set(this.acsc1DiscreteWord1.bitValueOr(27, false) || this.acsc2DiscreteWord1.bitValueOr(27, false)); + this.hotAirOpen.set(!this.acsc1DiscreteWord1.bitValueOr(20, false) || !this.acsc2DiscreteWord1.bitValueOr(20, false)); + this.hotAirPbOn.set(this.acsc1DiscreteWord1.bitValueOr(23, false) || this.acsc2DiscreteWord1.bitValueOr(23, false)); - this.trimAirFault.set(this.acscDiscreteWord1.bitValueOr(28, false)); - this.ckptTrimFault.set(this.acscDiscreteWord2.bitValueOr(18, false)); - this.fwdTrimFault.set(this.acscDiscreteWord2.bitValueOr(19, false)); - this.aftTrimFault.set(this.acscDiscreteWord2.bitValueOr(20, false)); - this.trimAirHighPressure.set(this.acscDiscreteWord1.bitValueOr(18, false)); + this.trimAirFault.set(this.acsc1DiscreteWord1.bitValueOr(28, false) || this.acsc2DiscreteWord1.bitValueOr(28, false)); + this.ckptTrimFault.set(this.acsc1DiscreteWord2.bitValueOr(18, false) || this.acsc2DiscreteWord2.bitValueOr(18, false)); + this.fwdTrimFault.set(this.acsc1DiscreteWord2.bitValueOr(19, false) || this.acsc2DiscreteWord2.bitValueOr(19, false)); + this.aftTrimFault.set(this.acsc1DiscreteWord2.bitValueOr(20, false) || this.acsc2DiscreteWord2.bitValueOr(20, false)); + this.trimAirHighPressure.set(this.acsc1DiscreteWord1.bitValueOr(18, false) || this.acsc2DiscreteWord1.bitValueOr(18, false)); - this.ckptDuctOvht.set(this.acscDiscreteWord1.bitValueOr(11, false)); - this.fwdDuctOvht.set(this.acscDiscreteWord1.bitValueOr(12, false)); - this.aftDuctOvht.set(this.acscDiscreteWord1.bitValueOr(13, false)); + this.ckptDuctOvht.set(this.acsc1DiscreteWord1.bitValueOr(11, false) || this.acsc2DiscreteWord1.bitValueOr(11, false)); + this.fwdDuctOvht.set(this.acsc1DiscreteWord1.bitValueOr(12, false) || this.acsc2DiscreteWord1.bitValueOr(12, false)); + this.aftDuctOvht.set(this.acsc1DiscreteWord1.bitValueOr(13, false) || this.acsc2DiscreteWord1.bitValueOr(13, false)); this.anyDuctOvht.set(this.ckptDuctOvht.get() || this.fwdDuctOvht.get() || this.aftDuctOvht.get()); - this.lavGalleyFanFault.set(this.acscDiscreteWord1.bitValueOr(24, false)); - this.zoneControllerPrimaryFault.set(this.acscDiscreteWord1.bitValueOr(21, false)); + this.lavGalleyFanFault.set(this.acsc1DiscreteWord1.bitValueOr(24, false) || this.acsc2DiscreteWord1.bitValueOr(24, false)); const crossfeed = SimVar.GetSimVarValue('L:A32NX_PNEU_XBLEED_VALVE_OPEN', 'bool'); const eng1Bleed = SimVar.GetSimVarValue('A:BLEED AIR ENGINE:1', 'bool'); @@ -2290,16 +2294,6 @@ export class PseudoFWC { sysPage: 7, side: 'LEFT', }, - 2163300: { // ZONE REGUL FAULT - flightPhaseInhib: [3, 4, 5, 7, 8], - simVarIsActive: this.zoneControllerPrimaryFault, - whichCodeToReturn: () => [0], - codesToReturn: ['216330001'], - memoInhibit: () => false, - failure: 1, - sysPage: -1, - side: 'LEFT', - }, 216330: { // TRIM AIR SYS FAULT flightPhaseInhib: [3, 4, 5, 7, 8], simVarIsActive: this.trimAirFault, diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 4cb4395d055..69763b8baf4 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -13,8 +13,11 @@ export const CondPage = () => { // Display trim valve position for each zone const gaugeOffset = -43; // Gauges range is from -43 degree to +43 degree - const AcscDiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_DISCRETE_WORD_1'); - // TODO: If the Sign Status is Failure Warning or No Computed Data, the whole page should display XX's + const Acsc1DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); + const Acsc2DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); + const AcscDiscreteWord1 = !Acsc1DiscreteWord1.isFailureWarning() ? Acsc1DiscreteWord1 : Acsc2DiscreteWord1; + + // TODO: If both Sign Status are Failure Warning or No Computed Data, the whole page should display XX's const [cockpitTrimAirValve] = useSimVar('L:A32NX_COND_CKPT_TRIM_AIR_VALVE_POSITION', 'number', 1000); const [cockpitTrimTemp] = useSimVar('L:A32NX_COND_CKPT_DUCT_TEMP', 'celsius', 1000); diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index a9eb9bd66af..e9d08cb57fa 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -1,7 +1,7 @@ use systems::{ accept_iterable, air_conditioning::{ - acs_controller::{ACSCActiveComputer, AirConditioningSystemController, Pack}, + acs_controller::{AcscId, AirConditioningSystemController, Pack}, cabin_air::CabinAirSimulation, cabin_pressure_controller::CabinPressureController, pressure_valve::{OutflowValve, SafetyValve}, @@ -241,8 +241,8 @@ impl SimulationElement for A320Cabin { } pub struct A320AirConditioningSystem { - acs_interface: AirConditioningSystemInterfaceUnit, - acsc: AirConditioningSystemController<3, 2>, + acs_interface: [AirConditioningSystemInterfaceUnit; 2], + acsc: [AirConditioningSystemController<3, 2>; 2], cabin_fans: [CabinFan; 2], mixer_unit: MixerUnit<3>, // Temporary structure until packs are simulated @@ -255,19 +255,30 @@ pub struct A320AirConditioningSystem { impl A320AirConditioningSystem { pub(crate) fn new(context: &mut InitContext, cabin_zones: &[ZoneType; 3]) -> Self { Self { - acs_interface: AirConditioningSystemInterfaceUnit::new(context, cabin_zones), - acsc: AirConditioningSystemController::new( - context, - cabin_zones, - vec![ - ElectricalBusType::DirectCurrent(1), - ElectricalBusType::AlternatingCurrent(1), - ], - vec![ - ElectricalBusType::DirectCurrent(2), - ElectricalBusType::AlternatingCurrent(2), - ], - ), + acs_interface: [ + AirConditioningSystemInterfaceUnit::new(context, AcscId::Acsc1, cabin_zones), + AirConditioningSystemInterfaceUnit::new(context, AcscId::Acsc2, cabin_zones), + ], + acsc: [ + AirConditioningSystemController::new( + context, + AcscId::Acsc1, + cabin_zones, + vec![ + ElectricalBusType::DirectCurrent(1), + ElectricalBusType::AlternatingCurrent(1), + ], + ), + AirConditioningSystemController::new( + context, + AcscId::Acsc2, + cabin_zones, + vec![ + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::AlternatingCurrent(2), + ], + ), + ], cabin_fans: [ CabinFan::new(1, ElectricalBusType::AlternatingCurrent(1)), CabinFan::new(2, ElectricalBusType::AlternatingCurrent(2)), @@ -277,7 +288,7 @@ impl A320AirConditioningSystem { AirConditioningPack::new(Pack(1)), AirConditioningPack::new(Pack(2)), ], - trim_air_system: TrimAirSystem::new(context, cabin_zones), + trim_air_system: TrimAirSystem::new(context, cabin_zones, vec![1]), air_conditioning_overhead: A320AirConditioningSystemOverhead::new(context, cabin_zones), } @@ -296,10 +307,9 @@ impl A320AirConditioningSystem { pressurization_overhead: &A320PressurizationOverheadPanel, lgciu: [&impl LgciuWeightOnWheels; 2], ) { - self.acsc.update( + self.update_acsc( context, adirs, - &self.air_conditioning_overhead, cabin_simulation, engines, engine_fire_push_buttons, @@ -308,54 +318,119 @@ impl A320AirConditioningSystem { pressurization, pressurization_overhead, lgciu, - &self.trim_air_system, ); + self.update_fans(cabin_simulation); + + self.update_packs(); + + self.update_mixer_unit(); + + self.update_trim_air_system(context); + + self.update_acsc_interface(); + + self.air_conditioning_overhead + .set_pack_pushbutton_fault(self.pack_fault_determination(pneumatic)); + self.air_conditioning_overhead.set_hot_air_pushbutton_fault( + self.acsc[0].hot_air_pb_fault_light_determination() + || self.acsc[1].hot_air_pb_fault_light_determination(), + ); + } + + fn update_acsc( + &mut self, + context: &UpdateContext, + adirs: &impl AdirsToAirCondInterface, + cabin_simulation: &impl CabinSimulation, + engines: [&impl EngineCorrectedN1; 2], + engine_fire_push_buttons: &impl EngineFirePushButtons, + pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), + pneumatic_overhead: &impl EngineBleedPushbutton<2>, + pressurization: &impl CabinAltitude, + pressurization_overhead: &A320PressurizationOverheadPanel, + lgciu: [&impl LgciuWeightOnWheels; 2], + ) { + for acsc in self.acsc.iter_mut() { + acsc.update( + context, + adirs, + &self.air_conditioning_overhead, + cabin_simulation, + engines, + engine_fire_push_buttons, + pneumatic, + pneumatic_overhead, + pressurization, + pressurization_overhead, + lgciu, + &self.trim_air_system, + ); + } + } + + fn update_fans(&mut self, cabin_simulation: &impl CabinSimulation) { for fan in self.cabin_fans.iter_mut() { - fan.update(cabin_simulation, &self.acsc.cabin_fans_controller()) + fan.update(cabin_simulation, &self.acsc[1].cabin_fans_controller()) } + } + fn update_packs(&mut self) { let pack_flow: [MassRate; 2] = [ - self.acsc.individual_pack_flow(Pack(1)), - self.acsc.individual_pack_flow(Pack(2)), + self.acsc[0].individual_pack_flow(), + self.acsc[1].individual_pack_flow(), + ]; + + let duct_demand_temperature = vec![ + self.acsc[0].duct_demand_temperature()[0], + self.acsc[1].duct_demand_temperature()[1], + self.acsc[1].duct_demand_temperature()[2], ]; - let duct_demand_temperature = self.acsc.duct_demand_temperature(); - for (id, pack) in self.packs.iter_mut().enumerate() { - pack.update( + + [0, 1].iter().for_each(|&id| { + self.packs[id].update( pack_flow[id], &duct_demand_temperature, - self.acsc.zone_controller_failure(), + self.acsc[id].both_channels_failure(), ) - } + }); + } + fn update_mixer_unit(&mut self) { let mut mixer_intakes: Vec<&dyn OutletAir> = vec![&self.packs[0], &self.packs[1]]; for fan in self.cabin_fans.iter() { mixer_intakes.push(fan) } self.mixer_unit.update(mixer_intakes); + } + fn update_trim_air_system(&mut self, context: &UpdateContext) { + // TAPRV monitors by ACSC 1 self.trim_air_system.update( context, - self.acsc.trim_air_pressure_regulating_valve_is_open(), + self.acsc[0].should_open_trim_air_pressure_regulating_valve() + && self.acsc[1].should_open_trim_air_pressure_regulating_valve(), &self.mixer_unit, - &self.acsc, + &[&self.acsc[0], &self.acsc[1], &self.acsc[1]], ); + } - self.air_conditioning_overhead - .set_pack_pushbutton_fault(self.pack_fault_determination(pneumatic)); - self.air_conditioning_overhead - .set_hot_air_pushbutton_fault(self.acsc.hot_air_pb_fault_light_determination()); - - self.acs_interface.update( - &self.air_conditioning_overhead, - &self.acsc, - &self.cabin_fans, - &self.trim_air_system, - ); + fn update_acsc_interface(&mut self) { + for (index, interface) in self.acs_interface.iter_mut().enumerate() { + interface.update( + &self.air_conditioning_overhead, + &self.acsc[index], + &self.cabin_fans, + &self.trim_air_system, + ) + } } pub fn pack_fault_determination(&self, pneumatic: &impl PackFlowValveState) -> [bool; 2] { - self.acsc.pack_fault_determination(pneumatic) + [ + self.acsc[0].pack_fault_determination(pneumatic), + self.acsc[1].pack_fault_determination(pneumatic), + ] } pub fn mix_packs_air_update(&mut self, pack_container: &mut [impl PneumaticContainer; 2]) { @@ -368,7 +443,8 @@ impl PackFlowControllers for A320AirConditioningSystem { as PackFlowControllers>::PackFlowControllerSignal; fn pack_flow_controller(&self, pack_id: usize) -> &Self::PackFlowControllerSignal { - self.acsc.pack_flow_controller(pack_id) + // // Pack ID 1 or 2 + self.acsc[pack_id - 1].pack_flow_controller(pack_id - 1) } } @@ -382,7 +458,7 @@ impl OutletAir for A320AirConditioningSystem { fn outlet_air(&self) -> Air { let mut outlet_air = Air::new(); outlet_air.set_flow_rate( - self.acsc.individual_pack_flow(Pack(1)) + self.acsc.individual_pack_flow(Pack(2)), + self.acsc[0].individual_pack_flow() + self.acsc[1].individual_pack_flow(), ); outlet_air.set_pressure(self.trim_air_system.trim_air_outlet_pressure()); outlet_air.set_temperature(self.duct_temperature().iter().average()); @@ -393,8 +469,8 @@ impl OutletAir for A320AirConditioningSystem { impl SimulationElement for A320AirConditioningSystem { fn accept(&mut self, visitor: &mut V) { - self.acs_interface.accept(visitor); - self.acsc.accept(visitor); + accept_iterable!(self.acs_interface, visitor); + accept_iterable!(self.acsc, visitor); self.trim_air_system.accept(visitor); accept_iterable!(self.cabin_fans, visitor); @@ -404,7 +480,6 @@ impl SimulationElement for A320AirConditioningSystem { } } -/// Not sure this is a real subsystem of the ACSC but to keep it tidy we separate it struct AirConditioningSystemInterfaceUnit { discrete_word_1_id: VariableIdentifier, discrete_word_2_id: VariableIdentifier, @@ -415,10 +490,12 @@ struct AirConditioningSystemInterfaceUnit { } impl AirConditioningSystemInterfaceUnit { - fn new(context: &mut InitContext, cabin_zones: &[ZoneType; 3]) -> Self { + fn new(context: &mut InitContext, acsc_id: AcscId, cabin_zones: &[ZoneType; 3]) -> Self { Self { - discrete_word_1_id: context.get_identifier("COND_ACSC_DISCRETE_WORD_1".to_owned()), - discrete_word_2_id: context.get_identifier("COND_ACSC_DISCRETE_WORD_2".to_owned()), + discrete_word_1_id: context + .get_identifier(format!("COND_ACSC_{}_DISCRETE_WORD_1", acsc_id)), + discrete_word_2_id: context + .get_identifier(format!("COND_ACSC_{}_DISCRETE_WORD_2", acsc_id)), cabin_zones: *cabin_zones, discrete_word_1: Arinc429Word::new(0, SignStatus::NoComputedData), @@ -433,17 +510,12 @@ impl AirConditioningSystemInterfaceUnit { cabin_fans: &[CabinFan; 2], trim_air_system: &TrimAirSystem<3, 2>, ) { - let duct_overheat = self - .cabin_zones - .map(|zone| acsc.duct_overheat(zone.id())); + let duct_overheat = self.cabin_zones.map(|zone| acsc.duct_overheat(zone.id())); let trim_air_valve_fault = self .cabin_zones .map(|zone| trim_air_system.trim_air_valve_has_fault(zone.id())); - if matches!( - acsc.operation_mode_determination(), - ACSCActiveComputer::None - ) { + if acsc.both_channels_failure() { self.discrete_word_1 = Arinc429Word::new(0, SignStatus::FailureWarning); self.discrete_word_2 = Arinc429Word::new(0, SignStatus::FailureWarning); } else { @@ -455,12 +527,11 @@ impl AirConditioningSystemInterfaceUnit { self.discrete_word_1.set_bit(13, duct_overheat[2]); self.discrete_word_1 .set_bit(18, trim_air_system.trim_air_high_pressure()); + self.discrete_word_1.set_bit(19, acsc.active_channel_1()); self.discrete_word_1 .set_bit(20, !acsc.trim_air_pressure_regulating_valve_is_open()); - self.discrete_word_1 - .set_bit(21, acsc.zone_controller_primary_fault()); - self.discrete_word_1 - .set_bit(22, acsc.zone_controller_secondary_fault()); + self.discrete_word_1.set_bit(21, acsc.channel_1_inop()); + self.discrete_word_1.set_bit(22, acsc.channel_2_inop()); self.discrete_word_1 .set_bit(23, acs_overhead.hot_air_pushbutton_is_on()); self.discrete_word_1.set_bit(24, acsc.galley_fan_fault()); @@ -472,10 +543,13 @@ impl AirConditioningSystemInterfaceUnit { 28, trim_air_valve_fault.iter().any(|&t| t) || trim_air_system.trim_air_high_pressure(), ); + self.discrete_word_1.set_bit(29, true); // Permanently true self.discrete_word_2.set_bit(18, trim_air_valve_fault[0]); self.discrete_word_2.set_bit(19, trim_air_valve_fault[1]); self.discrete_word_2.set_bit(20, trim_air_valve_fault[2]); + // 23 - Both packs off + // 24 - One pack operation } } } diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index d3bafc4e573..5b004a68421 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -24,7 +24,7 @@ use reversers::reversers; use rudder::rudder; use spoilers::spoilers; use std::error::Error; -use systems::air_conditioning::{acs_controller::ZoneControllerChannel, ZoneType}; +use systems::air_conditioning::ZoneType; use systems::failures::FailureType; use systems::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, ElectricalBusType, GearActuatorId, @@ -72,14 +72,14 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { (21_007, FailureType::TrimAirOverheat(ZoneType::Cabin(1))), (21_008, FailureType::TrimAirOverheat(ZoneType::Cabin(2))), (21_009, FailureType::GalleyFans), - ( - 21_010, - FailureType::ZoneController(ZoneControllerChannel::Primary), - ), - ( - 21_011, - FailureType::ZoneController(ZoneControllerChannel::Secondary), - ), + // ( + // 21_010, + // FailureType::ZoneController(ZoneControllerChannel::Primary), + // ), + // ( + // 21_011, + // FailureType::ZoneController(ZoneControllerChannel::Secondary), + // ), (21_012, FailureType::TrimAirHighPressure), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index c61a1bcad05..e967774af9b 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -1,7 +1,7 @@ use systems::{ accept_iterable, air_conditioning::{ - acs_controller::{AirConditioningSystemController, Pack}, + acs_controller::{AcscId, AirConditioningSystemController, Pack}, cabin_air::CabinAirSimulation, cabin_pressure_controller::CabinPressureController, full_digital_agu_controller::FullDigitalAGUController, @@ -319,15 +319,12 @@ impl A380AirConditioningSystem { Self { acsc: AirConditioningSystemController::new( context, + AcscId::Acsc1, cabin_zones, vec![ ElectricalBusType::DirectCurrent(1), ElectricalBusType::AlternatingCurrent(1), ], - vec![ - ElectricalBusType::DirectCurrent(2), - ElectricalBusType::AlternatingCurrent(2), - ], ), fdac: [ FullDigitalAGUController::new( @@ -354,7 +351,7 @@ impl A380AirConditioningSystem { AirConditioningPack::new(Pack(1)), AirConditioningPack::new(Pack(2)), ], - trim_air_system: TrimAirSystem::new(context, cabin_zones), + trim_air_system: TrimAirSystem::new(context, cabin_zones, vec![1]), air_conditioning_overhead: A380AirConditioningSystemOverhead::new(context), } @@ -408,16 +405,13 @@ impl A380AirConditioningSystem { fan.update(cabin_simulation, &self.acsc.cabin_fans_controller()) } - let pack_flow: [MassRate; 2] = [ - self.acsc.individual_pack_flow(Pack(1)), - self.acsc.individual_pack_flow(Pack(2)), - ]; + let pack_flow: [MassRate; 2] = [self.fdac[0].pack_flow(), self.fdac[1].pack_flow()]; let duct_demand_temperature = self.acsc.duct_demand_temperature(); for (id, pack) in self.packs.iter_mut().enumerate() { pack.update( pack_flow[id], &duct_demand_temperature, - self.acsc.zone_controller_failure(), + self.acsc.both_channels_failure(), ) } @@ -431,7 +425,7 @@ impl A380AirConditioningSystem { context, self.acsc.trim_air_pressure_regulating_valve_is_open(), &self.mixer_unit, - &self.acsc, + &[&self.acsc; 18], ); self.air_conditioning_overhead @@ -472,9 +466,8 @@ impl DuctTemperature for A380AirConditioningSystem { impl OutletAir for A380AirConditioningSystem { fn outlet_air(&self) -> Air { let mut outlet_air = Air::new(); - outlet_air.set_flow_rate( - self.acsc.individual_pack_flow(Pack(1)) + self.acsc.individual_pack_flow(Pack(2)), - ); + outlet_air + .set_flow_rate(self.acsc.individual_pack_flow() + self.acsc.individual_pack_flow()); outlet_air.set_pressure(self.trim_air_system.trim_air_outlet_pressure()); outlet_air.set_temperature(self.duct_temperature().iter().average()); diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 3a5131b40e5..1f66276a57e 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -3,8 +3,8 @@ use crate::{ pneumatic::{EngineModeSelector, EngineState, PneumaticValveSignal}, shared::{ pid::PidController, CabinAltitude, CabinSimulation, ControllerSignal, ElectricalBusType, - ElectricalBuses, EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, - EngineStartState, LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, + EngineBleedPushbutton, EngineCorrectedN1, EngineFirePushButtons, EngineStartState, + LgciuWeightOnWheels, PackFlowValveState, PneumaticBleed, }, simulation::{ InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, @@ -13,12 +13,12 @@ use crate::{ }; use super::{ - AdirsToAirCondInterface, AirConditioningOverheadShared, DuctTemperature, OverheadFlowSelector, - PackFlow, PackFlowControllers, PackFlowValveSignal, PressurizationOverheadShared, - TrimAirSystem, ZoneType, + AdirsToAirCondInterface, AirConditioningOverheadShared, Channel, DuctTemperature, + OperatingChannel, OverheadFlowSelector, PackFlow, PackFlowControllers, PackFlowValveSignal, + PressurizationOverheadShared, TrimAirControllers, TrimAirSystem, ZoneType, }; -use std::time::Duration; +use std::{fmt::Display, time::Duration}; use uom::si::{ f64::*, @@ -36,43 +36,86 @@ pub enum ACSCActiveComputer { None, } +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum AcscId { + Acsc1, + Acsc2, +} + +impl From for usize { + fn from(value: AcscId) -> Self { + match value { + AcscId::Acsc1 => 1, + AcscId::Acsc2 => 2, + } + } +} + +impl Display for AcscId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AcscId::Acsc1 => write!(f, "1"), + AcscId::Acsc2 => write!(f, "2"), + } + } +} + +#[derive(PartialEq)] +enum AcscFault { + OneChannelFault, + BothChannelsFault, +} + pub struct AirConditioningSystemController { + id: AcscId, + active_channel: OperatingChannel, + stand_by_channel: OperatingChannel, + aircraft_state: AirConditioningStateManager, zone_controller: Vec>, - pack_flow_controller: [PackFlowController; 2], + pack_flow_controller: PackFlowController, trim_air_system_controller: TrimAirSystemController, cabin_fans_controller: CabinFanController, - primary_powered_by: Vec, - primary_is_powered: bool, - secondary_powered_by: Vec, - secondary_is_powered: bool, + + internal_failure: Option, } impl AirConditioningSystemController { pub fn new( context: &mut InitContext, + id: AcscId, cabin_zone_ids: &[ZoneType; ZONES], - primary_powered_by: Vec, - secondary_powered_by: Vec, + powered_by: Vec, ) -> Self { - let zone_controller = cabin_zone_ids - .iter() - .map(ZoneController::new) - .collect::>>(); Self { + id, + + // FIXME Find correct power supply for ACSC + active_channel: OperatingChannel::new(1, powered_by[0]), + stand_by_channel: OperatingChannel::new(2, powered_by[1]), + aircraft_state: AirConditioningStateManager::new(), - zone_controller, - pack_flow_controller: [ - PackFlowController::new(context, Pack(1)), - PackFlowController::new(context, Pack(2)), - ], - trim_air_system_controller: TrimAirSystemController::new(context), + zone_controller: Self::zone_controller_initiation(id, cabin_zone_ids), + pack_flow_controller: PackFlowController::new(context, Pack(id.into())), + trim_air_system_controller: TrimAirSystemController::new(), cabin_fans_controller: CabinFanController::new(), - primary_powered_by, - primary_is_powered: false, - secondary_powered_by, - secondary_is_powered: false, + internal_failure: None, + } + } + + fn zone_controller_initiation( + id: AcscId, + cabin_zone_ids: &[ZoneType; ZONES], + ) -> Vec> { + // ACSC 1 regulates the cockpit temperature and ACSC 2 the cabin zones + if matches!(id, AcscId::Acsc1) { + vec![ZoneController::new(&cabin_zone_ids[0])] + } else { + cabin_zone_ids[1..ZONES] + .iter() + .map(ZoneController::new) + .collect::>>() } } @@ -91,35 +134,35 @@ impl AirConditioningSystemController, ) { + self.fault_determination(); + let ground_speed = self.ground_speed(adirs).unwrap_or_default(); self.aircraft_state = self .aircraft_state .update(context, ground_speed, &engines, lgciu); - let operation_mode = self.operation_mode_determination(); - - for pack_flow_controller in self.pack_flow_controller.iter_mut() { - pack_flow_controller.update( - context, - &self.aircraft_state, - acs_overhead, - engines, - engine_fire_push_buttons, - pneumatic, - pneumatic_overhead, - pressurization, - pressurization_overhead, - operation_mode, - ); - } + self.pack_flow_controller.update( + context, + &self.aircraft_state, + acs_overhead, + engines, + engine_fire_push_buttons, + pneumatic, + pneumatic_overhead, + pressurization, + pressurization_overhead, + !self.both_channels_failure(), + ); - for (index, zone) in self.zone_controller.iter_mut().enumerate() { + let both_channels_failure = self.both_channels_failure(); + for zone in self.zone_controller.iter_mut() { zone.update( context, + self.id, acs_overhead, - cabin_temperature.cabin_temperature()[index], + !both_channels_failure, + cabin_temperature.cabin_temperature(), pressurization, - &operation_mode, ) } @@ -127,28 +170,61 @@ impl AirConditioningSystemController ACSCActiveComputer { - // TODO: Add failures - if self.primary_is_powered { - ACSCActiveComputer::Primary - } else if self.secondary_is_powered { - ACSCActiveComputer::Secondary - } else { - ACSCActiveComputer::None - } + fn fault_determination(&mut self) { + self.internal_failure = match self.active_channel.has_fault() { + true => { + if self.stand_by_channel.has_fault() { + Some(AcscFault::BothChannelsFault) + } else { + self.switch_active_channel(); + Some(AcscFault::OneChannelFault) + } + } + false => { + if self.stand_by_channel.has_fault() { + Some(AcscFault::OneChannelFault) + } else { + None + } + } + }; + } + + fn switch_active_channel(&mut self) { + std::mem::swap(&mut self.stand_by_channel, &mut self.active_channel); + } + + pub fn active_channel_1(&self) -> bool { + matches!(self.active_channel.id(), Channel::ChannelOne) + } + + pub fn channel_1_inop(&self) -> bool { + [&self.active_channel, &self.stand_by_channel] + .iter() + .find(|channel| matches!(channel.id(), Channel::ChannelOne)) + .unwrap() + .has_fault() + } + + pub fn channel_2_inop(&self) -> bool { + [&self.active_channel, &self.stand_by_channel] + .iter() + .find(|channel| matches!(channel.id(), Channel::ChannelTwo)) + .unwrap() + .has_fault() + } + + pub fn both_channels_failure(&self) -> bool { + self.internal_failure == Some(AcscFault::BothChannelsFault) } fn ground_speed(&self, adirs: &impl AdirsToAirCondInterface) -> Option { @@ -158,35 +234,45 @@ impl AirConditioningSystemController [bool; 2] { - [ - self.pack_flow_controller[Pack(1).to_index()].fcv_status_determination(pneumatic), - self.pack_flow_controller[Pack(2).to_index()].fcv_status_determination(pneumatic), - ] - } - - pub(super) fn trim_air_valve_controllers(&self, zone_id: usize) -> TrimAirValveController { - self.trim_air_system_controller - .trim_air_valve_controllers(zone_id) + pub fn pack_fault_determination(&self, pneumatic: &impl PackFlowValveState) -> bool { + self.pack_flow_controller + .fcv_status_determination(pneumatic) } pub fn cabin_fans_controller(&self) -> CabinFanController { self.cabin_fans_controller } - pub fn individual_pack_flow(&self, pack_id: Pack) -> MassRate { - self.pack_flow_controller[pack_id.to_index()].pack_flow() + pub fn individual_pack_flow(&self) -> MassRate { + self.pack_flow_controller.pack_flow() } pub fn duct_demand_temperature(&self) -> Vec { - self.zone_controller + let mut demand_temperature: Vec = self + .zone_controller .iter() .map(|zone| zone.duct_demand_temperature()) - .collect() + .collect(); + // Because each ACSC calculates the demand of its respective zone(s), we fill the vector for the trim air system + let mut filler_vector = vec![ + ThermodynamicTemperature::new::(24.); + ZONES - demand_temperature.len() + ]; + if self.id == AcscId::Acsc1 { + demand_temperature.extend(filler_vector); + demand_temperature + } else { + filler_vector.extend(demand_temperature); + filler_vector + } + } + + pub fn should_open_trim_air_pressure_regulating_valve(&self) -> bool { + self.trim_air_system_controller.should_open_taprv() } pub fn trim_air_pressure_regulating_valve_is_open(&self) -> bool { - self.trim_air_system_controller.is_open() + self.trim_air_system_controller.tarpv_is_open() } pub fn duct_overheat(&self, zone_id: usize) -> bool { @@ -197,18 +283,6 @@ impl AirConditioningSystemController bool { - self.zone_controller - .iter() - .any(|zone| zone.zone_controller_primary_fault()) - } - - pub fn zone_controller_secondary_fault(&self) -> bool { - self.zone_controller - .iter() - .any(|zone| zone.zone_controller_secondary_fault()) - } - pub fn galley_fan_fault(&self) -> bool { self.zone_controller .iter() @@ -219,17 +293,13 @@ impl AirConditioningSystemController bool { - self.zone_controller_primary_fault() && self.zone_controller_secondary_fault() - } } impl PackFlow for AirConditioningSystemController { fn pack_flow(&self) -> MassRate { - self.pack_flow_controller[0].pack_flow() + self.pack_flow_controller[1].pack_flow() + self.pack_flow_controller.pack_flow() } } @@ -238,8 +308,17 @@ impl PackFlowControllers { type PackFlowControllerSignal = PackFlowController; - fn pack_flow_controller(&self, pack_id: usize) -> &Self::PackFlowControllerSignal { - &self.pack_flow_controller[pack_id - 1] + fn pack_flow_controller(&self, _pack_id: usize) -> &Self::PackFlowControllerSignal { + &self.pack_flow_controller + } +} + +impl TrimAirControllers + for AirConditioningSystemController +{ + fn trim_air_valve_controllers(&self, zone_id: usize) -> TrimAirValveController { + self.trim_air_system_controller + .trim_air_valve_controllers(zone_id) } } @@ -247,20 +326,14 @@ impl SimulationElement for AirConditioningSystemController { fn accept(&mut self, visitor: &mut T) { - accept_iterable!(self.pack_flow_controller, visitor); + self.active_channel.accept(visitor); + self.stand_by_channel.accept(visitor); + + self.pack_flow_controller.accept(visitor); accept_iterable!(self.zone_controller, visitor); - self.trim_air_system_controller.accept(visitor); visitor.visit(self); } - - fn receive_power(&mut self, buses: &impl ElectricalBuses) { - self.primary_is_powered = self.primary_powered_by.iter().all(|&p| buses.is_powered(p)); - self.secondary_is_powered = self - .secondary_powered_by - .iter() - .all(|&p| buses.is_powered(p)); - } } #[derive(Copy, Clone)] @@ -502,12 +575,6 @@ impl AirConditioningState { transition!(EndLanding, OnGround); -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum ZoneControllerChannel { - Primary, - Secondary, -} - struct ZoneController { zone_id: usize, duct_demand_temperature: ThermodynamicTemperature, @@ -515,8 +582,6 @@ struct ZoneController { pid_controller: PidController, galley_fan_failure: Failure, - zone_regulation_primary_failure: Failure, - zone_regulation_secondary_failure: Failure, } impl ZoneController { @@ -565,51 +630,36 @@ impl ZoneController { pid_controller, galley_fan_failure: Failure::new(FailureType::GalleyFans), - zone_regulation_primary_failure: Failure::new(FailureType::ZoneController( - ZoneControllerChannel::Primary, - )), - zone_regulation_secondary_failure: Failure::new(FailureType::ZoneController( - ZoneControllerChannel::Secondary, - )), } } fn update( &mut self, context: &UpdateContext, + acsc_id: AcscId, acs_overhead: &impl AirConditioningOverheadShared, - zone_measured_temperature: ThermodynamicTemperature, + is_enabled: bool, + zone_measured_temperature: Vec, pressurization: &impl CabinAltitude, - operation_mode: &ACSCActiveComputer, ) { - self.zone_selected_temperature = if matches!(operation_mode, ACSCActiveComputer::Secondary) - || self.zone_regulation_primary_failure.is_active() - { - // If the Zone controller is working on secondary power or the primary channel has failed, - // the zones are controlled to 24 degrees by the secondary computer + self.zone_selected_temperature = if !is_enabled { + // If unpowered or failed, the zone is maintained at fixed temperature ThermodynamicTemperature::new::(24.) } else { acs_overhead.selected_cabin_temperature(self.zone_id) }; - self.duct_demand_temperature = if matches!(operation_mode, ACSCActiveComputer::None) - || (self.zone_regulation_primary_failure.is_active() - && self.zone_regulation_secondary_failure.is_active()) - { - // If unpowered or failed, the pack controller would take over and deliver a fixed 20deg - // for the cockpit and 10 for the cabin - // Simulated here until packs are modelled - ThermodynamicTemperature::new::(if self.zone_id == 0 { - 20. + self.duct_demand_temperature = + if self.galley_fan_failure.is_active() && matches!(acsc_id, AcscId::Acsc2) { + // Cabin zone temperature sensors are ventilated by air extracted by this fan, cabin temperature regulation is lost + // Cabin inlet duct is constant at 15C, cockpit air is unnafected + ThermodynamicTemperature::new::(15.) } else { - 10. - }) - } else if self.galley_fan_failure.is_active() && self.zone_id != 0 { - // Cabin zone temperature sensors are ventilated by air extracted by this fan, cabin temperature regulation is lost - // Cabin inlet duct is constant at 15C, cockpit air is unnafected - ThermodynamicTemperature::new::(15.) - } else { - self.calculate_duct_temp_demand(context, pressurization, zone_measured_temperature) - }; + self.calculate_duct_temp_demand( + context, + pressurization, + zone_measured_temperature[self.zone_id], + ) + }; } fn calculate_duct_temp_demand( @@ -710,14 +760,6 @@ impl ZoneController { self.duct_demand_temperature } - fn zone_controller_primary_fault(&self) -> bool { - self.zone_regulation_primary_failure.is_active() - } - - fn zone_controller_secondary_fault(&self) -> bool { - self.zone_regulation_secondary_failure.is_active() - } - fn galley_fan_fault(&self) -> bool { self.galley_fan_failure.is_active() } @@ -726,8 +768,6 @@ impl ZoneController { impl SimulationElement for ZoneController { fn accept(&mut self, visitor: &mut T) { self.galley_fan_failure.accept(visitor); - self.zone_regulation_primary_failure.accept(visitor); - self.zone_regulation_secondary_failure.accept(visitor); visitor.visit(self); } } @@ -763,13 +803,13 @@ pub struct PackFlowController { pack_flow_id: VariableIdentifier, id: usize, + is_enabled: bool, flow_demand: Ratio, fcv_open_allowed: bool, should_open_fcv: bool, pack_flow: MassRate, pack_flow_demand: MassRate, pid: PidController, - operation_mode: ACSCActiveComputer, fcv_timer_open: Duration, } @@ -790,13 +830,13 @@ impl PackFlowController { pack_flow_id: context.get_identifier(Self::pack_flow_id(pack_id.to_index())), id: pack_id.to_index(), + is_enabled: false, flow_demand: Ratio::default(), fcv_open_allowed: false, should_open_fcv: false, pack_flow: MassRate::default(), pack_flow_demand: MassRate::default(), pid: PidController::new(0.01, 0.1, 0., 0., 1., 0., 1.), - operation_mode: ACSCActiveComputer::None, fcv_timer_open: Duration::from_secs(0), } @@ -817,10 +857,10 @@ impl PackFlowController { pneumatic_overhead: &impl EngineBleedPushbutton, pressurization: &impl CabinAltitude, pressurization_overhead: &impl PressurizationOverheadShared, - operation_mode: ACSCActiveComputer, + is_enabled: bool, ) { // TODO: Add overheat protection - self.operation_mode = operation_mode; + self.is_enabled = is_enabled; self.flow_demand = self.flow_demand_determination(aircraft_state, acs_overhead, pneumatic); self.fcv_open_allowed = self.fcv_open_allowed_determination( acs_overhead, @@ -857,12 +897,9 @@ impl PackFlowController { acs_overhead: &impl AirConditioningOverheadShared, pneumatic: &(impl EngineStartState + PackFlowValveState + PneumaticBleed), ) -> Ratio { - if matches!(self.operation_mode, ACSCActiveComputer::None) { - // If the computer is unpowered, return previous flow demand - return self.flow_demand; - } else if matches!(self.operation_mode, ACSCActiveComputer::Secondary) { - // If Secondary computer is active flow setting optimization is not available - return Ratio::new::(100.); + if !self.is_enabled { + // If both lanes of the ACSC fail, the PFV closes and the flow demand is 0 + return Ratio::new::(0.); } let mut intermediate_flow: Ratio = acs_overhead.flow_selector_position().into(); // TODO: Add "insufficient performance" based on Pack Mixer Temperature Demand @@ -962,17 +999,13 @@ impl PackFlow for PackFlowController { impl ControllerSignal for PackFlowController { fn signal(&self) -> Option { - // Only send signal to move the valve if the computer is powered - if !matches!(self.operation_mode, ACSCActiveComputer::None) { - let target_open = Ratio::new::(if self.should_open_fcv { - self.pid.output() - } else { - 0. - }); - Some(PackFlowValveSignal::new(target_open)) + // If both lanes of the ACSC fail, the PFV closes + let target_open = if self.is_enabled && self.should_open_fcv { + Ratio::new::(self.pid.output()) } else { - None - } + Ratio::default() + }; + Some(PackFlowValveSignal::new(target_open)) } } @@ -983,13 +1016,9 @@ impl SimulationElement for PackFlowController { } struct TrimAirSystemController { - hot_air_is_enabled_id: VariableIdentifier, - hot_air_is_open_id: VariableIdentifier, - duct_overheat: [bool; ZONES], is_enabled: bool, is_open: bool, - failure: Failure, overheat_timer: [Duration; ZONES], taprv_open_disagrees: bool, taprv_open_timer: Duration, @@ -1005,15 +1034,11 @@ impl TrimAirSystemController Self { + fn new() -> Self { Self { - hot_air_is_enabled_id: context.get_identifier("HOT_AIR_VALVE_IS_ENABLED".to_owned()), - hot_air_is_open_id: context.get_identifier("HOT_AIR_VALVE_IS_OPEN".to_owned()), - duct_overheat: [false; ZONES], is_enabled: false, is_open: false, - failure: Failure::new(FailureType::HotAir(1)), // Only one hot air valve in the A320 overheat_timer: [Duration::default(); ZONES], taprv_open_disagrees: false, taprv_open_timer: Duration::default(), @@ -1028,28 +1053,21 @@ impl TrimAirSystemController; 2], - operation_mode: ACSCActiveComputer, + is_enabled: bool, + pack_flow_controller: &PackFlowController, pneumatic: &impl PackFlowValveState, trim_air_system: &TrimAirSystem, - zone_controller_fault: bool, ) { - // When there is a failure the overhead button (or any other condition) - // will not open or close the trim air pressure regulating valve - // If the valve was open before the failure, it will stay open + // If both lanes of the ACSC fail, the associated trim air valves close self.is_enabled = self.trim_air_pressure_regulating_valve_status_determination( acs_overhead, trim_air_system.any_trim_air_valve_has_fault(), - operation_mode, + is_enabled, pack_flow_controller, pneumatic, - zone_controller_fault, ); - // When a failure is active the TAPRV will be unresponsive - it will stay in the same state as it was - if !self.failure.is_active() { - self.is_open = self.is_enabled; - } + self.is_open = trim_air_system.trim_air_pressure_regulating_valve_is_open(); for (id, tav_controller) in self.trim_air_valve_controllers.iter_mut().enumerate() { tav_controller.update( @@ -1083,32 +1101,28 @@ impl TrimAirSystemController; 2], + is_enabled: bool, + pack_flow_controller: &PackFlowController, pneumatic: &impl PackFlowValveState, - zone_controller_fault: bool, ) -> bool { acs_overhead.hot_air_pushbutton_is_on() - && operation_mode == ACSCActiveComputer::Primary - && !pack_flow_controller - .iter() - .any(|pack| pack.pack_start_condition_determination(pneumatic)) + && is_enabled + && !pack_flow_controller.pack_start_condition_determination(pneumatic) && ((pneumatic.pack_flow_valve_is_open(1)) || (pneumatic.pack_flow_valve_is_open(2))) && !self.duct_overheat_monitor() && !any_tav_has_fault - && !zone_controller_fault } fn trim_air_valve_controllers(&self, zone_id: usize) -> TrimAirValveController { self.trim_air_valve_controllers[zone_id] } - fn is_enabled(&self) -> bool { - self.is_enabled + fn tarpv_is_open(&self) -> bool { + self.is_open } - fn is_open(&self) -> bool { - self.is_open + fn should_open_taprv(&self) -> bool { + self.is_enabled } fn duct_zone_overheat_monitor( @@ -1214,20 +1228,6 @@ impl TrimAirSystemController SimulationElement - for TrimAirSystemController -{ - fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.hot_air_is_enabled_id, self.is_enabled()); - writer.write(&self.hot_air_is_open_id, self.is_open()); - } - - fn accept(&mut self, visitor: &mut T) { - self.failure.accept(visitor); - visitor.visit(self); - } -} - pub struct TrimAirValveSignal { target_open_amount: Ratio, } @@ -1243,7 +1243,7 @@ impl PneumaticValveSignal for TrimAirValveSignal { } #[derive(Clone, Copy)] -pub(super) struct TrimAirValveController { +pub struct TrimAirValveController { tav_open_allowed: bool, pid: PidController, } @@ -1303,13 +1303,8 @@ impl CabinFanController { Self { is_enabled: false } } - fn update( - &mut self, - acs_overhead: &impl AirConditioningOverheadShared, - operation_mode: ACSCActiveComputer, - ) { - self.is_enabled = - acs_overhead.cabin_fans_is_on() && !matches!(operation_mode, ACSCActiveComputer::None); + fn update(&mut self, acs_overhead: &impl AirConditioningOverheadShared) { + self.is_enabled = acs_overhead.cabin_fans_is_on(); } #[cfg(test)] @@ -1717,7 +1712,7 @@ mod acs_controller_tests { fn update( &mut self, context: &UpdateContext, - pack_flow_valve_signals: &impl PackFlowControllers, + pack_flow_valve_signals: [&impl PackFlowControllers; 2], engine_bleed: [&impl EngineCorrectedN1; 2], ) { self.engine_bleed @@ -1726,8 +1721,9 @@ mod acs_controller_tests { self.packs .iter_mut() .zip(self.engine_bleed.iter_mut()) - .for_each(|(pack, engine_bleed)| { - pack.update(context, engine_bleed, pack_flow_valve_signals) + .enumerate() + .for_each(|(id, (pack, engine_bleed))| { + pack.update(context, engine_bleed, pack_flow_valve_signals[id]) }); } @@ -1803,7 +1799,7 @@ mod acs_controller_tests { PneumaticPipe::new( Volume::new::(8.), Pressure::new::(44.), - ThermodynamicTemperature::new::(144.), + ThermodynamicTemperature::new::(200.), ) } else { PneumaticPipe::new( @@ -1819,7 +1815,7 @@ mod acs_controller_tests { PneumaticPipe::new( Volume::new::(16.), Pressure::new::(14.7), - ThermodynamicTemperature::new::(131.), + ThermodynamicTemperature::new::(200.), ) } else { PneumaticPipe::new( @@ -2082,7 +2078,7 @@ mod acs_controller_tests { } struct TestAircraft { - acsc: AirConditioningSystemController<2, 2>, + acsc: [AirConditioningSystemController<2, 2>; 2], acs_overhead: TestAcsOverhead, adirs: TestAdirs, air_conditioning_system: TestAirConditioningSystem, @@ -2115,18 +2111,26 @@ mod acs_controller_tests { let cabin_zones = [ZoneType::Cockpit, ZoneType::Cabin(1)]; Self { - acsc: AirConditioningSystemController::new( - context, - &cabin_zones, - vec![ - ElectricalBusType::DirectCurrent(1), - ElectricalBusType::AlternatingCurrent(1), - ], - vec![ - ElectricalBusType::DirectCurrent(2), - ElectricalBusType::AlternatingCurrent(2), - ], - ), + acsc: [ + AirConditioningSystemController::new( + context, + AcscId::Acsc1, + &cabin_zones, + vec![ + ElectricalBusType::DirectCurrent(1), + ElectricalBusType::AlternatingCurrent(1), + ], + ), + AirConditioningSystemController::new( + context, + AcscId::Acsc2, + &cabin_zones, + vec![ + ElectricalBusType::DirectCurrent(2), + ElectricalBusType::AlternatingCurrent(2), + ], + ), + ], acs_overhead: TestAcsOverhead::new(context), adirs: TestAdirs::new(), air_conditioning_system: TestAirConditioningSystem::new(), @@ -2150,7 +2154,7 @@ mod acs_controller_tests { lgciu1: TestLgciu::new(false), lgciu2: TestLgciu::new(false), cabin_air_simulation: TestCabinAirSimulation::new(context), - trim_air_system: TrimAirSystem::new(context, &cabin_zones), + trim_air_system: TrimAirSystem::new(context, &cabin_zones, vec![1]), powered_dc_source_1: TestElectricitySource::powered( context, PotentialOrigin::Battery(1), @@ -2267,22 +2271,28 @@ mod acs_controller_tests { fn update_after_power_distribution(&mut self, context: &UpdateContext) { let lgciu_gears_compressed = self.lgciu1.compressed() && self.lgciu2.compressed(); - self.acsc.update( + for acsc in self.acsc.iter_mut() { + acsc.update( + context, + &self.adirs, + &self.acs_overhead, + &self.cabin_air_simulation, + [&self.engine_1, &self.engine_2], + &self.engine_fire_push_buttons, + &self.pneumatic, + &self.pneumatic_overhead, + &self.pressurization, + &self.pressurization_overhead, + [&self.lgciu1, &self.lgciu2], + &self.trim_air_system, + ); + } + + self.pneumatic.update( context, - &self.adirs, - &self.acs_overhead, - &self.cabin_air_simulation, + [&self.acsc[0], &self.acsc[1]], [&self.engine_1, &self.engine_2], - &self.engine_fire_push_buttons, - &self.pneumatic, - &self.pneumatic_overhead, - &self.pressurization, - &self.pressurization_overhead, - [&self.lgciu1, &self.lgciu2], - &self.trim_air_system, ); - self.pneumatic - .update(context, &self.acsc, [&self.engine_1, &self.engine_2]); self.trim_air_system .mix_packs_air_update(self.pneumatic.packs()); @@ -2296,21 +2306,27 @@ mod acs_controller_tests { ); let pack_flow: [MassRate; 2] = [ - self.acsc.individual_pack_flow(Pack(1)), - self.acsc.individual_pack_flow(Pack(2)), + self.acsc[0].individual_pack_flow(), + self.acsc[1].individual_pack_flow(), ]; - let duct_demand_temperature = self.acsc.duct_demand_temperature(); - for (id, pack) in self.packs.iter_mut().enumerate() { - pack.update( + let duct_demand_temperature = vec![ + self.acsc[0].duct_demand_temperature()[0], + self.acsc[1].duct_demand_temperature()[1], + ]; + + [0, 1].iter().for_each(|&id| { + self.packs[id].update( pack_flow[id], &duct_demand_temperature, - self.acsc.zone_controller_failure(), + self.acsc[id].both_channels_failure(), ) - } + }); + + // Fan monitors by ACSC 2 for fan in self.cabin_fans.iter_mut() { fan.update( &self.cabin_air_simulation, - &self.acsc.cabin_fans_controller(), + &self.acsc[1].cabin_fans_controller(), ); } let mut mixer_intakes: Vec<&dyn OutletAir> = vec![&self.packs[0], &self.packs[1]]; @@ -2321,24 +2337,28 @@ mod acs_controller_tests { self.trim_air_system.update( context, - self.acsc.trim_air_pressure_regulating_valve_is_open(), + self.acsc[0].should_open_trim_air_pressure_regulating_valve() + && self.acsc[1].should_open_trim_air_pressure_regulating_valve(), &self.mixer_unit, - &self.acsc, + &[&self.acsc[0], &self.acsc[1]], ); - self.acs_overhead - .set_pack_pushbutton_fault(self.acsc.pack_fault_determination(&self.pneumatic)); + self.acs_overhead.set_pack_pushbutton_fault([ + self.acsc[0].pack_fault_determination(&self.pneumatic), + self.acsc[1].pack_fault_determination(&self.pneumatic), + ]); self.air_conditioning_system.update( self.trim_air_system.duct_temperature(), - self.acsc.individual_pack_flow(Pack(1)) + self.acsc.individual_pack_flow(Pack(2)), + self.acsc[0].individual_pack_flow() + self.acsc[1].individual_pack_flow(), self.trim_air_system.trim_air_outlet_pressure(), ); } } + impl SimulationElement for TestAircraft { fn accept(&mut self, visitor: &mut V) { - self.acsc.accept(visitor); + accept_iterable!(self.acsc, visitor); self.acs_overhead.accept(visitor); self.cabin_air_simulation.accept(visitor); self.pneumatic.accept(visitor); @@ -2453,49 +2473,49 @@ mod acs_controller_tests { fn ac_state_is_initialisation(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::Initialisation(_) ) } fn ac_state_is_on_ground(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::OnGround(_) ) } fn ac_state_is_begin_takeoff(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::BeginTakeOff(_) ) } fn ac_state_is_end_takeoff(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::EndTakeOff(_) ) } fn ac_state_is_in_flight(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::InFlight(_) ) } fn ac_state_is_begin_landing(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::BeginLanding(_) ) } fn ac_state_is_end_landing(&self) -> bool { matches!( - self.query(|a| a.acsc.aircraft_state), + self.query(|a| a.acsc[0].aircraft_state), AirConditioningStateManager::EndLanding(_) ) } @@ -2632,7 +2652,10 @@ mod acs_controller_tests { } fn duct_demand_temperature(&self) -> Vec { - self.query(|a| a.acsc.duct_demand_temperature()) + vec![ + self.query(|a| a.acsc[0].duct_demand_temperature()[0]), + self.query(|a| a.acsc[1].duct_demand_temperature())[1], + ] } fn duct_temperature(&self) -> Vec { @@ -2655,8 +2678,8 @@ mod acs_controller_tests { fn trim_air_system_controller_is_enabled(&self) -> bool { self.query(|a| { - a.acsc.trim_air_system_controller.is_enabled() - && a.acsc.trim_air_system_controller.is_open() + a.acsc[0].trim_air_pressure_regulating_valve_is_open() + && a.acsc[1].trim_air_pressure_regulating_valve_is_open() }) } @@ -2677,7 +2700,7 @@ mod acs_controller_tests { } fn mixer_unit_controller_is_enabled(&self) -> bool { - self.query(|a| a.acsc.cabin_fans_controller.is_enabled()) + self.query(|a| a.acsc[0].cabin_fans_controller.is_enabled()) } fn mixer_unit_outlet_air(&self) -> Air { @@ -2948,6 +2971,87 @@ mod acs_controller_tests { } } + mod air_conditioning_system_controller_tests { + use super::*; + + #[test] + fn trim_air_achieves_selected_temperature() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]) + .iterate(1000); + + assert!((test_bed.measured_temperature().get::() - 26.).abs() < 1.); + } + + #[test] + fn unpowering_one_lane_has_no_effect() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]) + .unpowered_ac_1_bus() + .unpowered_ac_2_bus() + .iterate(1000); + + assert!((test_bed.measured_temperature().get::() - 26.).abs() < 1.); + } + + #[test] + fn unpowering_both_lanes_shuts_off_pack() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]) + .unpowered_ac_2_bus() + .unpowered_dc_2_bus() + .iterate(1000); + + assert_eq!(test_bed.trim_air_valves_open_amount()[1], Ratio::default()); + assert!(!test_bed.trim_air_system_controller_is_enabled()); + assert!((test_bed.measured_temperature().get::() - 26.).abs() > 1.); + } + + #[test] + fn unpowering_opposite_acsc_doesnt_shut_off_pack() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]) + .unpowered_ac_1_bus() + .unpowered_dc_1_bus() + .iterate(1000); + + assert_ne!(test_bed.pack_flow(), MassRate::default()); + assert!((test_bed.measured_temperature().get::() - 24.).abs() < 1.); + } + } + mod zone_controller_tests { use super::*; @@ -3162,7 +3266,7 @@ mod acs_controller_tests { } #[test] - fn knobs_dont_affect_duct_temperature_when_primary_unpowered() { + fn knobs_dont_affect_duct_temperature_one_acsc_unpowered() { let mut test_bed = test_bed() .with() .both_packs_on() @@ -3180,88 +3284,6 @@ mod acs_controller_tests { assert!((test_bed.duct_temperature()[1].get::() - 24.).abs() < 1.); } - #[test] - fn unpowering_the_system_gives_control_to_packs() { - let mut test_bed = test_bed() - .with() - .both_packs_on() - .and() - .engine_idle() - .iterate(2) - .and() - .unpowered_dc_1_bus() - .unpowered_ac_1_bus() - .unpowered_dc_2_bus() - .unpowered_ac_2_bus() - .command_selected_temperature( - [ThermodynamicTemperature::new::(30.); 2], - ); - - test_bed = test_bed.iterate_with_delta(100, Duration::from_secs(10)); - - assert!( - (test_bed.duct_demand_temperature()[0].get::() - 20.).abs() < 1. - ); - assert!( - (test_bed.duct_demand_temperature()[1].get::() - 10.).abs() < 1. - ); - } - - #[test] - fn failing_primary_sets_the_zones_at_24c() { - let mut test_bed = test_bed() - .with() - .both_packs_on() - .and() - .engine_idle() - .and() - .command_selected_temperature( - [ThermodynamicTemperature::new::(30.); 2], - ); - - test_bed.fail(FailureType::ZoneController(ZoneControllerChannel::Primary)); - - test_bed = test_bed.iterate(1000); - - assert!((test_bed.duct_temperature()[1].get::() - 24.).abs() < 1.); - assert_eq!( - (test_bed.trim_air_valves_open_amount()[1]), - Ratio::default() - ); - } - - #[test] - fn failing_both_channels_sets_duct_demand_to_packs() { - let mut test_bed = test_bed() - .with() - .both_packs_on() - .and() - .engine_idle() - .iterate(2) - .and() - .command_selected_temperature( - [ThermodynamicTemperature::new::(30.); 2], - ); - - test_bed.fail(FailureType::ZoneController(ZoneControllerChannel::Primary)); - test_bed.fail(FailureType::ZoneController( - ZoneControllerChannel::Secondary, - )); - - test_bed = test_bed.iterate(1000); - - assert!( - (test_bed.duct_demand_temperature()[0].get::() - 20.).abs() < 1. - ); - assert!( - (test_bed.duct_demand_temperature()[1].get::() - 10.).abs() < 1. - ); - assert_eq!( - (test_bed.trim_air_valves_open_amount()[1]), - Ratio::default() - ); - } - #[test] fn failing_galley_fans_sets_duct_demand_to_15c() { let mut test_bed = test_bed() @@ -3289,7 +3311,7 @@ mod acs_controller_tests { } #[test] - fn unpowering_and_repowering_primary_behaves_as_expected() { + fn unpowering_and_repowering_acsc_behaves_as_expected() { let mut test_bed = test_bed() .with() .both_packs_on() @@ -3596,7 +3618,7 @@ mod acs_controller_tests { } #[test] - fn pack_flow_controller_is_unresponsive_when_unpowered() { + fn pack_flow_is_zero_when_acsc_unpowered() { let mut test_bed = test_bed() .with() .both_packs_on() @@ -3610,14 +3632,14 @@ mod acs_controller_tests { .unpowered_ac_1_bus() .unpowered_dc_2_bus() .unpowered_ac_2_bus(); - test_bed.command_ditching_on(); + test_bed = test_bed.iterate(2); - assert!(test_bed.pack_flow() > MassRate::default()); + assert_eq!(test_bed.pack_flow(), MassRate::default()); } #[test] - fn unpowering_ac_or_dc_unpowers_system() { + fn unpowering_one_acsc_shuts_down_one_pack_only() { let mut test_bed = test_bed() .with() .both_packs_on() @@ -3626,44 +3648,29 @@ mod acs_controller_tests { .iterate(2); assert!(test_bed.pack_flow() > MassRate::default()); - test_bed = test_bed.unpowered_dc_1_bus().unpowered_ac_2_bus(); - test_bed.command_ditching_on(); + let initial_flow = test_bed.pack_flow(); + + test_bed = test_bed.unpowered_dc_2_bus().unpowered_ac_2_bus(); + test_bed = test_bed.iterate(2); + + assert!(test_bed.pack_flow() < initial_flow); assert!(test_bed.pack_flow() > MassRate::default()); test_bed = test_bed - .powered_dc_1_bus() + .unpowered_dc_1_bus() .unpowered_ac_1_bus() - .unpowered_dc_2_bus() + .powered_dc_2_bus() .powered_ac_2_bus(); test_bed = test_bed.iterate(2); + assert!(test_bed.pack_flow() < initial_flow); assert!(test_bed.pack_flow() > MassRate::default()); - test_bed = test_bed.powered_ac_1_bus().powered_dc_2_bus().iterate(2); - assert_eq!(test_bed.pack_flow(), MassRate::default(),); - } - - #[test] - fn pack_flow_loses_optimization_when_secondary_computer_active() { - let mut test_bed = test_bed() - .with() - .both_packs_on() - .and() - .engine_idle() - .iterate(40); - - let initial_flow = test_bed.pack_flow(); - test_bed.command_apu_bleed_on(); - test_bed.run(); - assert!(test_bed.pack_flow() > initial_flow); - - test_bed = test_bed.unpowered_dc_1_bus().unpowered_ac_1_bus(); - test_bed.command_pack_flow_selector_position(0.); - test_bed = test_bed.iterate(20); - assert!( - (test_bed.pack_flow() - initial_flow).abs() - < MassRate::new::(0.1) - ); + test_bed = test_bed + .unpowered_ac_2_bus() + .unpowered_dc_2_bus() + .iterate(20); + assert_eq!(test_bed.pack_flow(), MassRate::default()); } #[test] @@ -3681,9 +3688,9 @@ mod acs_controller_tests { .unpowered_ac_1_bus() .unpowered_dc_2_bus() .unpowered_ac_2_bus(); - test_bed.command_ditching_on(); + test_bed = test_bed.iterate(2); - assert!(test_bed.pack_flow() > MassRate::default()); + assert_eq!(test_bed.pack_flow(), MassRate::default()); test_bed = test_bed .powered_dc_1_bus() @@ -3691,7 +3698,7 @@ mod acs_controller_tests { .powered_dc_2_bus() .powered_ac_2_bus() .iterate(2); - assert_eq!(test_bed.pack_flow(), MassRate::default()); + assert!(test_bed.pack_flow() > MassRate::default()); } } @@ -3729,21 +3736,24 @@ mod acs_controller_tests { .iterate(32); assert!(test_bed.trim_air_system_controller_is_enabled()); - test_bed = test_bed.hot_air_pb_on(false).and_run(); + test_bed = test_bed.hot_air_pb_on(false).iterate(2); assert!(!test_bed.trim_air_system_controller_is_enabled()); test_bed = test_bed.hot_air_pb_on(true); test_bed.command_pack_1_pb_position(false); - test_bed = test_bed.iterate(2); + test_bed = test_bed.iterate(4); assert!(test_bed.trim_air_system_controller_is_enabled()); // Pack 1 should be in start condition test_bed.command_pack_1_pb_position(true); - test_bed = test_bed.iterate(2); + test_bed = test_bed.iterate(4); assert!(!test_bed.trim_air_system_controller_is_enabled()); - // Secondary circuit - test_bed = test_bed.unpowered_dc_1_bus().unpowered_ac_1_bus().and_run(); + // ACSC 1 unpowered + test_bed = test_bed + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .iterate(2); assert!(!test_bed.trim_air_system_controller_is_enabled()); test_bed = test_bed.powered_dc_1_bus().powered_ac_1_bus().iterate(32); @@ -3778,12 +3788,15 @@ mod acs_controller_tests { test_bed = test_bed.cab_fans_pb_on(false).and_run(); assert!(!test_bed.mixer_unit_controller_is_enabled()); - // Unpower both circuits + // Unpowering ACSC doesn't affect fans test_bed = test_bed.cab_fans_pb_on(true); - test_bed = test_bed.unpowered_dc_1_bus().unpowered_ac_2_bus().and_run(); - assert!(!test_bed.mixer_unit_controller_is_enabled()); + test_bed = test_bed + .unpowered_dc_1_bus() + .unpowered_ac_1_bus() + .unpowered_dc_2_bus() + .unpowered_ac_2_bus() + .and_run(); - test_bed = test_bed.powered_dc_1_bus().powered_ac_2_bus().and_run(); assert!(test_bed.mixer_unit_controller_is_enabled()); } diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 3463a3e7690..742b84f17c5 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -1,6 +1,4 @@ -use self::acs_controller::{ - AirConditioningSystemController, CabinFansSignal, Pack, TrimAirValveController, -}; +use self::acs_controller::{CabinFansSignal, Pack, TrimAirValveController}; use crate::{ failures::{Failure, FailureType}, @@ -56,6 +54,10 @@ pub trait PackFlowControllers { fn pack_flow_controller(&self, pack_id: usize) -> &Self::PackFlowControllerSignal; } +pub trait TrimAirControllers { + fn trim_air_valve_controllers(&self, zone_id: usize) -> TrimAirValveController; +} + pub struct PackFlowValveSignal { target_open_amount: Ratio, } @@ -201,6 +203,72 @@ pub trait CabinPressure { fn cabin_pressure(&self) -> Pressure; } +// Future work this can be different types of failure. +enum OperatingChannelFault { + NoFault, + Fault, +} + +#[derive(Clone, Copy)] +enum Channel { + ChannelOne, + ChannelTwo, +} + +impl From for usize { + fn from(value: Channel) -> Self { + match value { + Channel::ChannelOne => 1, + Channel::ChannelTwo => 2, + } + } +} + +struct OperatingChannel { + channel_id: Channel, + powered_by: ElectricalBusType, + is_powered: bool, + fault: OperatingChannelFault, +} + +impl OperatingChannel { + fn new(id: usize, powered_by: ElectricalBusType) -> Self { + let channel_id: Channel = { + match id { + 1 => Channel::ChannelOne, + 2 => Channel::ChannelTwo, + _ => panic!("Operating Channel out of bounds"), + } + }; + Self { + channel_id, + powered_by, + is_powered: false, + fault: OperatingChannelFault::NoFault, + } + } + + fn has_fault(&self) -> bool { + matches!(self.fault, OperatingChannelFault::Fault) + } + + fn id(&self) -> Channel { + self.channel_id + } +} + +impl SimulationElement for OperatingChannel { + fn receive_power(&mut self, buses: &impl ElectricalBuses) { + self.is_powered = buses.is_powered(self.powered_by); + // For now the channel faults only when it's unpowered. In the future we can add other types of failure + if !self.is_powered { + self.fault = OperatingChannelFault::Fault; + } else { + self.fault = OperatingChannelFault::NoFault; + } + } +} + pub trait PressurizationConstants { const CABIN_VOLUME_CUBIC_METER: f64; const COCKPIT_VOLUME_CUBIC_METER: f64; @@ -439,14 +507,14 @@ impl AirConditioningPack { &mut self, pack_flow: MassRate, duct_demand: &[ThermodynamicTemperature], - zone_controller_failure: bool, + acsc_failure: bool, ) { self.outlet_air.set_flow_rate(pack_flow); let min_temp = duct_demand .iter() .fold(f64::INFINITY, |acc, &t| acc.min(t.get::())); - if zone_controller_failure { + if acsc_failure { if matches!(self.pack_id, Pack(1)) { self.outlet_air .set_temperature(ThermodynamicTemperature::new::(20.)); @@ -467,9 +535,44 @@ impl OutletAir for AirConditioningPack { } } +struct TrimAirPressureRegulatingValve { + is_open: bool, + failure: Failure, +} + +impl TrimAirPressureRegulatingValve { + fn new(id: usize) -> Self { + Self { + is_open: false, + failure: Failure::new(FailureType::HotAir(id)), + } + } + + fn update(&mut self, should_open_taprv: bool) { + // When a failure is active the TAPRV is unresponsive - it stays in the same state as it was + self.is_open = if !self.failure.is_active() { + should_open_taprv + } else { + self.is_open + }; + } + + fn is_open(&self) -> bool { + self.is_open + } +} + +impl SimulationElement for TrimAirPressureRegulatingValve { + fn accept(&mut self, visitor: &mut T) { + self.failure.accept(visitor); + visitor.visit(self); + } +} + pub struct TrimAirSystem { duct_temperature_id: [VariableIdentifier; ZONES], + trim_air_pressure_regulating_valves: Vec, trim_air_valves: [TrimAirValve; ZONES], // These are not a real components of the system, but a tool to simulate the mixing of air pack_mixer_container: PneumaticPipe, @@ -480,13 +583,22 @@ pub struct TrimAirSystem { } impl TrimAirSystem { - pub fn new(context: &mut InitContext, cabin_zone_ids: &[ZoneType; ZONES]) -> Self { + pub fn new( + context: &mut InitContext, + cabin_zone_ids: &[ZoneType; ZONES], + taprv_ids: Vec, + ) -> Self { let duct_temperature_id = cabin_zone_ids.map(|id| context.get_identifier(format!("COND_{}_DUCT_TEMP", id))); + let trim_air_pressure_regulating_valves = taprv_ids + .iter() + .map(|id| TrimAirPressureRegulatingValve::new(*id)) + .collect::>(); Self { duct_temperature_id, + trim_air_pressure_regulating_valves, trim_air_valves: cabin_zone_ids.map(|id| TrimAirValve::new(context, &id)), pack_mixer_container: PneumaticPipe::new( Volume::new::(4.), @@ -503,16 +615,23 @@ impl TrimAirSystem { pub fn update( &mut self, context: &UpdateContext, - trim_air_pressure_regulating_valve_open: bool, + should_open_taprv: bool, mixer_air: &MixerUnit, - tav_controller: &AirConditioningSystemController, + tav_controller: &[&impl TrimAirControllers], ) { + // FIXME: In the A380 should_open_taprv needs to be a vector + self.trim_air_pressure_regulating_valves + .iter_mut() + .for_each(|taprv| taprv.update(should_open_taprv)); + for (id, tav) in self.trim_air_valves.iter_mut().enumerate() { tav.update( context, - trim_air_pressure_regulating_valve_open, + self.trim_air_pressure_regulating_valves + .iter() + .any(|taprv| taprv.is_open()), &mut self.pack_mixer_container, - tav_controller.trim_air_valve_controllers(id), + tav_controller[id].trim_air_valve_controllers(id), ); self.trim_air_mixers[id].update(vec![tav, &mixer_air.mixer_unit_individual_outlet(id)]); } @@ -574,6 +693,12 @@ impl TrimAirSystem { self.outlet_air.pressure() > Pressure::new::(20.) } + fn trim_air_pressure_regulating_valve_is_open(&self) -> bool { + self.trim_air_pressure_regulating_valves + .iter() + .any(|taprv| taprv.is_open()) + } + #[cfg(test)] fn trim_air_valves_open_amount(&self) -> [Ratio; ZONES] { self.trim_air_valves @@ -598,6 +723,7 @@ impl DuctTemperature for TrimAirSystem impl SimulationElement for TrimAirSystem { fn accept(&mut self, visitor: &mut V) { + accept_iterable!(self.trim_air_pressure_regulating_valves, visitor); accept_iterable!(self.trim_air_valves, visitor); self.duct_high_pressure.accept(visitor); diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index 95370413f73..ba6131d4513 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -1,4 +1,4 @@ -use crate::air_conditioning::{acs_controller::ZoneControllerChannel, ZoneType}; +use crate::air_conditioning::ZoneType; use crate::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId, @@ -13,7 +13,6 @@ pub enum FailureType { TrimAirFault(ZoneType), TrimAirHighPressure, GalleyFans, - ZoneController(ZoneControllerChannel), TransformerRectifier(usize), ReservoirLeak(HydraulicColor), ReservoirAirLeak(HydraulicColor), From bbab85365e37274cc0a4ffc5a27fefc3ad896888 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Mon, 14 Aug 2023 22:07:17 +0100 Subject: [PATCH 10/18] feat: acsc failures --- fbw-a32nx/src/systems/failures/src/a320.ts | 28 +-- .../instruments/src/Common/EWDMessages.tsx | 16 ++ .../EFB/failures-orchestrator-provider.tsx | 12 +- .../systems/instruments/src/EWD/PseudoFWC.ts | 159 +++++++++++++++--- .../instruments/src/SD/Pages/Cond/Cond.tsx | 9 +- .../a320_systems/src/air_conditioning.rs | 18 +- .../wasm/systems/a320_systems_wasm/src/lib.rs | 48 +++--- .../a380_systems/src/air_conditioning/mod.rs | 8 +- .../src/air_conditioning/acs_controller.rs | 111 +++++++++--- .../systems/src/air_conditioning/mod.rs | 46 +++-- .../wasm/systems/systems/src/failures/mod.rs | 3 +- 11 files changed, 343 insertions(+), 115 deletions(-) diff --git a/fbw-a32nx/src/systems/failures/src/a320.ts b/fbw-a32nx/src/systems/failures/src/a320.ts index 5be77ad08ca..fc843dea56f 100644 --- a/fbw-a32nx/src/systems/failures/src/a320.ts +++ b/fbw-a32nx/src/systems/failures/src/a320.ts @@ -1,19 +1,21 @@ // One can rightfully argue that this constant shouldn't be located in @flybywiresim/failures. // Once we create an A320 specific package, such as @flybywiresim/a320, we can move it there. export const A320Failure = Object.freeze({ - CabinFan1Failure: 21000, - CabinFan2Failure: 21001, - HotAir: 21002, - CkptTrimAirFailure: 21003, - FwdTrimAirFailure: 21004, - AftTrimAirFailure: 21005, - CkptDuctOvht: 21006, - FwdDuctOvht: 21007, - AftDuctOvht: 21008, - LabGalleyFan: 21009, - ZoneControllerPrimary: 21010, - ZoneControllerSecondary: 21011, - TrimAirHighPressure: 21012, + Acsc1Lane1: 21000, + Acsc1Lane2: 21001, + Acsc2Lane1: 21002, + Acsc2Lane2: 21003, + HotAir: 21004, + TrimAirHighPressure: 21005, + CkptTrimAirFailure: 21006, + FwdTrimAirFailure: 21007, + AftTrimAirFailure: 21008, + CkptDuctOvht: 21009, + FwdDuctOvht: 21010, + AftDuctOvht: 21011, + CabinFan1Failure: 21012, + CabinFan2Failure: 21013, + LabGalleyFan: 21014, Fac1Failure: 22000, Fac2Failure: 22001, diff --git a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx index 5613f2bd262..35dd9f017c5 100644 --- a/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx +++ b/fbw-a32nx/src/systems/instruments/src/Common/EWDMessages.tsx @@ -122,8 +122,24 @@ const EWDMessages = { '213122114': '\x1b<5m MAX FL.....100/MEA-MORA', '213122115': '\x1b<7m .IF CAB ALT>14000FT:', '213122116': '\x1b<5m -PAX OXY MASKS...MAN ON', + '216120601': '\x1b<4m\x1b4mAIR\x1bm PACK 1+2 FAULT', + '216120602': '\x1b<5m -PACK 1.............OFF', + '216120603': '\x1b<5m -PACK 2.............OFF', + '216120604': '\x1b<5m -DES TO FL 100/MEA-MORA', + '216120605': '\x1b<7m .WHEN DIFF PR <1 PSI', + '216120606': '\x1b<7m AND FL BELOW 100:', + '216120607': '\x1b<5m -RAM AIR.............ON', + '216120608': '\x1b<5m MAX FL.....100/MEA-MORA', + '216120201': '\x1b<4m\x1b4mAIR\x1bm PACK 1 FAULT', + '216120202': '\x1b<5m -PACK 1.............OFF', + '216120301': '\x1b<4m\x1b4mAIR\x1bm PACK 2 FAULT', + '216120302': '\x1b<5m -PACK 2.............OFF', '216120701': '\x1b<4m\x1b4mAIR\x1bm PACK 1 OFF', '216120801': '\x1b<4m\x1b4mAIR\x1bm PACK 2 OFF', + '216129101': '\x1b<4m\x1b4mAIR\x1bm COND CTL 1-A FAULT', + '216129401': '\x1b<4m\x1b4mAIR\x1bm COND CTL 2-A FAULT', + '216129701': '\x1b<4m\x1b4mAIR\x1bm COND CTL 1-B FAULT', + '216129801': '\x1b<4m\x1b4mAIR\x1bm COND CTL 2-B FAULT', '216321001': '\x1b<4m\x1b4mCOND\x1bm CKPT DUCT OVHT', '216321002': '\x1b<7m .WHEN DUCT TEMP<70 DEG C:', '216321003': '\x1b<7m .WHEN DUCT TEMP<158 DEG F:', diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index a8ba6567a73..f83ec6cab4b 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -15,19 +15,21 @@ interface FailuresOrchestratorContext { } const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ - [21, A320Failure.CabinFan1Failure, 'Cabin Fan 1'], - [21, A320Failure.CabinFan2Failure, 'Cabin Fan 2'], + [21, A320Failure.Acsc1Lane1, 'ACSC 1 Line 1'], + [21, A320Failure.Acsc1Lane2, 'ACSC 1 Line 2'], + [21, A320Failure.Acsc2Lane1, 'ACSC 2 Line 1'], + [21, A320Failure.Acsc2Lane2, 'ACSC 2 Line 2'], [21, A320Failure.HotAir, 'Trim Air Pressure Regulating Valve'], + [21, A320Failure.TrimAirHighPressure, 'Trim Air System High Pressure'], [21, A320Failure.CkptTrimAirFailure, 'Cockpit Trim Air Valve'], [21, A320Failure.FwdTrimAirFailure, 'Forward Zone Trim Air Valve'], [21, A320Failure.AftTrimAirFailure, 'Aft Zone Trim Air Valve'], [21, A320Failure.CkptDuctOvht, 'Cockpit Duct Overheat'], [21, A320Failure.FwdDuctOvht, 'Forward Zone Duct Overheat'], [21, A320Failure.AftDuctOvht, 'Aft Zone Duct Overheat'], + [21, A320Failure.CabinFan1Failure, 'Cabin Fan 1'], + [21, A320Failure.CabinFan2Failure, 'Cabin Fan 2'], [21, A320Failure.LabGalleyFan, 'Extraction Fan of lavatory and galley'], - [21, A320Failure.ZoneControllerPrimary, 'Zone Controller primary channel'], - [21, A320Failure.ZoneControllerSecondary, 'Zone Controller secondary channel'], - [21, A320Failure.TrimAirHighPressure, 'Trim Air System High Pressure'], [22, A320Failure.Fac1Failure, 'FAC 1'], [22, A320Failure.Fac2Failure, 'FAC 2'], diff --git a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts index 53dd34f4dce..37bb327eb0f 100644 --- a/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts +++ b/fbw-a32nx/src/systems/instruments/src/EWD/PseudoFWC.ts @@ -118,6 +118,22 @@ export class PseudoFWC { private readonly excessPressure = Subject.create(false); + private readonly acsc1Lane1Fault = Subject.create(false); + + private readonly acsc1Lane2Fault = Subject.create(false); + + private readonly acsc2Lane1Fault = Subject.create(false); + + private readonly acsc2Lane2Fault = Subject.create(false); + + private readonly acsc1Fault = Subject.create(false); + + private readonly acsc2Fault = Subject.create(false); + + private readonly pack1And2Fault = Subject.create(false); + + private readonly ramAirOn = Subject.create(false); + private readonly hotAirDisagrees = Subject.create(false); private readonly hotAirOpen = Subject.create(false); @@ -1073,10 +1089,23 @@ export class PseudoFWC { this.acsc2DiscreteWord1.setFromSimVar('L:A32NX_COND_ACSC_2_DISCRETE_WORD_1'); this.acsc2DiscreteWord2.setFromSimVar('L:A32NX_COND_ACSC_2_DISCRETE_WORD_2'); + this.acsc1Lane1Fault.set(this.acsc1DiscreteWord1.bitValueOr(21, false)); + this.acsc1Lane2Fault.set(this.acsc1DiscreteWord1.bitValueOr(22, false)); + this.acsc2Lane1Fault.set(this.acsc2DiscreteWord1.bitValueOr(21, false)); + this.acsc2Lane2Fault.set(this.acsc2DiscreteWord1.bitValueOr(22, false)); + + const acsc1FT = this.acsc1DiscreteWord1.isFailureWarning(); + const acsc2FT = this.acsc2DiscreteWord1.isFailureWarning(); + this.acsc1Fault.set(acsc1FT && !acsc2FT); + this.acsc2Fault.set(!acsc1FT && acsc2FT); + const acscBothFault = acsc1FT && acsc2FT; + + this.ramAirOn.set(SimVar.GetSimVarValue('L:A32NX_AIRCOND_RAMAIR_TOGGLE', 'bool')); + this.cabFanHasFault1.set(this.acsc1DiscreteWord1.bitValueOr(25, false) || this.acsc2DiscreteWord1.bitValueOr(25, false)); this.cabFanHasFault2.set(this.acsc1DiscreteWord1.bitValueOr(26, false) || this.acsc2DiscreteWord1.bitValueOr(26, false)); - this.hotAirDisagrees.set(this.acsc1DiscreteWord1.bitValueOr(27, false) || this.acsc2DiscreteWord1.bitValueOr(27, false)); + this.hotAirDisagrees.set(this.acsc1DiscreteWord1.bitValueOr(27, false) && this.acsc2DiscreteWord1.bitValueOr(27, false)); this.hotAirOpen.set(!this.acsc1DiscreteWord1.bitValueOr(20, false) || !this.acsc2DiscreteWord1.bitValueOr(20, false)); this.hotAirPbOn.set(this.acsc1DiscreteWord1.bitValueOr(23, false) || this.acsc2DiscreteWord1.bitValueOr(23, false)); @@ -1114,6 +1143,7 @@ export class PseudoFWC { this.packOffBleedAvailable2.write((eng2Bleed === 1 && !eng2BleedPbFault) || crossfeed === 1, deltaTime); this.packOffNotFailed1Status.set(this.packOffNotFailed1.write(!this.pack1On.get() && !pack1Fault && this.packOffBleedAvailable1.read() && this.fwcFlightPhase.get() === 6, deltaTime)); this.packOffNotFailed2Status.set(this.packOffNotFailed2.write(!this.pack2On.get() && !pack2Fault && this.packOffBleedAvailable2.read() && this.fwcFlightPhase.get() === 6, deltaTime)); + this.pack1And2Fault.set(acscBothFault || (this.packOffNotFailed1Status.get() && this.acsc2Fault.get()) || (this.packOffNotFailed2Status.get() && this.acsc1Fault.get())); /* OTHER STUFF */ @@ -2229,6 +2259,111 @@ export class PseudoFWC { sysPage: 2, side: 'LEFT', }, + 2161206: { // PACK 1+2 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.pack1And2Fault, + whichCodeToReturn: () => [ + 0, + this.pack1On.get() ? 1 : null, + this.pack2On.get() ? 2 : null, + !this.aircraftOnGround.get() && !this.ramAirOn.get() ? 3 : null, + !this.aircraftOnGround.get() && !this.ramAirOn.get() ? 4 : null, + !this.aircraftOnGround.get() && !this.ramAirOn.get() ? 5 : null, + !this.aircraftOnGround.get() && !this.ramAirOn.get() ? 6 : null, + !this.aircraftOnGround.get() ? 7 : null, + ], + codesToReturn: ['216120601', '216120602', '216120603', '216120604', '216120605', '216120606', '216120607', '216120608'], + memoInhibit: () => false, + failure: 2, + sysPage: 1, + side: 'LEFT', + }, + 2161202: { // PACK 1 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.acsc1Fault, + whichCodeToReturn: () => [ + 0, + this.pack1On.get() ? 1 : null, + ], + codesToReturn: ['216120201', '216120202'], + memoInhibit: () => false, + failure: 2, + sysPage: 1, + side: 'LEFT', + }, + 2161203: { // PACK 2 FAULT + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: this.acsc2Fault, + whichCodeToReturn: () => [ + 0, + this.pack2On.get() ? 1 : null, + ], + codesToReturn: ['216120301', '216120302'], + memoInhibit: () => false, + failure: 2, + sysPage: 1, + side: 'LEFT', + }, + 2161207: { // PACK 1 ABNORMALLY OFF + flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], + simVarIsActive: this.packOffNotFailed1Status, + whichCodeToReturn: () => [0], + codesToReturn: ['216120701'], + memoInhibit: () => false, + failure: 2, + sysPage: 1, + side: 'LEFT', + }, + 2161208: { // PACK 2 ABNORMALLY OFF + flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], + simVarIsActive: this.packOffNotFailed2Status, + whichCodeToReturn: () => [0], + codesToReturn: ['216120801'], + memoInhibit: () => false, + failure: 2, + sysPage: 1, + side: 'LEFT', + }, + 2161291: { // COND CTL 1-A FAULT + flightPhaseInhib: [2, 3, 4, 5, 6, 7, 8, 9], + simVarIsActive: MappedSubject.create(([acsc1Lane1Fault, acsc1Lane2Fault]) => acsc1Lane1Fault && !acsc1Lane2Fault, this.acsc1Lane1Fault, this.acsc1Lane2Fault), + whichCodeToReturn: () => [0], + codesToReturn: ['216129101'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, + 2161297: { // COND CTL 1-B FAULT + flightPhaseInhib: [2, 3, 4, 5, 6, 7, 8, 9], + simVarIsActive: MappedSubject.create(([acsc1Lane1Fault, acsc1Lane2Fault]) => !acsc1Lane1Fault && acsc1Lane2Fault, this.acsc1Lane1Fault, this.acsc1Lane2Fault), + whichCodeToReturn: () => [0], + codesToReturn: ['216129701'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, + 2161294: { // COND CTL 2-A FAULT + flightPhaseInhib: [2, 3, 4, 5, 6, 7, 8, 9], + simVarIsActive: MappedSubject.create(([acsc2Lane1Fault, acsc2Lane2Fault]) => acsc2Lane1Fault && !acsc2Lane2Fault, this.acsc2Lane1Fault, this.acsc2Lane2Fault), + whichCodeToReturn: () => [0], + codesToReturn: ['216129401'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, + 2161298: { // COND CTL 2-B FAULT + flightPhaseInhib: [2, 3, 4, 5, 6, 7, 8, 9], + simVarIsActive: MappedSubject.create(([acsc2Lane1Fault, acsc2Lane2Fault]) => !acsc2Lane1Fault && acsc2Lane2Fault, this.acsc2Lane1Fault, this.acsc2Lane2Fault), + whichCodeToReturn: () => [0], + codesToReturn: ['216129801'], + memoInhibit: () => false, + failure: 1, + sysPage: -1, + side: 'LEFT', + }, 2163210: { // CKPT DUCT OVHT flightPhaseInhib: [3, 4, 5, 7, 8], simVarIsActive: this.ckptDuctOvht, @@ -2294,7 +2429,7 @@ export class PseudoFWC { sysPage: 7, side: 'LEFT', }, - 216330: { // TRIM AIR SYS FAULT + 2163305: { // TRIM AIR SYS FAULT flightPhaseInhib: [3, 4, 5, 7, 8], simVarIsActive: this.trimAirFault, whichCodeToReturn: () => [0, @@ -2346,26 +2481,6 @@ export class PseudoFWC { sysPage: -1, side: 'LEFT', }, - 2161207: { // PACK 1 ABNORMALLY OFF - flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], - simVarIsActive: this.packOffNotFailed1Status, - whichCodeToReturn: () => [0], - codesToReturn: ['216120701'], - memoInhibit: () => false, - failure: 2, - sysPage: 1, - side: 'LEFT', - }, - 2161208: { // PACK 2 ABNORMALLY OFF - flightPhaseInhib: [1, 2, 3, 4, 5, 7, 8, 9, 10], - simVarIsActive: this.packOffNotFailed2Status, - whichCodeToReturn: () => [0], - codesToReturn: ['216120801'], - memoInhibit: () => false, - failure: 2, - sysPage: 1, - side: 'LEFT', - }, 3200060: { // NW ANTI SKID INACTIVE flightPhaseInhib: [4, 5], simVarIsActive: this.antiskidActive.map((v) => !v), diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 69763b8baf4..4888cffeea5 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -14,7 +14,7 @@ export const CondPage = () => { const gaugeOffset = -43; // Gauges range is from -43 degree to +43 degree const Acsc1DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); - const Acsc2DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); + const Acsc2DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_2_DISCRETE_WORD_1'); const AcscDiscreteWord1 = !Acsc1DiscreteWord1.isFailureWarning() ? Acsc1DiscreteWord1 : Acsc2DiscreteWord1; // TODO: If both Sign Status are Failure Warning or No Computed Data, the whole page should display XX's @@ -38,11 +38,6 @@ export const CondPage = () => { const cabFanHasFault1 = AcscDiscreteWord1.getBitValueOr(25, false); const cabFanHasFault2 = AcscDiscreteWord1.getBitValueOr(26, false); - const zoneControllerPrimaryFault = AcscDiscreteWord1.getBitValueOr(21, false); - const zoneControllerBothChannelFault = AcscDiscreteWord1.getBitValueOr(21, false) && AcscDiscreteWord1.getBitValueOr(22, false); - - const altnMode = zoneControllerPrimaryFault && !zoneControllerBothChannelFault; - return ( {/* Title and unit */} @@ -53,8 +48,6 @@ export const CondPage = () => { °C FAN FAN - ALTN MODE - PACK REG {/* Plane shape */} diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index e9d08cb57fa..11cde53aec3 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -6,7 +6,7 @@ use systems::{ cabin_pressure_controller::CabinPressureController, pressure_valve::{OutflowValve, SafetyValve}, AdirsToAirCondInterface, Air, AirConditioningOverheadShared, AirConditioningPack, CabinFan, - DuctTemperature, MixerUnit, OutflowValveSignal, OutletAir, OverheadFlowSelector, + Channel, DuctTemperature, MixerUnit, OutflowValveSignal, OutletAir, OverheadFlowSelector, PackFlowControllers, PressurizationConstants, PressurizationOverheadShared, TrimAirSystem, ZoneType, }, @@ -256,13 +256,21 @@ impl A320AirConditioningSystem { pub(crate) fn new(context: &mut InitContext, cabin_zones: &[ZoneType; 3]) -> Self { Self { acs_interface: [ - AirConditioningSystemInterfaceUnit::new(context, AcscId::Acsc1, cabin_zones), - AirConditioningSystemInterfaceUnit::new(context, AcscId::Acsc2, cabin_zones), + AirConditioningSystemInterfaceUnit::new( + context, + AcscId::Acsc1(Channel::ChannelOne), + cabin_zones, + ), + AirConditioningSystemInterfaceUnit::new( + context, + AcscId::Acsc2(Channel::ChannelOne), + cabin_zones, + ), ], acsc: [ AirConditioningSystemController::new( context, - AcscId::Acsc1, + AcscId::Acsc1(Channel::ChannelOne), cabin_zones, vec![ ElectricalBusType::DirectCurrent(1), @@ -271,7 +279,7 @@ impl A320AirConditioningSystem { ), AirConditioningSystemController::new( context, - AcscId::Acsc2, + AcscId::Acsc2(Channel::ChannelOne), cabin_zones, vec![ ElectricalBusType::DirectCurrent(2), diff --git a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs index 5b004a68421..9294e731a6c 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems_wasm/src/lib.rs @@ -24,7 +24,7 @@ use reversers::reversers; use rudder::rudder; use spoilers::spoilers; use std::error::Error; -use systems::air_conditioning::ZoneType; +use systems::air_conditioning::{acs_controller::AcscId, Channel, ZoneType}; use systems::failures::FailureType; use systems::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, ElectricalBusType, GearActuatorId, @@ -62,25 +62,33 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { ])? .with_auxiliary_power_unit(Variable::named("OVHD_APU_START_PB_IS_AVAILABLE"), 8)? .with_failures(vec![ - (21_000, FailureType::CabinFan(1)), - (21_001, FailureType::CabinFan(2)), - (21_002, FailureType::HotAir(1)), - (21_003, FailureType::TrimAirFault(ZoneType::Cockpit)), - (21_004, FailureType::TrimAirFault(ZoneType::Cabin(1))), - (21_005, FailureType::TrimAirFault(ZoneType::Cabin(2))), - (21_006, FailureType::TrimAirOverheat(ZoneType::Cockpit)), - (21_007, FailureType::TrimAirOverheat(ZoneType::Cabin(1))), - (21_008, FailureType::TrimAirOverheat(ZoneType::Cabin(2))), - (21_009, FailureType::GalleyFans), - // ( - // 21_010, - // FailureType::ZoneController(ZoneControllerChannel::Primary), - // ), - // ( - // 21_011, - // FailureType::ZoneController(ZoneControllerChannel::Secondary), - // ), - (21_012, FailureType::TrimAirHighPressure), + ( + 21_000, + FailureType::Acsc(AcscId::Acsc1(Channel::ChannelOne)), + ), + ( + 21_001, + FailureType::Acsc(AcscId::Acsc1(Channel::ChannelTwo)), + ), + ( + 21_002, + FailureType::Acsc(AcscId::Acsc2(Channel::ChannelOne)), + ), + ( + 21_003, + FailureType::Acsc(AcscId::Acsc2(Channel::ChannelTwo)), + ), + (21_004, FailureType::HotAir(1)), + (21_005, FailureType::TrimAirHighPressure), + (21_006, FailureType::TrimAirFault(ZoneType::Cockpit)), + (21_007, FailureType::TrimAirFault(ZoneType::Cabin(1))), + (21_008, FailureType::TrimAirFault(ZoneType::Cabin(2))), + (21_009, FailureType::TrimAirOverheat(ZoneType::Cockpit)), + (21_010, FailureType::TrimAirOverheat(ZoneType::Cabin(1))), + (21_011, FailureType::TrimAirOverheat(ZoneType::Cabin(2))), + (21_012, FailureType::CabinFan(1)), + (21_013, FailureType::CabinFan(2)), + (21_014, FailureType::GalleyFans), (24_000, FailureType::TransformerRectifier(1)), (24_001, FailureType::TransformerRectifier(2)), (24_002, FailureType::TransformerRectifier(3)), diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index e967774af9b..28deb9b4635 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -7,9 +7,9 @@ use systems::{ full_digital_agu_controller::FullDigitalAGUController, pressure_valve::{OutflowValve, SafetyValve}, AdirsToAirCondInterface, Air, AirConditioningOverheadShared, AirConditioningPack, CabinFan, - DuctTemperature, MixerUnit, OutflowValveSignal, OutletAir, OverheadFlowSelector, PackFlow, - PackFlowControllers, PressurizationConstants, PressurizationOverheadShared, TrimAirSystem, - ZoneType, + Channel, DuctTemperature, MixerUnit, OutflowValveSignal, OutletAir, OverheadFlowSelector, + PackFlow, PackFlowControllers, PressurizationConstants, PressurizationOverheadShared, + TrimAirSystem, ZoneType, }, overhead::{ AutoManFaultPushButton, NormalOnPushButton, OnOffFaultPushButton, OnOffPushButton, @@ -319,7 +319,7 @@ impl A380AirConditioningSystem { Self { acsc: AirConditioningSystemController::new( context, - AcscId::Acsc1, + AcscId::Acsc1(Channel::ChannelOne), cabin_zones, vec![ ElectricalBusType::DirectCurrent(1), diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 1f66276a57e..575d92066cb 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -29,24 +29,17 @@ use uom::si::{ velocity::knot, }; -#[derive(Eq, PartialEq, Clone, Copy)] -pub enum ACSCActiveComputer { - Primary, - Secondary, - None, -} - #[derive(Eq, PartialEq, Clone, Copy)] pub enum AcscId { - Acsc1, - Acsc2, + Acsc1(Channel), + Acsc2(Channel), } impl From for usize { fn from(value: AcscId) -> Self { match value { - AcscId::Acsc1 => 1, - AcscId::Acsc2 => 2, + AcscId::Acsc1(_) => 1, + AcscId::Acsc2(_) => 2, } } } @@ -54,8 +47,8 @@ impl From for usize { impl Display for AcscId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - AcscId::Acsc1 => write!(f, "1"), - AcscId::Acsc2 => write!(f, "2"), + AcscId::Acsc1(_) => write!(f, "1"), + AcscId::Acsc2(_) => write!(f, "2"), } } } @@ -66,6 +59,7 @@ enum AcscFault { BothChannelsFault, } +/// A320 ACSC P/N S1803A0001-xx pub struct AirConditioningSystemController { id: AcscId, active_channel: OperatingChannel, @@ -87,12 +81,23 @@ impl AirConditioningSystemController, ) -> Self { + let failure_types: [FailureType; 2] = match id { + AcscId::Acsc1(_) => [ + FailureType::Acsc(AcscId::Acsc1(Channel::ChannelOne)), + FailureType::Acsc(AcscId::Acsc1(Channel::ChannelTwo)), + ], + AcscId::Acsc2(_) => [ + FailureType::Acsc(AcscId::Acsc2(Channel::ChannelOne)), + FailureType::Acsc(AcscId::Acsc2(Channel::ChannelTwo)), + ], + }; + Self { id, // FIXME Find correct power supply for ACSC - active_channel: OperatingChannel::new(1, powered_by[0]), - stand_by_channel: OperatingChannel::new(2, powered_by[1]), + active_channel: OperatingChannel::new(1, failure_types[0], powered_by[0]), + stand_by_channel: OperatingChannel::new(2, failure_types[1], powered_by[1]), aircraft_state: AirConditioningStateManager::new(), zone_controller: Self::zone_controller_initiation(id, cabin_zone_ids), @@ -109,7 +114,7 @@ impl AirConditioningSystemController Vec> { // ACSC 1 regulates the cockpit temperature and ACSC 2 the cabin zones - if matches!(id, AcscId::Acsc1) { + if matches!(id, AcscId::Acsc1(_)) { vec![ZoneController::new(&cabin_zone_ids[0])] } else { cabin_zone_ids[1..ZONES] @@ -180,6 +185,9 @@ impl AirConditioningSystemController { if self.stand_by_channel.has_fault() { @@ -237,6 +245,7 @@ impl AirConditioningSystemController bool { self.pack_flow_controller .fcv_status_determination(pneumatic) + || self.both_channels_failure() } pub fn cabin_fans_controller(&self) -> CabinFanController { @@ -258,7 +267,7 @@ impl AirConditioningSystemController(24.); ZONES - demand_temperature.len() ]; - if self.id == AcscId::Acsc1 { + if matches!(self.id, AcscId::Acsc1(_)) { demand_temperature.extend(filler_vector); demand_temperature } else { @@ -649,7 +658,7 @@ impl ZoneController { acs_overhead.selected_cabin_temperature(self.zone_id) }; self.duct_demand_temperature = - if self.galley_fan_failure.is_active() && matches!(acsc_id, AcscId::Acsc2) { + if self.galley_fan_failure.is_active() && matches!(acsc_id, AcscId::Acsc2(_)) { // Cabin zone temperature sensors are ventilated by air extracted by this fan, cabin temperature regulation is lost // Cabin inlet duct is constant at 15C, cockpit air is unnafected ThermodynamicTemperature::new::(15.) @@ -2114,7 +2123,7 @@ mod acs_controller_tests { acsc: [ AirConditioningSystemController::new( context, - AcscId::Acsc1, + AcscId::Acsc1(Channel::ChannelOne), &cabin_zones, vec![ ElectricalBusType::DirectCurrent(1), @@ -2123,7 +2132,7 @@ mod acs_controller_tests { ), AirConditioningSystemController::new( context, - AcscId::Acsc2, + AcscId::Acsc2(Channel::ChannelOne), &cabin_zones, vec![ ElectricalBusType::DirectCurrent(2), @@ -3010,6 +3019,25 @@ mod acs_controller_tests { assert!((test_bed.measured_temperature().get::() - 26.).abs() < 1.); } + #[test] + fn failing_one_lane_has_no_effect() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]); + + test_bed.fail(FailureType::Acsc(AcscId::Acsc1(Channel::ChannelOne))); + test_bed = test_bed.iterate(1000); + + assert!((test_bed.measured_temperature().get::() - 26.).abs() < 1.); + } + #[test] fn unpowering_both_lanes_shuts_off_pack() { let mut test_bed = test_bed() @@ -3031,6 +3059,28 @@ mod acs_controller_tests { assert!((test_bed.measured_temperature().get::() - 26.).abs() > 1.); } + #[test] + fn failing_both_lanes_shuts_off_pack() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]); + + test_bed.fail(FailureType::Acsc(AcscId::Acsc2(Channel::ChannelOne))); + test_bed.fail(FailureType::Acsc(AcscId::Acsc2(Channel::ChannelTwo))); + test_bed = test_bed.iterate(1000); + + assert_eq!(test_bed.trim_air_valves_open_amount()[1], Ratio::default()); + assert!(!test_bed.trim_air_system_controller_is_enabled()); + assert!((test_bed.measured_temperature().get::() - 26.).abs() > 1.); + } + #[test] fn unpowering_opposite_acsc_doesnt_shut_off_pack() { let mut test_bed = test_bed() @@ -3050,6 +3100,27 @@ mod acs_controller_tests { assert_ne!(test_bed.pack_flow(), MassRate::default()); assert!((test_bed.measured_temperature().get::() - 24.).abs() < 1.); } + + #[test] + fn failing_opposite_acsc_doesnt_shut_off_pack() { + let mut test_bed = test_bed() + .with() + .both_packs_on() + .and() + .engine_idle() + .and() + .command_selected_temperature([ + ThermodynamicTemperature::new::(24.), + ThermodynamicTemperature::new::(26.), + ]); + + test_bed.fail(FailureType::Acsc(AcscId::Acsc1(Channel::ChannelOne))); + test_bed.fail(FailureType::Acsc(AcscId::Acsc1(Channel::ChannelTwo))); + test_bed = test_bed.iterate(1000); + + assert_ne!(test_bed.pack_flow(), MassRate::default()); + assert!((test_bed.measured_temperature().get::() - 24.).abs() < 1.); + } } mod zone_controller_tests { diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 742b84f17c5..32525e3754a 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -209,8 +209,8 @@ enum OperatingChannelFault { Fault, } -#[derive(Clone, Copy)] -enum Channel { +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum Channel { ChannelOne, ChannelTwo, } @@ -224,30 +224,43 @@ impl From for usize { } } +impl From for Channel { + fn from(value: usize) -> Self { + match value { + 1 => Channel::ChannelOne, + 2 => Channel::ChannelTwo, + _ => panic!("Operating Channel out of bounds"), + } + } +} + struct OperatingChannel { channel_id: Channel, powered_by: ElectricalBusType, is_powered: bool, + failure: Failure, fault: OperatingChannelFault, } impl OperatingChannel { - fn new(id: usize, powered_by: ElectricalBusType) -> Self { - let channel_id: Channel = { - match id { - 1 => Channel::ChannelOne, - 2 => Channel::ChannelTwo, - _ => panic!("Operating Channel out of bounds"), - } - }; + fn new(id: usize, failure_type: FailureType, powered_by: ElectricalBusType) -> Self { Self { - channel_id, + channel_id: id.into(), powered_by, is_powered: false, + failure: Failure::new(failure_type), fault: OperatingChannelFault::NoFault, } } + fn update_fault(&mut self) { + self.fault = if !self.is_powered || self.failure.is_active() { + OperatingChannelFault::Fault + } else { + OperatingChannelFault::NoFault + }; + } + fn has_fault(&self) -> bool { matches!(self.fault, OperatingChannelFault::Fault) } @@ -258,14 +271,13 @@ impl OperatingChannel { } impl SimulationElement for OperatingChannel { + fn accept(&mut self, visitor: &mut T) { + self.failure.accept(visitor); + visitor.visit(self); + } + fn receive_power(&mut self, buses: &impl ElectricalBuses) { self.is_powered = buses.is_powered(self.powered_by); - // For now the channel faults only when it's unpowered. In the future we can add other types of failure - if !self.is_powered { - self.fault = OperatingChannelFault::Fault; - } else { - self.fault = OperatingChannelFault::NoFault; - } } } diff --git a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs index ba6131d4513..711160b3df3 100644 --- a/fbw-common/src/wasm/systems/systems/src/failures/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/failures/mod.rs @@ -1,4 +1,4 @@ -use crate::air_conditioning::ZoneType; +use crate::air_conditioning::{acs_controller::AcscId, ZoneType}; use crate::shared::{ AirbusElectricPumpId, AirbusEngineDrivenPumpId, GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId, @@ -7,6 +7,7 @@ use crate::simulation::SimulationElement; #[derive(Clone, Copy, PartialEq, Eq)] pub enum FailureType { + Acsc(AcscId), CabinFan(usize), HotAir(usize), TrimAirOverheat(ZoneType), From 6b7ae6ac42ef8b1579e1343e360608b52a1cea8e Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Fri, 25 Aug 2023 20:18:41 +0100 Subject: [PATCH 11/18] feat: valve travel and pack smoothness --- .../instruments/src/SD/Pages/Cond/Cond.tsx | 12 +- .../a320_systems/src/air_conditioning.rs | 12 +- .../a380_systems/src/air_conditioning/mod.rs | 3 +- .../src/air_conditioning/acs_controller.rs | 68 ++++- .../systems/src/air_conditioning/mod.rs | 257 ++++++++++++++---- 5 files changed, 267 insertions(+), 85 deletions(-) diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 4888cffeea5..01400578ceb 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -19,16 +19,16 @@ export const CondPage = () => { // TODO: If both Sign Status are Failure Warning or No Computed Data, the whole page should display XX's - const [cockpitTrimAirValve] = useSimVar('L:A32NX_COND_CKPT_TRIM_AIR_VALVE_POSITION', 'number', 1000); - const [cockpitTrimTemp] = useSimVar('L:A32NX_COND_CKPT_DUCT_TEMP', 'celsius', 1000); + const [cockpitTrimAirValve] = useSimVar('L:A32NX_COND_CKPT_TRIM_AIR_VALVE_POSITION', 'number', 100); + const [cockpitTrimTemp] = useSimVar('L:A32NX_COND_CKPT_DUCT_TEMP', 'celsius', 100); const [cockpitCabinTemp] = useSimVar('L:A32NX_COND_CKPT_TEMP', 'celsius', 1000); - const [fwdTrimAirValve] = useSimVar('L:A32NX_COND_FWD_TRIM_AIR_VALVE_POSITION', 'number', 1000); - const [fwdTrimTemp] = useSimVar('L:A32NX_COND_FWD_DUCT_TEMP', 'celsius', 1000); + const [fwdTrimAirValve] = useSimVar('L:A32NX_COND_FWD_TRIM_AIR_VALVE_POSITION', 'number', 100); + const [fwdTrimTemp] = useSimVar('L:A32NX_COND_FWD_DUCT_TEMP', 'celsius', 100); const [fwdCabinTemp] = useSimVar('L:A32NX_COND_FWD_TEMP', 'celsius', 1000); - const [aftTrimAirValve] = useSimVar('L:A32NX_COND_AFT_TRIM_AIR_VALVE_POSITION', 'number', 1000); - const [aftTrimTemp] = useSimVar('L:A32NX_COND_AFT_DUCT_TEMP', 'celsius', 1000); + const [aftTrimAirValve] = useSimVar('L:A32NX_COND_AFT_TRIM_AIR_VALVE_POSITION', 'number', 100); + const [aftTrimTemp] = useSimVar('L:A32NX_COND_AFT_DUCT_TEMP', 'celsius', 100); const [aftCabinTemp] = useSimVar('L:A32NX_COND_AFT_TEMP', 'celsius', 1000); const hotAirOpen = !AcscDiscreteWord1.getBitValueOr(20, false); diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index 11cde53aec3..a3d91d65e5e 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -330,7 +330,7 @@ impl A320AirConditioningSystem { self.update_fans(cabin_simulation); - self.update_packs(); + self.update_packs(context); self.update_mixer_unit(); @@ -383,7 +383,7 @@ impl A320AirConditioningSystem { } } - fn update_packs(&mut self) { + fn update_packs(&mut self, context: &UpdateContext) { let pack_flow: [MassRate; 2] = [ self.acsc[0].individual_pack_flow(), self.acsc[1].individual_pack_flow(), @@ -397,6 +397,7 @@ impl A320AirConditioningSystem { [0, 1].iter().for_each(|&id| { self.packs[id].update( + context, pack_flow[id], &duct_demand_temperature, self.acsc[id].both_channels_failure(), @@ -413,12 +414,13 @@ impl A320AirConditioningSystem { } fn update_trim_air_system(&mut self, context: &UpdateContext) { - // TAPRV monitors by ACSC 1 self.trim_air_system.update( context, - self.acsc[0].should_open_trim_air_pressure_regulating_valve() - && self.acsc[1].should_open_trim_air_pressure_regulating_valve(), &self.mixer_unit, + &[ + &self.acsc[0].trim_air_pressure_regulating_valve_controller(), + &self.acsc[1].trim_air_pressure_regulating_valve_controller(), + ], &[&self.acsc[0], &self.acsc[1], &self.acsc[1]], ); } diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index 28deb9b4635..2139d915d15 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -409,6 +409,7 @@ impl A380AirConditioningSystem { let duct_demand_temperature = self.acsc.duct_demand_temperature(); for (id, pack) in self.packs.iter_mut().enumerate() { pack.update( + context, pack_flow[id], &duct_demand_temperature, self.acsc.both_channels_failure(), @@ -423,8 +424,8 @@ impl A380AirConditioningSystem { self.trim_air_system.update( context, - self.acsc.trim_air_pressure_regulating_valve_is_open(), &self.mixer_unit, + &[&self.acsc.trim_air_pressure_regulating_valve_controller(); 18], &[&self.acsc; 18], ); diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 575d92066cb..59b68767461 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -276,8 +276,10 @@ impl AirConditioningSystemController bool { - self.trim_air_system_controller.should_open_taprv() + pub fn trim_air_pressure_regulating_valve_controller( + &self, + ) -> TrimAirPressureRegulatingValveController { + self.trim_air_system_controller.taprv_controller() } pub fn trim_air_pressure_regulating_valve_is_open(&self) -> bool { @@ -1033,6 +1035,7 @@ struct TrimAirSystemController { taprv_open_timer: Duration, taprv_closed_disagrees: bool, taprv_closed_timer: Duration, + taprv_controller: TrimAirPressureRegulatingValveController, trim_air_valve_controllers: [TrimAirValveController; ZONES], } @@ -1053,6 +1056,7 @@ impl TrimAirSystemController TrimAirSystemController TrimAirSystemController bool { - self.is_enabled + fn taprv_controller(&self) -> TrimAirPressureRegulatingValveController { + self.taprv_controller } fn duct_zone_overheat_monitor( @@ -1237,6 +1243,7 @@ impl TrimAirSystemController Self { + Self { + should_open_taprv: false, + } + } + + fn update(&mut self, should_open_taprv: bool) { + self.should_open_taprv = should_open_taprv + } +} + +impl ControllerSignal for TrimAirPressureRegulatingValveController { + fn signal(&self) -> Option { + if self.should_open_taprv { + Some(TrimAirValveSignal::new(Ratio::new::(100.))) + } else { + Some(TrimAirValveSignal::new_closed()) + } + } +} + #[derive(Clone, Copy)] pub struct TrimAirValveController { tav_open_allowed: bool, @@ -2325,6 +2359,7 @@ mod acs_controller_tests { [0, 1].iter().for_each(|&id| { self.packs[id].update( + context, pack_flow[id], &duct_demand_temperature, self.acsc[id].both_channels_failure(), @@ -2346,9 +2381,11 @@ mod acs_controller_tests { self.trim_air_system.update( context, - self.acsc[0].should_open_trim_air_pressure_regulating_valve() - && self.acsc[1].should_open_trim_air_pressure_regulating_valve(), &self.mixer_unit, + &[ + &self.acsc[0].trim_air_pressure_regulating_valve_controller(), + &self.acsc[1].trim_air_pressure_regulating_valve_controller(), + ], &[&self.acsc[0], &self.acsc[1]], ); @@ -3366,11 +3403,12 @@ mod acs_controller_tests { .and() .command_selected_temperature( [ThermodynamicTemperature::new::(30.); 2], - ); + ) + .iterate(500); test_bed.fail(FailureType::GalleyFans); - test_bed = test_bed.iterate(1000); + test_bed = test_bed.iterate(100); assert!( (test_bed.duct_demand_temperature()[1].get::() - 15.).abs() < 1. @@ -3807,7 +3845,7 @@ mod acs_controller_tests { .iterate(32); assert!(test_bed.trim_air_system_controller_is_enabled()); - test_bed = test_bed.hot_air_pb_on(false).iterate(2); + test_bed = test_bed.hot_air_pb_on(false).iterate(4); assert!(!test_bed.trim_air_system_controller_is_enabled()); test_bed = test_bed.hot_air_pb_on(true); @@ -3817,14 +3855,14 @@ mod acs_controller_tests { // Pack 1 should be in start condition test_bed.command_pack_1_pb_position(true); - test_bed = test_bed.iterate(4); + test_bed = test_bed.iterate(6); assert!(!test_bed.trim_air_system_controller_is_enabled()); // ACSC 1 unpowered test_bed = test_bed .unpowered_dc_1_bus() .unpowered_ac_1_bus() - .iterate(2); + .iterate(4); assert!(!test_bed.trim_air_system_controller_is_enabled()); test_bed = test_bed.powered_dc_1_bus().powered_ac_1_bus().iterate(32); @@ -4262,9 +4300,11 @@ mod acs_controller_tests { test_bed = test_bed.iterate(100); - assert_eq!( - test_bed.duct_temperature()[0], - test_bed.duct_temperature()[1] + assert!( + (test_bed.duct_temperature()[0].get::() + - test_bed.duct_temperature()[1].get::()) + .abs() + < 1. ); } diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 32525e3754a..6880a0d05f0 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -1,4 +1,4 @@ -use self::acs_controller::{CabinFansSignal, Pack, TrimAirValveController}; +use self::acs_controller::{CabinFansSignal, Pack, TrimAirValveController, TrimAirValveSignal}; use crate::{ failures::{Failure, FailureType}, @@ -7,8 +7,8 @@ use crate::{ ControllablePneumaticValve, PneumaticContainer, PneumaticPipe, PneumaticValveSignal, }, shared::{ - arinc429::Arinc429Word, AverageExt, CabinSimulation, ConsumePower, ControllerSignal, - ElectricalBusType, ElectricalBuses, + arinc429::Arinc429Word, low_pass_filter::LowPassFilter, AverageExt, CabinSimulation, + ConsumePower, ControllerSignal, ElectricalBusType, ElectricalBuses, }, simulation::{ InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, @@ -16,7 +16,7 @@ use crate::{ }, }; -use std::{convert::TryInto, fmt::Display}; +use std::{convert::TryInto, fmt::Display, time::Duration}; use uom::si::{ f64::*, @@ -502,13 +502,16 @@ impl OutletAir for MixerUnitOutlet { /// Temporary struct until packs are fully simulated pub struct AirConditioningPack { pack_id: Pack, + outlet_temperature: LowPassFilter, // Degree Celsius outlet_air: Air, } impl AirConditioningPack { + const PACK_REACTION_TIME: Duration = Duration::from_secs(10); pub fn new(pack_id: Pack) -> Self { Self { pack_id, + outlet_temperature: LowPassFilter::new_with_init_value(Self::PACK_REACTION_TIME, 15.), outlet_air: Air::new(), } } @@ -517,27 +520,30 @@ impl AirConditioningPack { /// this is a placeholder until the packs are modelled pub fn update( &mut self, + context: &UpdateContext, pack_flow: MassRate, duct_demand: &[ThermodynamicTemperature], acsc_failure: bool, ) { self.outlet_air.set_flow_rate(pack_flow); - let min_temp = duct_demand - .iter() - .fold(f64::INFINITY, |acc, &t| acc.min(t.get::())); - if acsc_failure { + let unfiltered_outlet_temperature = if acsc_failure { if matches!(self.pack_id, Pack(1)) { - self.outlet_air - .set_temperature(ThermodynamicTemperature::new::(20.)); + 20. } else { - self.outlet_air - .set_temperature(ThermodynamicTemperature::new::(10.)); + 10. } } else { - self.outlet_air - .set_temperature(ThermodynamicTemperature::new::(min_temp)); - } + duct_demand + .iter() + .fold(f64::INFINITY, |acc, &t| acc.min(t.get::())) + }; + self.outlet_temperature + .update(context.delta(), unfiltered_outlet_temperature); + self.outlet_air + .set_temperature(ThermodynamicTemperature::new::( + self.outlet_temperature.output(), + )); } } @@ -547,40 +553,6 @@ impl OutletAir for AirConditioningPack { } } -struct TrimAirPressureRegulatingValve { - is_open: bool, - failure: Failure, -} - -impl TrimAirPressureRegulatingValve { - fn new(id: usize) -> Self { - Self { - is_open: false, - failure: Failure::new(FailureType::HotAir(id)), - } - } - - fn update(&mut self, should_open_taprv: bool) { - // When a failure is active the TAPRV is unresponsive - it stays in the same state as it was - self.is_open = if !self.failure.is_active() { - should_open_taprv - } else { - self.is_open - }; - } - - fn is_open(&self) -> bool { - self.is_open - } -} - -impl SimulationElement for TrimAirPressureRegulatingValve { - fn accept(&mut self, visitor: &mut T) { - self.failure.accept(visitor); - visitor.visit(self); - } -} - pub struct TrimAirSystem { duct_temperature_id: [VariableIdentifier; ZONES], @@ -627,22 +599,37 @@ impl TrimAirSystem { pub fn update( &mut self, context: &UpdateContext, - should_open_taprv: bool, mixer_air: &MixerUnit, + taprv_controller: &[&impl ControllerSignal], tav_controller: &[&impl TrimAirControllers], ) { - // FIXME: In the A380 should_open_taprv needs to be a vector self.trim_air_pressure_regulating_valves .iter_mut() - .for_each(|taprv| taprv.update(should_open_taprv)); - + .for_each(|taprv| { + taprv.update( + context, + &mut self.pack_mixer_container, + *taprv_controller + .iter() + .min_by_key(|signal| { + signal + .signal() + .unwrap_or_default() + .target_open_amount() + .get::() as u64 + }) + .unwrap(), + ) + }); + + // Fixme: A380 will need to take both TAPRV for (id, tav) in self.trim_air_valves.iter_mut().enumerate() { tav.update( context, self.trim_air_pressure_regulating_valves .iter() .any(|taprv| taprv.is_open()), - &mut self.pack_mixer_container, + &mut self.trim_air_pressure_regulating_valves[0], tav_controller[id].trim_air_valve_controllers(id), ); self.trim_air_mixers[id].update(vec![tav, &mixer_air.mixer_unit_individual_outlet(id)]); @@ -749,10 +736,158 @@ impl SimulationElement for TrimAirSyst } } +/// Struct to simulate the travel time of the TAVs and the TAPRV +struct TrimAirValveTravelTime { + valve_open_command: Ratio, + travel_time: Duration, +} + +impl TrimAirValveTravelTime { + fn new(travel_time: Duration) -> Self { + Self { + valve_open_command: Ratio::default(), + travel_time, + } + } + + fn update( + &mut self, + context: &UpdateContext, + valve_open_amount: Ratio, + signal: &impl ControllerSignal, + ) { + self.valve_open_command = valve_open_amount; + if let Some(signal) = signal.signal() { + if self.valve_open_command < signal.target_open_amount() { + self.valve_open_command += + Ratio::new::(self.get_valve_change_for_delta(context).min( + signal.target_open_amount().get::() + - self.valve_open_command.get::(), + )); + } else if self.valve_open_command > signal.target_open_amount() { + self.valve_open_command -= + Ratio::new::(self.get_valve_change_for_delta(context).min( + self.valve_open_command.get::() + - signal.target_open_amount().get::(), + )); + } + } + } + + fn get_valve_change_for_delta(&self, context: &UpdateContext) -> f64 { + 100. * (context.delta_as_secs_f64() / self.travel_time.as_secs_f64()) + } +} + +impl ControllerSignal for TrimAirValveTravelTime { + fn signal(&self) -> Option { + if self.valve_open_command > Ratio::default() { + Some(TrimAirValveSignal::new(self.valve_open_command)) + } else { + Some(TrimAirValveSignal::new_closed()) + } + } +} + +struct TrimAirPressureRegulatingValve { + trim_air_pressure_regulating_valve: DefaultValve, + taprv_travel_time: TrimAirValveTravelTime, + downstream: PneumaticPipe, + exhaust: PneumaticExhaust, + failure: Failure, +} + +impl TrimAirPressureRegulatingValve { + fn new(id: usize) -> Self { + Self { + trim_air_pressure_regulating_valve: DefaultValve::new_closed(), + taprv_travel_time: TrimAirValveTravelTime::new(Duration::from_secs(3)), + downstream: PneumaticPipe::new( + Volume::new::(4.), + Pressure::new::(14.7), + ThermodynamicTemperature::new::(15.), + ), + exhaust: PneumaticExhaust::new(0.1, 0.1, Pressure::new::(0.)), + failure: Failure::new(FailureType::HotAir(id)), + } + } + + fn update( + &mut self, + context: &UpdateContext, + from: &mut impl PneumaticContainer, + signal: &impl ControllerSignal, + ) { + // When a failure is active or there is no signal coming from the controller the TAPRV is unresponsive + self.taprv_travel_time.update( + context, + self.trim_air_pressure_regulating_valve.open_amount(), + signal, + ); + + if !self.failure.is_active() { + self.trim_air_pressure_regulating_valve + .update_open_amount(&self.taprv_travel_time); + } + + self.trim_air_pressure_regulating_valve.update_move_fluid( + context, + from, + &mut self.downstream, + ); + self.exhaust + .update_move_fluid(context, &mut self.downstream); + } + + fn is_open(&self) -> bool { + self.trim_air_pressure_regulating_valve.open_amount() > Ratio::new::(0.01) + } +} + +impl PneumaticContainer for TrimAirPressureRegulatingValve { + fn pressure(&self) -> Pressure { + self.downstream.pressure() + } + + fn volume(&self) -> Volume { + self.downstream.volume() + } + + fn temperature(&self) -> ThermodynamicTemperature { + self.downstream.temperature() + } + + fn mass(&self) -> Mass { + self.downstream.mass() + } + + fn change_fluid_amount( + &mut self, + fluid_amount: Mass, + fluid_temperature: ThermodynamicTemperature, + fluid_pressure: Pressure, + ) { + self.downstream + .change_fluid_amount(fluid_amount, fluid_temperature, fluid_pressure); + } + + fn update_temperature(&mut self, temperature_change: TemperatureInterval) { + self.downstream.update_temperature(temperature_change); + } +} + +impl SimulationElement for TrimAirPressureRegulatingValve { + fn accept(&mut self, visitor: &mut T) { + self.failure.accept(visitor); + visitor.visit(self); + } +} + struct TrimAirValve { trim_air_valve_id: VariableIdentifier, trim_air_valve: DefaultValve, + trim_air_valve_travel_time: TrimAirValveTravelTime, trim_air_container: PneumaticPipe, exhaust: PneumaticExhaust, outlet_air: Air, @@ -769,6 +904,7 @@ impl TrimAirValve { .get_identifier(format!("COND_{}_TRIM_AIR_VALVE_POSITION", zone_id)), trim_air_valve: DefaultValve::new_closed(), + trim_air_valve_travel_time: TrimAirValveTravelTime::new(Duration::from_secs(5)), trim_air_container: PneumaticPipe::new( Volume::new::(0.03), // Based on references Pressure::new::(14.7 + Self::PRESSURE_DIFFERENCE_WITH_CABIN_PSI), @@ -788,8 +924,15 @@ impl TrimAirValve { from: &mut impl PneumaticContainer, tav_controller: TrimAirValveController, ) { + self.trim_air_valve_travel_time.update( + context, + self.trim_air_valve_open_amount(), + &tav_controller, + ); + if !self.failure.is_active() { - self.trim_air_valve.update_open_amount(&tav_controller); + self.trim_air_valve + .update_open_amount(&self.trim_air_valve_travel_time); } self.trim_air_valve .update_move_fluid(context, from, &mut self.trim_air_container); @@ -810,10 +953,6 @@ impl TrimAirValve { self.outlet_air .set_pressure(self.trim_air_container.pressure()); - - if !trim_air_pressure_regulating_valve_open { - self.outlet_air.set_flow_rate(MassRate::default()); - } } fn trim_air_valve_open_amount(&self) -> Ratio { From 5b6ab0a07e00263f017517aed48e35ec6ff52951 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Fri, 25 Aug 2023 20:36:46 +0100 Subject: [PATCH 12/18] feat: changelog and final adjustments --- .github/CHANGELOG.md | 1 + fbw-a32nx/docs/a320-simvars.md | 13 +++---------- .../systems/instruments/src/SD/Pages/Cond/Cond.tsx | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 9a5e5919761..597a77aea84 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -41,6 +41,7 @@ 1. [EFB/FWC] Auto Call Outs selectable - @tracernz (Mike) 1. [FWC] Added "twenty five hundred" and "two thousand" callouts - @tracernz (Mike) 1. [FMS] Load procedures that reference a runway correctly - @tracernz (Mike) +1. [COND] Add Air Conditioning systems failures - @mjuhe (Miquel Juhe) ## 0.10.0 diff --git a/fbw-a32nx/docs/a320-simvars.md b/fbw-a32nx/docs/a320-simvars.md index 1b7e4153b55..44794b98b6c 100644 --- a/fbw-a32nx/docs/a320-simvars.md +++ b/fbw-a32nx/docs/a320-simvars.md @@ -2673,9 +2673,9 @@ In the variables below, {number} should be replaced with one item in the set: { | 20 | Trim valve AFT inop | | 21 | Not used | | 22 | Not used | - | 23 | FCV status (Both pakcs off) | - | 24 | One pack operation | - | 25 | FCV status (Both pakcs on) | + | 23 | *FCV status (Both pakcs off) | + | 24 | *One pack operation | + | 25 | *FCV status (Both pakcs on) | | 26 | Spare | | 27 | *Nacelle anti-ice eng 2 open | | 28 | *Nacelle anti-ice eng 1 open | @@ -2804,13 +2804,6 @@ In the variables below, {number} should be replaced with one item in the set: { - Bool - True if CAB FANS pushbutton is in the on position (no white light) -- A32NX_PACKS_{number}_IS_SUPPLYING - - Bool - - True if the corresponding pack is on and supplying air to the cabin - - {number} - - 1 - - 2 - ## Pneumatic - A32NX_PNEU_ENG_{number}_IP_PRESSURE: diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 01400578ceb..256d9c67dc2 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -32,7 +32,7 @@ export const CondPage = () => { const [aftCabinTemp] = useSimVar('L:A32NX_COND_AFT_TEMP', 'celsius', 1000); const hotAirOpen = !AcscDiscreteWord1.getBitValueOr(20, false); - const hotAirPositionDisagrees = AcscDiscreteWord1.getBitValueOr(27, false); + const hotAirPositionDisagrees = Acsc1DiscreteWord1.getBitValueOr(27, false) && Acsc2DiscreteWord1.getBitValueOr(27, false); const hotAirPb = AcscDiscreteWord1.getBitValueOr(23, false); const cabFanHasFault1 = AcscDiscreteWord1.getBitValueOr(25, false); From 463d683757c35ec6da733761f17028ba9234e821 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Fri, 25 Aug 2023 21:36:49 +0100 Subject: [PATCH 13/18] fix: type acsc line -> lane --- .../src/EFB/failures-orchestrator-provider.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx index 23b90283dc3..6dfdf4c020d 100644 --- a/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/fbw-a32nx/src/systems/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -15,10 +15,10 @@ interface FailuresOrchestratorContext { } const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ - [21, A320Failure.Acsc1Lane1, 'ACSC 1 Line 1'], - [21, A320Failure.Acsc1Lane2, 'ACSC 1 Line 2'], - [21, A320Failure.Acsc2Lane1, 'ACSC 2 Line 1'], - [21, A320Failure.Acsc2Lane2, 'ACSC 2 Line 2'], + [21, A320Failure.Acsc1Lane1, 'ACSC 1 Lane 1'], + [21, A320Failure.Acsc1Lane2, 'ACSC 1 Lane 2'], + [21, A320Failure.Acsc2Lane1, 'ACSC 2 Lane 1'], + [21, A320Failure.Acsc2Lane2, 'ACSC 2 Lane 2'], [21, A320Failure.HotAir, 'Trim Air Pressure Regulating Valve'], [21, A320Failure.TrimAirHighPressure, 'Trim Air System High Pressure'], [21, A320Failure.CkptTrimAirFailure, 'Cockpit Trim Air Valve'], From 6e04db1e2fedce9df9ae608027b5d1f49d02802f Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sat, 26 Aug 2023 22:10:51 +0100 Subject: [PATCH 14/18] fix: rust review comments --- .../a320_systems/src/air_conditioning.rs | 6 +-- .../a380_systems/src/air_conditioning/mod.rs | 6 +-- .../src/air_conditioning/acs_controller.rs | 45 ++++++++----------- .../systems/src/air_conditioning/mod.rs | 16 +++---- 4 files changed, 31 insertions(+), 42 deletions(-) diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index a3d91d65e5e..a95a992de08 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -272,7 +272,7 @@ impl A320AirConditioningSystem { context, AcscId::Acsc1(Channel::ChannelOne), cabin_zones, - vec![ + [ ElectricalBusType::DirectCurrent(1), ElectricalBusType::AlternatingCurrent(1), ], @@ -281,7 +281,7 @@ impl A320AirConditioningSystem { context, AcscId::Acsc2(Channel::ChannelOne), cabin_zones, - vec![ + [ ElectricalBusType::DirectCurrent(2), ElectricalBusType::AlternatingCurrent(2), ], @@ -296,7 +296,7 @@ impl A320AirConditioningSystem { AirConditioningPack::new(Pack(1)), AirConditioningPack::new(Pack(2)), ], - trim_air_system: TrimAirSystem::new(context, cabin_zones, vec![1]), + trim_air_system: TrimAirSystem::new(context, cabin_zones, &[1]), air_conditioning_overhead: A320AirConditioningSystemOverhead::new(context, cabin_zones), } diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index 2139d915d15..d9752f25f5c 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -321,7 +321,7 @@ impl A380AirConditioningSystem { context, AcscId::Acsc1(Channel::ChannelOne), cabin_zones, - vec![ + [ ElectricalBusType::DirectCurrent(1), ElectricalBusType::AlternatingCurrent(1), ], @@ -351,7 +351,7 @@ impl A380AirConditioningSystem { AirConditioningPack::new(Pack(1)), AirConditioningPack::new(Pack(2)), ], - trim_air_system: TrimAirSystem::new(context, cabin_zones, vec![1]), + trim_air_system: TrimAirSystem::new(context, cabin_zones, &[1]), air_conditioning_overhead: A380AirConditioningSystemOverhead::new(context), } @@ -405,7 +405,7 @@ impl A380AirConditioningSystem { fan.update(cabin_simulation, &self.acsc.cabin_fans_controller()) } - let pack_flow: [MassRate; 2] = [self.fdac[0].pack_flow(), self.fdac[1].pack_flow()]; + let pack_flow = [self.fdac[0].pack_flow(), self.fdac[1].pack_flow()]; let duct_demand_temperature = self.acsc.duct_demand_temperature(); for (id, pack) in self.packs.iter_mut().enumerate() { pack.update( diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 59b68767461..ad08fa9f250 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -79,9 +79,9 @@ impl AirConditioningSystemController, + powered_by: [ElectricalBusType; 2], ) -> Self { - let failure_types: [FailureType; 2] = match id { + let failure_types = match id { AcscId::Acsc1(_) => [ FailureType::Acsc(AcscId::Acsc1(Channel::ChannelOne)), FailureType::Acsc(AcscId::Acsc1(Channel::ChannelTwo)), @@ -117,7 +117,7 @@ impl AirConditioningSystemController>>() @@ -188,22 +188,17 @@ impl AirConditioningSystemController { - if self.stand_by_channel.has_fault() { - Some(AcscFault::BothChannelsFault) - } else { - self.switch_active_channel(); - Some(AcscFault::OneChannelFault) - } - } - false => { - if self.stand_by_channel.has_fault() { - Some(AcscFault::OneChannelFault) - } else { - None - } + self.internal_failure = if self.active_channel.has_fault() { + if self.stand_by_channel.has_fault() { + Some(AcscFault::BothChannelsFault) + } else { + self.switch_active_channel(); + Some(AcscFault::OneChannelFault) } + } else if self.stand_by_channel.has_fault() { + Some(AcscFault::OneChannelFault) + } else { + None }; } @@ -910,7 +905,7 @@ impl PackFlowController { ) -> Ratio { if !self.is_enabled { // If both lanes of the ACSC fail, the PFV closes and the flow demand is 0 - return Ratio::new::(0.); + return Ratio::default(); } let mut intermediate_flow: Ratio = acs_overhead.flow_selector_position().into(); // TODO: Add "insufficient performance" based on Pack Mixer Temperature Demand @@ -2159,7 +2154,7 @@ mod acs_controller_tests { context, AcscId::Acsc1(Channel::ChannelOne), &cabin_zones, - vec![ + [ ElectricalBusType::DirectCurrent(1), ElectricalBusType::AlternatingCurrent(1), ], @@ -2168,7 +2163,7 @@ mod acs_controller_tests { context, AcscId::Acsc2(Channel::ChannelOne), &cabin_zones, - vec![ + [ ElectricalBusType::DirectCurrent(2), ElectricalBusType::AlternatingCurrent(2), ], @@ -2197,7 +2192,7 @@ mod acs_controller_tests { lgciu1: TestLgciu::new(false), lgciu2: TestLgciu::new(false), cabin_air_simulation: TestCabinAirSimulation::new(context), - trim_air_system: TrimAirSystem::new(context, &cabin_zones, vec![1]), + trim_air_system: TrimAirSystem::new(context, &cabin_zones, &[1]), powered_dc_source_1: TestElectricitySource::powered( context, PotentialOrigin::Battery(1), @@ -2348,10 +2343,8 @@ mod acs_controller_tests { [0, self.number_of_passengers / 2], ); - let pack_flow: [MassRate; 2] = [ - self.acsc[0].individual_pack_flow(), - self.acsc[1].individual_pack_flow(), - ]; + let pack_flow = [0, 1].map(|id| self.acsc[id].individual_pack_flow()); + let duct_demand_temperature = vec![ self.acsc[0].duct_demand_temperature()[0], self.acsc[1].duct_demand_temperature()[1], diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 6880a0d05f0..7898103f3ce 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -570,7 +570,7 @@ impl TrimAirSystem { pub fn new( context: &mut InitContext, cabin_zone_ids: &[ZoneType; ZONES], - taprv_ids: Vec, + taprv_ids: &[usize], ) -> Self { let duct_temperature_id = cabin_zone_ids.map(|id| context.get_identifier(format!("COND_{}_DUCT_TEMP", id))); @@ -760,16 +760,12 @@ impl TrimAirValveTravelTime { if let Some(signal) = signal.signal() { if self.valve_open_command < signal.target_open_amount() { self.valve_open_command += - Ratio::new::(self.get_valve_change_for_delta(context).min( - signal.target_open_amount().get::() - - self.valve_open_command.get::(), - )); + Ratio::new::(self.get_valve_change_for_delta(context)) + .min(signal.target_open_amount() - self.valve_open_command); } else if self.valve_open_command > signal.target_open_amount() { self.valve_open_command -= - Ratio::new::(self.get_valve_change_for_delta(context).min( - self.valve_open_command.get::() - - signal.target_open_amount().get::(), - )); + Ratio::new::(self.get_valve_change_for_delta(context)) + .min(self.valve_open_command - signal.target_open_amount()); } } } @@ -807,7 +803,7 @@ impl TrimAirPressureRegulatingValve { Pressure::new::(14.7), ThermodynamicTemperature::new::(15.), ), - exhaust: PneumaticExhaust::new(0.1, 0.1, Pressure::new::(0.)), + exhaust: PneumaticExhaust::new(0.1, 0.1, Pressure::default()), failure: Failure::new(FailureType::HotAir(id)), } } From 3ec256bf0fd92f9ad99b3cb1da92c04139098537 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sat, 26 Aug 2023 22:50:45 +0100 Subject: [PATCH 15/18] fix: correct buses to power acsc channels --- .../a320_systems/src/air_conditioning.rs | 20 +++++-- .../systems/a320_systems/src/pneumatic.rs | 4 ++ .../a380_systems/src/air_conditioning/mod.rs | 10 +++- .../src/air_conditioning/acs_controller.rs | 55 +++++++++++++++---- .../systems/src/air_conditioning/mod.rs | 8 +-- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index a95a992de08..7034d180361 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -273,8 +273,14 @@ impl A320AirConditioningSystem { AcscId::Acsc1(Channel::ChannelOne), cabin_zones, [ - ElectricalBusType::DirectCurrent(1), - ElectricalBusType::AlternatingCurrent(1), + [ + ElectricalBusType::AlternatingCurrent(1), // 103XP + ElectricalBusType::DirectCurrent(1), // 101PP + ], + [ + ElectricalBusType::AlternatingCurrent(2), // 202XP + ElectricalBusType::DirectCurrentEssential, // 4PP + ], ], ), AirConditioningSystemController::new( @@ -282,8 +288,14 @@ impl A320AirConditioningSystem { AcscId::Acsc2(Channel::ChannelOne), cabin_zones, [ - ElectricalBusType::DirectCurrent(2), - ElectricalBusType::AlternatingCurrent(2), + [ + ElectricalBusType::AlternatingCurrent(2), // 101XP + ElectricalBusType::DirectCurrent(2), // 103PP + ], + [ + ElectricalBusType::AlternatingCurrent(2), // 204XP + ElectricalBusType::DirectCurrent(2), // 206PP + ], ], ), ], diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/pneumatic.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/pneumatic.rs index 22e60a3df49..f67282f54eb 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/pneumatic.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/pneumatic.rs @@ -1700,6 +1700,7 @@ mod tests { dc_ess_bus: ElectricalBus, dc_ess_shed_bus: ElectricalBus, ac_1_bus: ElectricalBus, + ac_2_bus: ElectricalBus, // Electric buses states to be able to kill them dynamically is_dc_1_powered: bool, is_dc_2_powered: bool, @@ -1731,6 +1732,7 @@ mod tests { ElectricalBusType::DirectCurrentEssentialShed, ), ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), + ac_2_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), is_dc_1_powered: true, is_dc_2_powered: true, is_dc_ess_powered: true, @@ -1774,6 +1776,8 @@ mod tests { if self.is_ac_1_powered { electricity.flow(&self.powered_source, &self.ac_1_bus); } + + electricity.flow(&self.powered_source, &self.ac_2_bus); } fn update_after_power_distribution(&mut self, context: &UpdateContext) { diff --git a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs index d9752f25f5c..5ad288a5ace 100644 --- a/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs +++ b/fbw-a380x/src/wasm/systems/a380_systems/src/air_conditioning/mod.rs @@ -322,8 +322,14 @@ impl A380AirConditioningSystem { AcscId::Acsc1(Channel::ChannelOne), cabin_zones, [ - ElectricalBusType::DirectCurrent(1), - ElectricalBusType::AlternatingCurrent(1), + [ + ElectricalBusType::AlternatingCurrent(1), // 103XP + ElectricalBusType::DirectCurrent(1), // 101PP + ], + [ + ElectricalBusType::AlternatingCurrent(2), // 202XP + ElectricalBusType::DirectCurrentEssential, // 4PP + ], ], ), fdac: [ diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index ad08fa9f250..52c1c6a8c17 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -79,7 +79,7 @@ impl AirConditioningSystemController Self { let failure_types = match id { AcscId::Acsc1(_) => [ @@ -95,9 +95,8 @@ impl AirConditioningSystemController Self { @@ -2155,8 +2156,14 @@ mod acs_controller_tests { AcscId::Acsc1(Channel::ChannelOne), &cabin_zones, [ - ElectricalBusType::DirectCurrent(1), - ElectricalBusType::AlternatingCurrent(1), + [ + ElectricalBusType::AlternatingCurrent(1), // 103XP + ElectricalBusType::DirectCurrent(1), // 101PP + ], + [ + ElectricalBusType::AlternatingCurrent(2), // 202XP + ElectricalBusType::DirectCurrentEssential, // 4PP + ], ], ), AirConditioningSystemController::new( @@ -2164,8 +2171,14 @@ mod acs_controller_tests { AcscId::Acsc2(Channel::ChannelOne), &cabin_zones, [ - ElectricalBusType::DirectCurrent(2), - ElectricalBusType::AlternatingCurrent(2), + [ + ElectricalBusType::AlternatingCurrent(2), // 101XP + ElectricalBusType::DirectCurrent(2), // 103PP + ], + [ + ElectricalBusType::AlternatingCurrent(2), // 204XP + ElectricalBusType::DirectCurrent(2), // 206PP + ], ], ), ], @@ -2209,10 +2222,15 @@ mod acs_controller_tests { context, PotentialOrigin::EngineGenerator(2), ), + powered_dc_ess_source: TestElectricitySource::powered( + context, + PotentialOrigin::StaticInverter, + ), dc_1_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(1)), ac_1_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(1)), dc_2_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrent(2)), ac_2_bus: ElectricalBus::new(context, ElectricalBusType::AlternatingCurrent(2)), + dc_ess_bus: ElectricalBus::new(context, ElectricalBusType::DirectCurrentEssential), } } @@ -2289,6 +2307,10 @@ mod acs_controller_tests { fn power_ac_2_bus(&mut self) { self.powered_ac_source_2.power(); } + + fn unpower_dc_ess_bus(&mut self) { + self.powered_dc_ess_source.unpower(); + } } impl Aircraft for TestAircraft { fn update_before_power_distribution( @@ -2300,10 +2322,12 @@ mod acs_controller_tests { electricity.supplied_by(&self.powered_ac_source_1); electricity.supplied_by(&self.powered_dc_source_2); electricity.supplied_by(&self.powered_ac_source_2); + electricity.supplied_by(&self.powered_dc_ess_source); electricity.flow(&self.powered_dc_source_1, &self.dc_1_bus); electricity.flow(&self.powered_ac_source_1, &self.ac_1_bus); electricity.flow(&self.powered_dc_source_2, &self.dc_2_bus); electricity.flow(&self.powered_ac_source_2, &self.ac_2_bus); + electricity.flow(&self.powered_dc_ess_source, &self.dc_ess_bus) } fn update_after_power_distribution(&mut self, context: &UpdateContext) { @@ -2599,6 +2623,11 @@ mod acs_controller_tests { self } + fn unpowered_dc_ess_bus(mut self) -> Self { + self.command(|a| a.unpower_dc_ess_bus()); + self + } + fn hot_air_pb_on(mut self, value: bool) -> Self { self.command(|a| a.acs_overhead.set_hot_air_pb(value)); self @@ -3043,7 +3072,6 @@ mod acs_controller_tests { ThermodynamicTemperature::new::(26.), ]) .unpowered_ac_1_bus() - .unpowered_ac_2_bus() .iterate(1000); assert!((test_bed.measured_temperature().get::() - 26.).abs() < 1.); @@ -3125,6 +3153,7 @@ mod acs_controller_tests { ]) .unpowered_ac_1_bus() .unpowered_dc_1_bus() + .unpowered_dc_ess_bus() .iterate(1000); assert_ne!(test_bed.pack_flow(), MassRate::default()); @@ -3376,6 +3405,7 @@ mod acs_controller_tests { .and() .unpowered_dc_1_bus() .unpowered_ac_1_bus() + .unpowered_dc_ess_bus() .command_selected_temperature( [ThermodynamicTemperature::new::(30.); 2], ); @@ -3420,15 +3450,15 @@ mod acs_controller_tests { .and() .engine_idle() .and() - .unpowered_dc_1_bus() - .unpowered_ac_1_bus() + .unpowered_dc_2_bus() + .unpowered_ac_2_bus() .command_selected_temperature( [ThermodynamicTemperature::new::(30.); 2], ); test_bed = test_bed.iterate(1000); assert!((test_bed.duct_temperature()[1].get::() - 24.).abs() < 1.); - test_bed = test_bed.powered_dc_1_bus().powered_ac_1_bus(); + test_bed = test_bed.powered_dc_2_bus().powered_ac_2_bus(); test_bed = test_bed.iterate(1000); assert!(test_bed.duct_temperature()[1].get::() > 24.); } @@ -3762,6 +3792,7 @@ mod acs_controller_tests { test_bed = test_bed .unpowered_dc_1_bus() .unpowered_ac_1_bus() + .unpowered_dc_ess_bus() .powered_dc_2_bus() .powered_ac_2_bus(); test_bed = test_bed.iterate(2); diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs index 7898103f3ce..a55163a6ed9 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/mod.rs @@ -236,17 +236,17 @@ impl From for Channel { struct OperatingChannel { channel_id: Channel, - powered_by: ElectricalBusType, + powered_by: Vec, is_powered: bool, failure: Failure, fault: OperatingChannelFault, } impl OperatingChannel { - fn new(id: usize, failure_type: FailureType, powered_by: ElectricalBusType) -> Self { + fn new(id: usize, failure_type: FailureType, powered_by: &[ElectricalBusType]) -> Self { Self { channel_id: id.into(), - powered_by, + powered_by: powered_by.to_vec(), is_powered: false, failure: Failure::new(failure_type), fault: OperatingChannelFault::NoFault, @@ -277,7 +277,7 @@ impl SimulationElement for OperatingChannel { } fn receive_power(&mut self, buses: &impl ElectricalBuses) { - self.is_powered = buses.is_powered(self.powered_by); + self.is_powered = self.powered_by.iter().all(|&p| buses.is_powered(p)); } } From dee0265399d02666494c311bfc7acdbb6cbe9fc3 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sat, 26 Aug 2023 23:03:53 +0100 Subject: [PATCH 16/18] style: correct case in variable naming --- .../instruments/src/SD/Pages/Cond/Cond.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx index 256d9c67dc2..24c894addfe 100644 --- a/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx +++ b/fbw-a32nx/src/systems/instruments/src/SD/Pages/Cond/Cond.tsx @@ -13,9 +13,9 @@ export const CondPage = () => { // Display trim valve position for each zone const gaugeOffset = -43; // Gauges range is from -43 degree to +43 degree - const Acsc1DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); - const Acsc2DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_2_DISCRETE_WORD_1'); - const AcscDiscreteWord1 = !Acsc1DiscreteWord1.isFailureWarning() ? Acsc1DiscreteWord1 : Acsc2DiscreteWord1; + const acsc1DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_1_DISCRETE_WORD_1'); + const acsc2DiscreteWord1 = useArinc429Var('L:A32NX_COND_ACSC_2_DISCRETE_WORD_1'); + const acscDiscreteWord1 = !acsc1DiscreteWord1.isFailureWarning() ? acsc1DiscreteWord1 : acsc2DiscreteWord1; // TODO: If both Sign Status are Failure Warning or No Computed Data, the whole page should display XX's @@ -31,12 +31,12 @@ export const CondPage = () => { const [aftTrimTemp] = useSimVar('L:A32NX_COND_AFT_DUCT_TEMP', 'celsius', 100); const [aftCabinTemp] = useSimVar('L:A32NX_COND_AFT_TEMP', 'celsius', 1000); - const hotAirOpen = !AcscDiscreteWord1.getBitValueOr(20, false); - const hotAirPositionDisagrees = Acsc1DiscreteWord1.getBitValueOr(27, false) && Acsc2DiscreteWord1.getBitValueOr(27, false); - const hotAirPb = AcscDiscreteWord1.getBitValueOr(23, false); + const hotAirOpen = !acscDiscreteWord1.getBitValueOr(20, false); + const hotAirPositionDisagrees = acsc1DiscreteWord1.getBitValueOr(27, false) && acsc2DiscreteWord1.getBitValueOr(27, false); + const hotAirSwitchPosition = acscDiscreteWord1.getBitValueOr(23, false); - const cabFanHasFault1 = AcscDiscreteWord1.getBitValueOr(25, false); - const cabFanHasFault2 = AcscDiscreteWord1.getBitValueOr(26, false); + const cabFanHasFault1 = acscDiscreteWord1.getBitValueOr(25, false); + const cabFanHasFault2 = acscDiscreteWord1.getBitValueOr(26, false); return ( @@ -64,7 +64,7 @@ export const CondPage = () => { x={153} y={105} offset={gaugeOffset} - hotAir={hotAirPositionDisagrees || !hotAirPb} + hotAir={hotAirPositionDisagrees || !hotAirSwitchPosition} /> {/* Fwd */} @@ -76,7 +76,7 @@ export const CondPage = () => { x={324} y={105} offset={gaugeOffset} - hotAir={hotAirPositionDisagrees || !hotAirPb} + hotAir={hotAirPositionDisagrees || !hotAirSwitchPosition} /> {/* Aft */} @@ -88,7 +88,7 @@ export const CondPage = () => { x={494} y={105} offset={gaugeOffset} - hotAir={hotAirPositionDisagrees || !hotAirPb} + hotAir={hotAirPositionDisagrees || !hotAirSwitchPosition} /> {/* Valve and tubes */} @@ -97,9 +97,9 @@ export const CondPage = () => { HOT AIR - - - + + + ); From e7780a8264ebdc558c7546ca988b95f591c94e13 Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sun, 27 Aug 2023 16:59:09 +0100 Subject: [PATCH 17/18] refactor: duct demand temperature vector --- .../src/air_conditioning/acs_controller.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 52c1c6a8c17..55a01455cdf 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -250,24 +250,20 @@ impl AirConditioningSystemController Vec { - let mut demand_temperature: Vec = self + pub fn duct_demand_temperature(&self) -> [ThermodynamicTemperature; ZONES] { + let demand_temperature: Vec = self .zone_controller .iter() .map(|zone| zone.duct_demand_temperature()) .collect(); // Because each ACSC calculates the demand of its respective zone(s), we fill the vector for the trim air system - let mut filler_vector = vec![ - ThermodynamicTemperature::new::(24.); - ZONES - demand_temperature.len() - ]; + let mut filler_vector = [ThermodynamicTemperature::new::(24.); ZONES]; if matches!(self.id, AcscId::Acsc1(_)) { - demand_temperature.extend(filler_vector); - demand_temperature + filler_vector[..1].copy_from_slice(&demand_temperature); } else { - filler_vector.extend(demand_temperature); - filler_vector - } + filler_vector[1..].copy_from_slice(&demand_temperature); + }; + filler_vector } pub fn trim_air_pressure_regulating_valve_controller( From f0b661bc8555b21061b09b0698de0fba478f53cc Mon Sep 17 00:00:00 2001 From: Miquel Juhe Date: Sat, 2 Sep 2023 09:45:16 +0100 Subject: [PATCH 18/18] fix: correct buses for acsc 2 --- .../a320_systems/src/air_conditioning.rs | 4 ++-- .../src/air_conditioning/acs_controller.rs | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs index 7034d180361..0acf71c0424 100644 --- a/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs +++ b/fbw-a32nx/src/wasm/systems/a320_systems/src/air_conditioning.rs @@ -289,8 +289,8 @@ impl A320AirConditioningSystem { cabin_zones, [ [ - ElectricalBusType::AlternatingCurrent(2), // 101XP - ElectricalBusType::DirectCurrent(2), // 103PP + ElectricalBusType::AlternatingCurrent(1), // 101XP + ElectricalBusType::DirectCurrent(1), // 103PP ], [ ElectricalBusType::AlternatingCurrent(2), // 204XP diff --git a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs index 55a01455cdf..8f6b1cf14c2 100644 --- a/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs +++ b/fbw-common/src/wasm/systems/systems/src/air_conditioning/acs_controller.rs @@ -2168,8 +2168,8 @@ mod acs_controller_tests { &cabin_zones, [ [ - ElectricalBusType::AlternatingCurrent(2), // 101XP - ElectricalBusType::DirectCurrent(2), // 103PP + ElectricalBusType::AlternatingCurrent(1), // 101XP + ElectricalBusType::DirectCurrent(1), // 103PP ], [ ElectricalBusType::AlternatingCurrent(2), // 204XP @@ -3104,7 +3104,7 @@ mod acs_controller_tests { ThermodynamicTemperature::new::(24.), ThermodynamicTemperature::new::(26.), ]) - .unpowered_ac_2_bus() + .unpowered_ac_1_bus() .unpowered_dc_2_bus() .iterate(1000); @@ -3447,14 +3447,14 @@ mod acs_controller_tests { .engine_idle() .and() .unpowered_dc_2_bus() - .unpowered_ac_2_bus() + .unpowered_ac_1_bus() .command_selected_temperature( [ThermodynamicTemperature::new::(30.); 2], ); test_bed = test_bed.iterate(1000); assert!((test_bed.duct_temperature()[1].get::() - 24.).abs() < 1.); - test_bed = test_bed.powered_dc_2_bus().powered_ac_2_bus(); + test_bed = test_bed.powered_dc_2_bus().powered_ac_1_bus(); test_bed = test_bed.iterate(1000); assert!(test_bed.duct_temperature()[1].get::() > 24.); } @@ -3778,7 +3778,7 @@ mod acs_controller_tests { let initial_flow = test_bed.pack_flow(); - test_bed = test_bed.unpowered_dc_2_bus().unpowered_ac_2_bus(); + test_bed = test_bed.unpowered_dc_2_bus().unpowered_ac_1_bus(); test_bed = test_bed.iterate(2); @@ -3787,16 +3787,15 @@ mod acs_controller_tests { test_bed = test_bed .unpowered_dc_1_bus() - .unpowered_ac_1_bus() .unpowered_dc_ess_bus() .powered_dc_2_bus() - .powered_ac_2_bus(); + .powered_ac_1_bus(); test_bed = test_bed.iterate(2); assert!(test_bed.pack_flow() < initial_flow); assert!(test_bed.pack_flow() > MassRate::default()); test_bed = test_bed - .unpowered_ac_2_bus() + .unpowered_ac_1_bus() .unpowered_dc_2_bus() .iterate(20); assert_eq!(test_bed.pack_flow(), MassRate::default());