diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index ab6ac5030444..48e387a141f3 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -13,6 +13,7 @@ 1. [ATSU] Fix LSK6L not returning to ATSU DATALINK page in ATC MENU - @BravoMike99 (Bruno_pt99#5802) 1. [HYD] Trimmable physical assemblies - @Crocket63 (crocket) 1. [HYD] Simulation of the rudder mechanical assembly and yaw dampers - @Crocket63 (crocket) +1. [HYD] Simple temperature simulation - @Crocket63 (crocket) 1. [FLIGHTMODEL] Reduced flap induced drag - @donstim (donbikes#4084) 1. [RMP] RMPs navigation backup - Julian Sebline (Julian Sebline#8476 on Discord) diff --git a/docs/a320-simvars.md b/docs/a320-simvars.md index 0b0b17134134..8f14c242a57f 100644 --- a/docs/a320-simvars.md +++ b/docs/a320-simvars.md @@ -828,6 +828,14 @@ - BLUE - YELLOW +- A32NX_HYD_{loop_name}_RESERVOIR_OVHT + - Boolean + - Reservoir of {loop_name} hydraulic circuit is overheating + - {loop_name} + - GREEN + - BLUE + - YELLOW + - A32NX_HYD_{loop_name}_EDPUMP_ACTIVE - Bool - Engine driven pump of {loop_name} hydraulic circuit is active @@ -907,6 +915,13 @@ - BLUE - YELLOW +- A32NX_HYD_{loop_name}_EPUMP_OVHT + - Bool + - Electric pump of {loop_name} hydraulic circuit is overheating + - {loop_name} + - BLUE + - YELLOW + - A32NX_HYD_{loop_name}_PUMP_1_FIRE_VALVE_OPENED - Bool - Engine driven pump of {loop_name} hydraulic circuit can receive hydraulic fluid diff --git a/src/failures/src/a320.ts b/src/failures/src/a320.ts index 18b9480e5488..2a094c2f36e5 100644 --- a/src/failures/src/a320.ts +++ b/src/failures/src/a320.ts @@ -13,6 +13,7 @@ export const A320Failure = Object.freeze({ Sec3Failure: 27004, Fcdc1Failure: 27005, Fcdc2Failure: 27006, + GreenReservoirLeak: 29000, BlueReservoirLeak: 29001, YellowReservoirLeak: 29002, @@ -22,6 +23,11 @@ export const A320Failure = Object.freeze({ GreenReservoirReturnLeak: 29006, BlueReservoirReturnLeak: 29007, YellowReservoirReturnLeak: 29008, + GreenEdpOverheat: 29009, + BlueEpumpOverheat: 29010, + YellowEdpOverheat: 29011, + YellowEpumpOverheat: 29012, + LeftPfdDisplay: 31000, RightPfdDisplay: 31001, diff --git a/src/instruments/src/Common/EWDMessages.tsx b/src/instruments/src/Common/EWDMessages.tsx index bcc0e1bb357d..950c94d7db7f 100644 --- a/src/instruments/src/Common/EWDMessages.tsx +++ b/src/instruments/src/Common/EWDMessages.tsx @@ -232,6 +232,15 @@ const EWDMessages = { '270055701': '\x1b<4m\x1b4mF/CTL\x1bm FCDC 2 FAULT', '290031001': '\x1b<4m*HYD', '290031201': '\x1b<4m*HYD', + '290012601': '\x1b<4m\x1b4mHYD\x1bm B RSVR OVHT', + '290012602': '\x1b<5m -BLUE ELEC PUMP.....OFF', + '290012701': '\x1b<4m\x1b4mHYD\x1bm Y RSVR OVHT', + '290012702': '\x1b<5m -PTU................OFF', + '290012703': '\x1b<5m -YELLOW ENG 2 PUMP..OFF', + '290012704': '\x1b<5m -YELLOW ELEC PUMP...OFF', + '290012801': '\x1b<4m\x1b4mHYD\x1bm G RSVR OVHT', + '290012802': '\x1b<5m -PTU................OFF', + '290012803': '\x1b<5m -GREEN ENG 1 PUMP...OFF', '308118601': '\x1b<4m\x1b4mSEVERE ICE\x1bm DETECTED', '308118602': '\x1b5m -WING ANTI ICE.......ON', '308118603': '\x1b5m -ENG MOD SEL........IGN', diff --git a/src/instruments/src/EFB/failures-orchestrator-provider.tsx b/src/instruments/src/EFB/failures-orchestrator-provider.tsx index c39706dbccf2..b4cb79d1ad96 100644 --- a/src/instruments/src/EFB/failures-orchestrator-provider.tsx +++ b/src/instruments/src/EFB/failures-orchestrator-provider.tsx @@ -35,6 +35,10 @@ const createOrchestrator = () => new FailuresOrchestrator('A32NX', [ [29, A320Failure.GreenReservoirReturnLeak, 'Green reservoir return leak'], [29, A320Failure.BlueReservoirReturnLeak, 'Blue reservoir return leak'], [29, A320Failure.YellowReservoirReturnLeak, 'Yellow reservoir return leak'], + [29, A320Failure.GreenEdpOverheat, 'Green engine pump overheat'], + [29, A320Failure.BlueEpumpOverheat, 'Blue electric pump overheat'], + [29, A320Failure.YellowEdpOverheat, 'Yellow engine pump overheat'], + [29, A320Failure.YellowEpumpOverheat, 'Yellow electric pump overheat'], [31, A320Failure.LeftPfdDisplay, 'Captain PFD display'], [31, A320Failure.RightPfdDisplay, 'F/O PFD display'], diff --git a/src/instruments/src/EWD/elements/PseudoFWC.tsx b/src/instruments/src/EWD/elements/PseudoFWC.tsx index c780cc060101..20b6295389fd 100644 --- a/src/instruments/src/EWD/elements/PseudoFWC.tsx +++ b/src/instruments/src/EWD/elements/PseudoFWC.tsx @@ -192,11 +192,17 @@ const PseudoFWC: React.FC = () => { const [blueLP] = useSimVar('L:A32NX_HYD_BLUE_EDPUMP_LOW_PRESS', 'bool', 500); const [blueSysPressurised] = useSimVar('L:A32NX_HYD_BLUE_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool', 500); const [blueRvrLow] = useSimVar('L:A32NX_HYD_BLUE_RESERVOIR_LEVEL_IS_LOW', 'bool', 500); + + const [yellowRvrOvht] = useSimVar('L:A32NX_HYD_YELLOW_RESERVOIR_OVHT', 'bool', 500); + const [greenRvrOvht] = useSimVar('L:A32NX_HYD_GREEN_RESERVOIR_OVHT', 'bool', 500); + const [blueRvrOvht] = useSimVar('L:A32NX_HYD_BLUE_RESERVOIR_OVHT', 'bool', 500); const [blueElecPumpPBAuto] = useSimVar('L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO', 'bool', 500); const [yellowLP] = useSimVar('L:A32NX_HYD_YELLOW_EDPUMP_LOW_PRESS', 'bool', 500); const [yellowSysPressurised] = useSimVar('L:A32NX_HYD_YELLOW_SYSTEM_1_SECTION_PRESSURE_SWITCH', 'bool', 500); const [eng1pumpPBisAuto] = useSimVar('L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO', 'bool', 500); const [eng2pumpPBisAuto] = useSimVar('L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO', 'bool', 500); + const [yepumpPBisAuto] = useSimVar('L:A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO', 'bool', 500); + const [ptuIsAuto] = useSimVar('L:A32NX_OVHD_HYD_PTU_PB_IS_AUTO', 'bool', 500); const [hydPTU] = useSimVar('L:A32NX_HYD_PTU_ON_ECAM_MEMO', 'bool', 500); const [ratDeployed] = useSimVar('L:A32NX_HYD_RAT_STOW_POSITION', 'percent over 100', 500); @@ -1181,6 +1187,51 @@ const PseudoFWC: React.FC = () => { sysPage: -1, side: 'LEFT', }, + 2900126: // *HYD - Blue reservoir overheat + { + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: blueRvrOvht, + whichCodeToReturn: [ + 0, + blueElecPumpPBAuto ? 1 : null, + ], + codesToReturn: ['290012601', '290012602'], + memoInhibit: false, + failure: 2, + sysPage: 4, + side: 'LEFT', + }, + 2900127: // *HYD - Yellow reservoir overheat + { + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: yellowRvrOvht, + whichCodeToReturn: [ + 0, + ptuIsAuto ? 1 : null, + eng2pumpPBisAuto ? 2 : null, + !yepumpPBisAuto ? 3 : null, + ], + codesToReturn: ['290012701', '290012702', '290012703', '290012704'], + memoInhibit: false, + failure: 2, + sysPage: 4, + side: 'LEFT', + }, + 2900128: // *HYD - Green reservoir overheat + { + flightPhaseInhib: [3, 4, 5, 7, 8], + simVarIsActive: greenRvrOvht, + whichCodeToReturn: [ + 0, + ptuIsAuto ? 1 : null, + eng1pumpPBisAuto ? 2 : null, + ], + codesToReturn: ['290012801', '290012802', '290012803'], + memoInhibit: false, + failure: 2, + sysPage: 4, + side: 'LEFT', + }, 2900310: // *HYD - Blue { flightPhaseInhib: [4, 5], @@ -1940,6 +1991,7 @@ const PseudoFWC: React.FC = () => { autoThrustStatus, blueElecPumpPBAuto, blueRvrLow, + blueRvrOvht, brakeFan, cabAltSetResetState1, cabAltSetResetState2, @@ -1976,10 +2028,12 @@ const PseudoFWC: React.FC = () => { eng1AntiIce, eng1FireTest, engine1State, + eng1pumpPBisAuto, eng2Agent1PB, eng2Agent2PB, eng2AntiIce, eng2FireTest, + eng2pumpPBisAuto, engine2State, engDualFault, engSelectorPosition, @@ -2003,6 +2057,7 @@ const PseudoFWC: React.FC = () => { gpwsFlaps3, gpwsTerrOff, greenHydEng1PBAuto, + greenRvrOvht, height1Failed, height2Failed, hydPTU, @@ -2030,6 +2085,7 @@ const PseudoFWC: React.FC = () => { packOffNotFailure2, parkBrake, predWSOn, + ptuIsAuto, ratDeployed, recallReset, rightOuterInnerValve, @@ -2053,6 +2109,8 @@ const PseudoFWC: React.FC = () => { usrStartRefueling, wingAntiIce, voiceVHF3, + yellowRvrOvht, + yepumpPBisAuto, ]); useEffect(() => { diff --git a/src/instruments/src/SD/Pages/Hyd/Hyd.tsx b/src/instruments/src/SD/Pages/Hyd/Hyd.tsx index a4b94a645cc9..7050243bd558 100644 --- a/src/instruments/src/SD/Pages/Hyd/Hyd.tsx +++ b/src/instruments/src/SD/Pages/Hyd/Hyd.tsx @@ -44,6 +44,8 @@ export const HydPage = () => { const [ACBus1IsPowered] = useSimVar('L:A32NX_ELEC_AC_1_BUS_IS_POWERED', 'bool', 1000); + const [blueElecPumpOvht] = useSimVar('L:A32NX_HYD_BLUE_EPUMP_OVHT', 'bool', 1000); + const [engine1Running, setEngine1Running] = useState(false); const [engine2Running, setEngine2Running] = useState(false); @@ -111,6 +113,13 @@ export const HydPage = () => { > ELEC + + OVHT + @@ -255,6 +264,10 @@ const HydReservoir = ({ system, x, y, lowLevel } : HydReservoirProps) => { const [lowAirPress] = useSimVar(`L:A32NX_HYD_${system}_RESERVOIR_AIR_PRESSURE_IS_LOW`, 'boolean', 1000); + // The overheat indication should be computed by the EIS itself from the numerical temperature value, + // by applying a hysteresis logic. For now, we just use a boolean from the hydraulics directly. + const [overheat] = useSimVar(`L:A32NX_HYD_${system}_RESERVOIR_OVHT`, 'boolean', 1000); + const fluidLevelInLitres = fluidLevel * litersPerGallon; const values = levels[system]; @@ -282,6 +295,10 @@ const HydReservoir = ({ system, x, y, lowLevel } : HydReservoirProps) => { LO AIR PRESS + + { /* Not sure about the exact placement, have to wait for an IRL ref */ } + OVHT + ); }; @@ -295,6 +312,8 @@ type YellowElecPumpProps = { const YellowElecPump = ({ pumpPushbuttonOn, pressure, enginePumpPressureLowSwitch }: YellowElecPumpProps) => { const [ACBus2IsPowered] = useSimVar('L:A32NX_ELEC_AC_2_BUS_IS_POWERED', 'bool', 1000); + const [yellowElecPumpOvht] = useSimVar('L:A32NX_HYD_YELLOW_EPUMP_OVHT', 'bool', 1000); + let elecHorizontalLineFormat: string; let verticalLineFormat: string; let elecTriangleFill: number; @@ -326,6 +345,13 @@ const YellowElecPump = ({ pumpPushbuttonOn, pressure, enginePumpPressureLowSwitc > ELEC + + OVHT + diff --git a/src/systems/a320_hydraulic_simulation_graphs/src/main.rs b/src/systems/a320_hydraulic_simulation_graphs/src/main.rs index 6b1cee0745c2..b1b15da5a197 100644 --- a/src/systems/a320_hydraulic_simulation_graphs/src/main.rs +++ b/src/systems/a320_hydraulic_simulation_graphs/src/main.rs @@ -10,8 +10,8 @@ use systems::hydraulic::*; use systems::{ electrical::{test::TestElectricitySource, ElectricalBus, Electricity}, shared::{ - update_iterator::FixedStepLoop, ElectricalBusType, HydraulicColor, PotentialOrigin, - ReservoirAirPressure, + update_iterator::FixedStepLoop, AirbusElectricPumpId, AirbusEngineDrivenPumpId, + ElectricalBusType, HydraulicColor, PotentialOrigin, ReservoirAirPressure, }, simulation::{ test::{SimulationTestBed, TestBed}, @@ -358,7 +358,7 @@ fn hydraulic_loop(context: &mut InitContext, loop_color: HydraulicColor) -> Hydr fn electric_pump(context: &mut InitContext) -> ElectricPump { ElectricPump::new( context, - "DEFAULT", + AirbusElectricPumpId::Green, ElectricalBusType::AlternatingCurrentGndFltService, ElectricCurrent::new::(45.), PumpCharacteristics::a320_electric_pump(), @@ -366,7 +366,11 @@ fn electric_pump(context: &mut InitContext) -> ElectricPump { } fn _engine_driven_pump(context: &mut InitContext) -> EngineDrivenPump { - EngineDrivenPump::new(context, "DEFAULT", PumpCharacteristics::a320_edp()) + EngineDrivenPump::new( + context, + AirbusEngineDrivenPumpId::Green, + PumpCharacteristics::a320_edp(), + ) } struct A320TestPneumatics { diff --git a/src/systems/a320_systems/src/hydraulic/mod.rs b/src/systems/a320_systems/src/hydraulic/mod.rs index 32bb3ae3282a..e301f3fd3297 100644 --- a/src/systems/a320_systems/src/hydraulic/mod.rs +++ b/src/systems/a320_systems/src/hydraulic/mod.rs @@ -46,10 +46,10 @@ use systems::{ ManualPitchTrimController, PitchTrimActuatorController, TrimmableHorizontalStabilizerAssembly, }, - ElectricPump, EngineDrivenPump, HydraulicCircuit, HydraulicCircuitController, - HydraulicPressureSensors, PowerTransferUnit, PowerTransferUnitCharacteristics, - PowerTransferUnitController, PressureSwitch, PressureSwitchType, PumpController, - RamAirTurbine, RamAirTurbineController, Reservoir, + ElectricPump, EngineDrivenPump, HeatingElement, HydraulicCircuit, + HydraulicCircuitController, HydraulicPressureSensors, PowerTransferUnit, + PowerTransferUnitCharacteristics, PowerTransferUnitController, PressureSwitch, + PressureSwitchType, PumpController, RamAirTurbine, RamAirTurbineController, Reservoir, }, landing_gear::{GearSystemSensors, LandingGearControlInterfaceUnitSet}, overhead::{ @@ -60,11 +60,12 @@ use systems::{ low_pass_filter::LowPassFilter, random_from_normal_distribution, random_from_range, update_iterator::{FixedStepLoop, MaxStepLoop}, - AdirsDiscreteOutputs, DelayedFalseLogicGate, DelayedPulseTrueLogicGate, - DelayedTrueLogicGate, ElectricalBusType, ElectricalBuses, EmergencyElectricalRatPushButton, - EmergencyElectricalState, EmergencyGeneratorPower, EngineFirePushButtons, GearWheel, - HydraulicColor, HydraulicGeneratorControlUnit, LandingGearHandle, LgciuInterface, - LgciuWeightOnWheels, ReservoirAirPressure, SectionPressure, TrimmableHorizontalStabilizer, + AdirsDiscreteOutputs, AirbusElectricPumpId, AirbusEngineDrivenPumpId, + DelayedFalseLogicGate, DelayedPulseTrueLogicGate, DelayedTrueLogicGate, ElectricalBusType, + ElectricalBuses, EmergencyElectricalRatPushButton, EmergencyElectricalState, + EmergencyGeneratorPower, EngineFirePushButtons, GearWheel, HydraulicColor, + HydraulicGeneratorControlUnit, LandingGearHandle, LgciuInterface, LgciuWeightOnWheels, + ReservoirAirPressure, SectionPressure, TrimmableHorizontalStabilizer, }, simulation::{ InitContext, Read, Reader, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -1590,7 +1591,7 @@ impl A320Hydraulic { engine_driven_pump_1: EngineDrivenPump::new( context, - "GREEN", + AirbusEngineDrivenPumpId::Green, PumpCharacteristics::a320_edp(), ), engine_driven_pump_1_controller: A320EngineDrivenPumpController::new( @@ -1601,7 +1602,7 @@ impl A320Hydraulic { engine_driven_pump_2: EngineDrivenPump::new( context, - "YELLOW", + AirbusEngineDrivenPumpId::Yellow, PumpCharacteristics::a320_edp(), ), engine_driven_pump_2_controller: A320EngineDrivenPumpController::new( @@ -1615,7 +1616,7 @@ impl A320Hydraulic { blue_electric_pump: ElectricPump::new( context, - "BLUE", + AirbusElectricPumpId::Blue, Self::BLUE_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), PumpCharacteristics::a320_electric_pump(), @@ -1627,7 +1628,7 @@ impl A320Hydraulic { yellow_electric_pump: ElectricPump::new( context, - "YELLOW", + AirbusElectricPumpId::Yellow, Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), PumpCharacteristics::a320_electric_pump(), @@ -1836,6 +1837,7 @@ impl A320Hydraulic { self.power_transfer_unit_controller .has_air_pressure_low_fault() || self.power_transfer_unit_controller.has_low_level_fault() + || self.power_transfer_unit_controller.has_overheat_fault() } fn green_edp_has_fault(&self) -> bool { @@ -1845,6 +1847,7 @@ impl A320Hydraulic { .engine_driven_pump_1_controller .has_air_pressure_low_fault() || self.engine_driven_pump_1_controller.has_low_level_fault() + || self.engine_driven_pump_1_controller.has_overheat_fault() } fn yellow_epump_has_fault(&self) -> bool { @@ -1854,6 +1857,7 @@ impl A320Hydraulic { .yellow_electric_pump_controller .has_air_pressure_low_fault() || self.yellow_electric_pump_controller.has_low_level_fault() + || self.yellow_electric_pump_controller.has_overheat_fault() } fn yellow_edp_has_fault(&self) -> bool { @@ -1863,6 +1867,7 @@ impl A320Hydraulic { .engine_driven_pump_2_controller .has_air_pressure_low_fault() || self.engine_driven_pump_2_controller.has_low_level_fault() + || self.engine_driven_pump_2_controller.has_overheat_fault() } fn blue_epump_has_fault(&self) -> bool { @@ -1871,6 +1876,7 @@ impl A320Hydraulic { .blue_electric_pump_controller .has_air_pressure_low_fault() || self.blue_electric_pump_controller.has_low_level_fault() + || self.blue_electric_pump_controller.has_overheat_fault() } pub fn green_reservoir(&self) -> &Reservoir { @@ -2339,6 +2345,7 @@ impl A320Hydraulic { lgciu1, lgciu2, self.blue_circuit.reservoir(), + &self.blue_electric_pump, ); self.blue_electric_pump.update( context, @@ -2355,6 +2362,7 @@ impl A320Hydraulic { &self.aft_cargo_door_controller, &self.yellow_circuit, self.yellow_circuit.reservoir(), + &self.yellow_electric_pump, ); self.yellow_electric_pump.update( context, @@ -2477,6 +2485,7 @@ impl A320Hydraulic { > Pressure::new::(Self::HIGH_PITCH_PTU_SOUND_DELTA_PRESS_THRESHOLD_PSI) && is_ptu_rotating && !self.ptu_high_pitch_sound_active.output() + && !self.power_transfer_unit.is_in_continuous_mode() } pub fn gear_system(&self) -> &impl GearSystemSensors { @@ -2775,6 +2784,7 @@ struct A320EngineDrivenPumpController { has_air_pressure_low_fault: bool, has_low_level_fault: bool, is_pressure_low: bool, + has_overheat_fault: bool, } impl A320EngineDrivenPumpController { fn new( @@ -2798,6 +2808,8 @@ impl A320EngineDrivenPumpController { has_low_level_fault: false, is_pressure_low: true, + + has_overheat_fault: false, } } @@ -2865,6 +2877,8 @@ impl A320EngineDrivenPumpController { self.update_low_air_pressure(reservoir, overhead_panel); self.update_low_level(reservoir, overhead_panel); + + self.has_overheat_fault = reservoir.is_overheating(); } fn has_pressure_low_fault(&self) -> bool { @@ -2878,6 +2892,10 @@ impl A320EngineDrivenPumpController { fn has_low_level_fault(&self) -> bool { self.has_low_level_fault } + + fn has_overheat_fault(&self) -> bool { + self.has_overheat_fault + } } impl PumpController for A320EngineDrivenPumpController { fn should_pressurise(&self) -> bool { @@ -2910,6 +2928,7 @@ struct A320BlueElectricPumpController { has_air_pressure_low_fault: bool, has_low_level_fault: bool, is_pressure_low: bool, + has_overheat_fault: bool, } impl A320BlueElectricPumpController { fn new(context: &mut InitContext, powered_by: ElectricalBusType) -> Self { @@ -2925,6 +2944,8 @@ impl A320BlueElectricPumpController { has_low_level_fault: false, is_pressure_low: true, + + has_overheat_fault: false, } } @@ -2937,6 +2958,7 @@ impl A320BlueElectricPumpController { lgciu1: &impl LgciuInterface, lgciu2: &impl LgciuInterface, reservoir: &Reservoir, + elec_pump: &impl HeatingElement, ) { let mut should_pressurise_if_powered = false; if overhead_panel.blue_epump_push_button.is_auto() { @@ -2967,6 +2989,9 @@ impl A320BlueElectricPumpController { self.update_low_air_pressure(reservoir, overhead_panel); self.update_low_level(reservoir, overhead_panel); + + // Elec pump has temperature sensor so we check also pump overheating state + self.has_overheat_fault = elec_pump.is_overheating() || reservoir.is_overheating(); } fn update_low_pressure( @@ -3024,6 +3049,10 @@ impl A320BlueElectricPumpController { fn has_low_level_fault(&self) -> bool { self.has_low_level_fault } + + fn has_overheat_fault(&self) -> bool { + self.has_low_level_fault + } } impl PumpController for A320BlueElectricPumpController { fn should_pressurise(&self) -> bool { @@ -3056,6 +3085,8 @@ struct A320YellowElectricPumpController { should_pressurise_for_cargo_door_operation: bool, low_pressure_hystereris: bool, + + has_overheat_fault: bool, } impl A320YellowElectricPumpController { const DURATION_OF_YELLOW_PUMP_ACTIVATION_AFTER_CARGO_DOOR_OPERATION: Duration = @@ -3088,6 +3119,8 @@ impl A320YellowElectricPumpController { should_pressurise_for_cargo_door_operation: false, low_pressure_hystereris: false, + + has_overheat_fault: false, } } @@ -3099,6 +3132,7 @@ impl A320YellowElectricPumpController { aft_cargo_door_controller: &A320DoorController, hydraulic_circuit: &impl HydraulicPressureSensors, reservoir: &Reservoir, + elec_pump: &impl HeatingElement, ) { self.update_cargo_door_logic( context, @@ -3116,6 +3150,9 @@ impl A320YellowElectricPumpController { self.update_low_air_pressure(reservoir, overhead_panel); self.update_low_level(reservoir, overhead_panel); + + // Elec pump has temperature sensor so we check also pump overheating state + self.has_overheat_fault = elec_pump.is_overheating() || reservoir.is_overheating(); } fn update_low_pressure(&mut self, hydraulic_circuit: &impl HydraulicPressureSensors) { @@ -3193,6 +3230,10 @@ impl A320YellowElectricPumpController { self.has_low_level_fault } + fn has_overheat_fault(&self) -> bool { + self.has_overheat_fault + } + fn should_pressurise_for_cargo_door_operation(&self) -> bool { self.should_pressurise_for_cargo_door_operation } @@ -3231,6 +3272,7 @@ struct A320PowerTransferUnitController { has_air_pressure_low_fault: bool, has_low_level_fault: bool, + has_overheat_fault: bool, } impl A320PowerTransferUnitController { const DURATION_OF_PTU_INHIBIT_AFTER_CARGO_DOOR_OPERATION: Duration = Duration::from_secs(40); @@ -3256,6 +3298,7 @@ impl A320PowerTransferUnitController { has_air_pressure_low_fault: false, has_low_level_fault: false, + has_overheat_fault: false, } } @@ -3293,6 +3336,9 @@ impl A320PowerTransferUnitController { self.update_low_air_pressure(reservoir_left_side, reservoir_right_side, overhead_panel); self.update_low_level(reservoir_left_side, reservoir_right_side, overhead_panel); + + self.has_overheat_fault = + reservoir_left_side.is_overheating() || reservoir_right_side.is_overheating(); } fn update_low_air_pressure( @@ -3324,6 +3370,10 @@ impl A320PowerTransferUnitController { fn has_low_level_fault(&self) -> bool { self.has_low_level_fault } + + fn has_overheat_fault(&self) -> bool { + self.has_overheat_fault + } } impl PowerTransferUnitController for A320PowerTransferUnitController { fn should_enable(&self) -> bool { @@ -6530,6 +6580,14 @@ mod tests { self.read_by_name("OVHD_HYD_EPUMPY_PB_HAS_FAULT") } + fn yellow_reservoir_has_overheat_fault(&mut self) -> bool { + self.read_by_name("HYD_YELLOW_RESERVOIR_OVHT") + } + + fn green_reservoir_has_overheat_fault(&mut self) -> bool { + self.read_by_name("HYD_GREEN_RESERVOIR_OVHT") + } + fn ptu_has_fault(&mut self) -> bool { self.read_by_name("OVHD_HYD_PTU_PB_HAS_FAULT") } @@ -11228,5 +11286,80 @@ mod tests { assert!(test_bed.is_all_gears_really_up()); assert!(test_bed.is_all_doors_really_up()); } + + #[test] + fn empty_green_reservoir_causes_yellow_overheat_if_ptu_on() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::ReservoirLeak(HydraulicColor::Green)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(120.)); + assert!(test_bed.yellow_reservoir_has_overheat_fault()); + } + + #[test] + fn empty_yellow_reservoir_causes_green_overheat_if_ptu_on() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::ReservoirLeak(HydraulicColor::Yellow)); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(120.)); + assert!(test_bed.green_reservoir_has_overheat_fault()); + } + + #[test] + fn green_edp_overheat_failure_causes_green_reservoir_overheat() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::EnginePumpOverheat( + AirbusEngineDrivenPumpId::Green, + )); + + test_bed = test_bed.run_waiting_for(Duration::from_secs_f64(120.)); + assert!(test_bed.green_reservoir_has_overheat_fault()); + } + + #[test] + fn green_edp_overheat_failure_do_not_causes_green_reservoir_overheat_if_unpressurised() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::EnginePumpOverheat( + AirbusEngineDrivenPumpId::Green, + )); + + test_bed = test_bed + .set_green_ed_pump(false) + .run_waiting_for(Duration::from_secs_f64(120.)); + assert!(!test_bed.green_reservoir_has_overheat_fault()); + } + + #[test] + fn yellow_edp_overheat_failure_do_not_causes_yellow_reservoir_overheat_if_unpressurised() { + let mut test_bed = test_bed_in_flight_with() + .set_cold_dark_inputs() + .in_flight() + .run_waiting_for(Duration::from_secs_f64(1.)); + + test_bed.fail(FailureType::EnginePumpOverheat( + AirbusEngineDrivenPumpId::Yellow, + )); + + test_bed = test_bed + .set_yellow_ed_pump(false) + .run_waiting_for(Duration::from_secs_f64(120.)); + assert!(!test_bed.yellow_reservoir_has_overheat_fault()); + } } } diff --git a/src/systems/a320_systems_wasm/src/lib.rs b/src/systems/a320_systems_wasm/src/lib.rs index 5cb620b0b939..4798eb5138bb 100644 --- a/src/systems/a320_systems_wasm/src/lib.rs +++ b/src/systems/a320_systems_wasm/src/lib.rs @@ -22,7 +22,8 @@ use spoilers::spoilers; use std::error::Error; use systems::failures::FailureType; use systems::shared::{ - ElectricalBusType, GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId, + AirbusElectricPumpId, AirbusEngineDrivenPumpId, ElectricalBusType, GearActuatorId, + HydraulicColor, LgciuId, ProximityDetectorId, }; use systems_wasm::aspects::ExecuteOn; use systems_wasm::{MsfsSimulationBuilder, Variable}; @@ -80,12 +81,28 @@ async fn systems(mut gauge: msfs::Gauge) -> Result<(), Box> { 29_008, FailureType::ReservoirReturnLeak(HydraulicColor::Yellow), ), - (31_500, FailureType::FlightWarningComputer(1)), - (31_501, FailureType::FlightWarningComputer(2)), + ( + 29_009, + FailureType::EnginePumpOverheat(AirbusEngineDrivenPumpId::Green), + ), + ( + 29_010, + FailureType::ElecPumpOverheat(AirbusElectricPumpId::Blue), + ), + ( + 29_011, + FailureType::EnginePumpOverheat(AirbusEngineDrivenPumpId::Yellow), + ), + ( + 29_012, + FailureType::ElecPumpOverheat(AirbusElectricPumpId::Yellow), + ), (32_000, FailureType::LgciuPowerSupply(LgciuId::Lgciu1)), (32_001, FailureType::LgciuPowerSupply(LgciuId::Lgciu2)), (32_002, FailureType::LgciuInternalError(LgciuId::Lgciu1)), (32_003, FailureType::LgciuInternalError(LgciuId::Lgciu2)), + (31_500, FailureType::FlightWarningComputer(1)), + (31_501, FailureType::FlightWarningComputer(2)), ( 32_004, FailureType::GearProxSensorDamage(ProximityDetectorId::UplockGearNose1), diff --git a/src/systems/a380_systems/src/hydraulic/mod.rs b/src/systems/a380_systems/src/hydraulic/mod.rs index 7df54fa1ad0c..f093da09f1eb 100644 --- a/src/systems/a380_systems/src/hydraulic/mod.rs +++ b/src/systems/a380_systems/src/hydraulic/mod.rs @@ -49,10 +49,11 @@ use systems::{ overhead::{AutoOffFaultPushButton, AutoOnFaultPushButton}, shared::{ interpolation, low_pass_filter::LowPassFilter, random_from_range, - update_iterator::MaxStepLoop, AdirsDiscreteOutputs, DelayedFalseLogicGate, - DelayedPulseTrueLogicGate, DelayedTrueLogicGate, ElectricalBusType, ElectricalBuses, - EngineFirePushButtons, GearWheel, HydraulicColor, LandingGearHandle, LgciuInterface, - LgciuWeightOnWheels, ReservoirAirPressure, SectionPressure, + update_iterator::MaxStepLoop, AdirsDiscreteOutputs, AirbusElectricPumpId, + AirbusEngineDrivenPumpId, DelayedFalseLogicGate, DelayedPulseTrueLogicGate, + DelayedTrueLogicGate, ElectricalBusType, ElectricalBuses, EngineFirePushButtons, GearWheel, + HydraulicColor, LandingGearHandle, LgciuInterface, LgciuWeightOnWheels, + ReservoirAirPressure, SectionPressure, }, simulation::{ InitContext, Read, Reader, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -1615,7 +1616,7 @@ impl A380Hydraulic { engine_driven_pump_1a: EngineDrivenPump::new( context, - "GREEN_1A", + AirbusEngineDrivenPumpId::Edp1a, PumpCharacteristics::a380_edp(), ), engine_driven_pump_1a_controller: A380EngineDrivenPumpController::new( @@ -1626,7 +1627,7 @@ impl A380Hydraulic { engine_driven_pump_2a: EngineDrivenPump::new( context, - "GREEN_2A", + AirbusEngineDrivenPumpId::Edp2a, PumpCharacteristics::a380_edp(), ), engine_driven_pump_2a_controller: A380EngineDrivenPumpController::new( @@ -1637,7 +1638,7 @@ impl A380Hydraulic { engine_driven_pump_3a: EngineDrivenPump::new( context, - "YELLOW_3A", + AirbusEngineDrivenPumpId::Edp3a, PumpCharacteristics::a380_edp(), ), engine_driven_pump_3a_controller: A380EngineDrivenPumpController::new( @@ -1648,7 +1649,7 @@ impl A380Hydraulic { engine_driven_pump_4a: EngineDrivenPump::new( context, - "YELLOW_4A", + AirbusEngineDrivenPumpId::Edp4a, PumpCharacteristics::a380_edp(), ), engine_driven_pump_4a_controller: A380EngineDrivenPumpController::new( @@ -1659,7 +1660,7 @@ impl A380Hydraulic { engine_driven_pump_1b: EngineDrivenPump::new( context, - "GREEN_1B", + AirbusEngineDrivenPumpId::Edp1b, PumpCharacteristics::a380_edp(), ), engine_driven_pump_1b_controller: A380EngineDrivenPumpController::new( @@ -1670,7 +1671,7 @@ impl A380Hydraulic { engine_driven_pump_2b: EngineDrivenPump::new( context, - "GREEN_2B", + AirbusEngineDrivenPumpId::Edp2b, PumpCharacteristics::a380_edp(), ), engine_driven_pump_2b_controller: A380EngineDrivenPumpController::new( @@ -1681,7 +1682,7 @@ impl A380Hydraulic { engine_driven_pump_3b: EngineDrivenPump::new( context, - "YELLOW_3B", + AirbusEngineDrivenPumpId::Edp3b, PumpCharacteristics::a380_edp(), ), engine_driven_pump_3b_controller: A380EngineDrivenPumpController::new( @@ -1692,7 +1693,7 @@ impl A380Hydraulic { engine_driven_pump_4b: EngineDrivenPump::new( context, - "YELLOW_4B", + AirbusEngineDrivenPumpId::Edp4b, PumpCharacteristics::a380_edp(), ), engine_driven_pump_4b_controller: A380EngineDrivenPumpController::new( @@ -1703,7 +1704,7 @@ impl A380Hydraulic { yellow_electric_pump_a: ElectricPump::new( context, - "YELLOW_A", + AirbusElectricPumpId::YellowA, Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), PumpCharacteristics::a380_electric_pump(), @@ -1717,7 +1718,7 @@ impl A380Hydraulic { yellow_electric_pump_b: ElectricPump::new( context, - "YELLOW_B", + AirbusElectricPumpId::YellowB, Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), PumpCharacteristics::a380_electric_pump(), @@ -1731,7 +1732,7 @@ impl A380Hydraulic { green_electric_pump_a: ElectricPump::new( context, - "GREEN_A", + AirbusElectricPumpId::GreenA, Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), PumpCharacteristics::a380_electric_pump(), @@ -1745,7 +1746,7 @@ impl A380Hydraulic { green_electric_pump_b: ElectricPump::new( context, - "GREEN_B", + AirbusElectricPumpId::GreenB, Self::YELLOW_ELEC_PUMP_SUPPLY_POWER_BUS, ElectricCurrent::new::(Self::ELECTRIC_PUMP_MAX_CURRENT_AMPERE), PumpCharacteristics::a380_electric_pump(), diff --git a/src/systems/systems/src/failures/mod.rs b/src/systems/systems/src/failures/mod.rs index 86f5422ca376..c25626bf651c 100644 --- a/src/systems/systems/src/failures/mod.rs +++ b/src/systems/systems/src/failures/mod.rs @@ -1,4 +1,7 @@ -use crate::shared::{GearActuatorId, HydraulicColor, LgciuId, ProximityDetectorId}; +use crate::shared::{ + AirbusElectricPumpId, AirbusEngineDrivenPumpId, GearActuatorId, HydraulicColor, LgciuId, + ProximityDetectorId, +}; use crate::simulation::SimulationElement; #[derive(Clone, Copy, PartialEq, Eq)] @@ -7,6 +10,8 @@ pub enum FailureType { ReservoirLeak(HydraulicColor), ReservoirAirLeak(HydraulicColor), ReservoirReturnLeak(HydraulicColor), + EnginePumpOverheat(AirbusEngineDrivenPumpId), + ElecPumpOverheat(AirbusElectricPumpId), LgciuPowerSupply(LgciuId), LgciuInternalError(LgciuId), GearProxSensorDamage(ProximityDetectorId), diff --git a/src/systems/systems/src/hydraulic/electrical_pump_physics.rs b/src/systems/systems/src/hydraulic/electrical_pump_physics.rs index 44d73d953b22..d7e9f29fb1e2 100644 --- a/src/systems/systems/src/hydraulic/electrical_pump_physics.rs +++ b/src/systems/systems/src/hydraulic/electrical_pump_physics.rs @@ -6,17 +6,23 @@ use uom::si::{ f64::*, power::watt, pressure::psi, + ratio::ratio, torque::{newton_meter, pound_force_inch}, volume::cubic_inch, }; -use crate::hydraulic::SectionPressure; +use crate::hydraulic::{HeatingElement, HeatingProperties, SectionPressure}; use crate::shared::{ - low_pass_filter::LowPassFilter, pid::PidController, ConsumePower, ElectricalBusType, - ElectricalBuses, + low_pass_filter::LowPassFilter, pid::PidController, random_from_normal_distribution, + ConsumePower, ElectricalBusType, ElectricalBuses, }; use crate::simulation::{ - InitContext, SimulationElement, SimulatorWriter, UpdateContext, VariableIdentifier, Write, + InitContext, SimulationElement, SimulationElementVisitor, SimulatorWriter, UpdateContext, + VariableIdentifier, Write, +}; +use crate::{ + failures::{Failure, FailureType}, + shared::AirbusElectricPumpId, }; use std::time::Duration; @@ -43,6 +49,9 @@ pub(super) struct ElectricalPumpPhysics { current_controller: PidController, displacement_filtered: LowPassFilter, + + overheat_failure: Failure, + heat_state: HeatingProperties, } impl ElectricalPumpPhysics { const DEFAULT_INERTIA: f64 = 0.011; @@ -60,9 +69,17 @@ impl ElectricalPumpPhysics { const DEFAULT_P_GAIN: f64 = 0.1; const DEFAULT_I_GAIN: f64 = 0.45; + const HEATING_TIME_CONSTANT_MEAN_S: f64 = 30.; + const HEATING_TIME_CONSTANT_STD_S: f64 = 5.; + + const COOLING_TIME_CONSTANT: Duration = Duration::from_secs(60 * 2); + const DAMAGE_TIME_CONSTANT: Duration = Duration::from_secs(60 * 2); + + const MIN_SPEED_TO_REPORT_ACTIVE_RPM: f64 = 10.; + pub fn new( context: &mut InitContext, - id: &str, + id: AirbusElectricPumpId, bus_type: ElectricalBusType, max_current: ElectricCurrent, regulated_speed: AngularVelocity, @@ -97,6 +114,18 @@ impl ElectricalPumpPhysics { displacement_filtered: LowPassFilter::::new( Self::SPEED_DISPLACEMENT_FILTER_TIME_CONSTANT, ), + overheat_failure: Failure::new(FailureType::ElecPumpOverheat(id)), + heat_state: HeatingProperties::new( + Duration::from_secs_f64( + random_from_normal_distribution( + Self::HEATING_TIME_CONSTANT_MEAN_S, + Self::HEATING_TIME_CONSTANT_STD_S, + ) + .max(10.), + ), + Self::COOLING_TIME_CONSTANT, + Self::DAMAGE_TIME_CONSTANT, + ), } } @@ -106,6 +135,11 @@ impl ElectricalPumpPhysics { section: &impl SectionPressure, current_displacement: Volume, ) { + self.heat_state.update( + context, + self.overheat_failure.is_active() && self.speed().get::() > 100., + ); + self.displacement_filtered .update(context.delta(), current_displacement); @@ -155,11 +189,20 @@ impl ElectricalPumpPhysics { Torque::new::(Self::DEFAULT_RESISTANT_TORQUE_WHEN_OFF_NEWTON_METER) }; - self.resistant_torque = pumping_torque + dynamic_friction_torque; + let overheat_resistant_torque_factor = if !self.heat_state.is_overheating() { + 1. + } else if !self.heat_state.is_damaged() { + 50. * self.heat_state.overheat_ratio().get::() + } else { + 100. + }; + + self.resistant_torque = + pumping_torque + dynamic_friction_torque * overheat_resistant_torque_factor; } fn update_current_control(&mut self, context: &UpdateContext) { - self.output_current = if self.pump_should_run() { + self.output_current = if self.pump_should_run() && !self.is_damaged() { ElectricCurrent::new::(self.current_controller.next_control_output( self.speed_raw.get::(), Some(context.delta()), @@ -187,7 +230,7 @@ impl ElectricalPumpPhysics { self.update_electrical_power_consumption(); - if self.pump_should_run() { + if self.pump_should_run() && !self.is_damaged() { if self.speed_raw.get::() < 5. && self.output_current.get::() > 0. { @@ -217,8 +260,18 @@ impl ElectricalPumpPhysics { } } impl SimulationElement for ElectricalPumpPhysics { + fn accept(&mut self, visitor: &mut T) { + self.overheat_failure.accept(visitor); + visitor.visit(self); + } + fn write(&self, writer: &mut SimulatorWriter) { - writer.write(&self.active_id, self.is_active); + writer.write( + &self.active_id, + self.is_active + && self.speed().get::() + > Self::MIN_SPEED_TO_REPORT_ACTIVE_RPM, + ); writer.write(&self.rpm_id, self.speed()); } @@ -231,6 +284,15 @@ impl SimulationElement for ElectricalPumpPhysics { consumption.consume_from_bus(self.powered_by, self.consumed_power); } } +impl HeatingElement for ElectricalPumpPhysics { + fn is_damaged(&self) -> bool { + self.heat_state.is_damaged() + } + + fn is_overheating(&self) -> bool { + self.heat_state.is_overheating() + } +} #[cfg(test)] mod tests { @@ -437,10 +499,45 @@ mod tests { ); } + #[test] + fn pump_with_overheat_failure_overheats_and_fails() { + let mut test_bed = SimulationTestBed::new(TestAircraft::new); + + test_bed.command(|a| a.set_ac_1_power(true)); + test_bed.command(|a| a.pump.set_active(true)); + test_bed.command(|a| a.set_current_displacement(Volume::new::(0.))); + test_bed.command(|a| a.set_current_pressure(Pressure::new::(3000.))); + + test_bed.run_with_delta(Duration::from_secs_f64(1.)); + + assert!( + test_bed.query(|a| a.pump.speed()) + >= AngularVelocity::new::(7000.) + ); + + test_bed.fail(FailureType::ElecPumpOverheat(AirbusElectricPumpId::Yellow)); + + test_bed.run_with_delta(Duration::from_secs_f64( + ElectricalPumpPhysics::HEATING_TIME_CONSTANT_MEAN_S + + 4. * ElectricalPumpPhysics::HEATING_TIME_CONSTANT_STD_S, + )); + + assert!(test_bed.query(|a| a.pump.is_overheating())); + + test_bed.run_with_delta(ElectricalPumpPhysics::DAMAGE_TIME_CONSTANT); + + assert!(test_bed.query(|a| a.pump.is_damaged())); + + assert!( + test_bed.query(|a| a.pump.speed()) + <= AngularVelocity::new::(100.) + ); + } + fn physical_pump(context: &mut InitContext) -> ElectricalPumpPhysics { ElectricalPumpPhysics::new( context, - "YELLOW", + AirbusElectricPumpId::Yellow, ElectricalBusType::AlternatingCurrent(1), ElectricCurrent::new::(45.), AngularVelocity::new::(7600.), diff --git a/src/systems/systems/src/hydraulic/mod.rs b/src/systems/systems/src/hydraulic/mod.rs index 8064bf361c08..7c1071466738 100644 --- a/src/systems/systems/src/hydraulic/mod.rs +++ b/src/systems/systems/src/hydraulic/mod.rs @@ -4,10 +4,11 @@ use crate::hydraulic::{ electrical_pump_physics::ElectricalPumpPhysics, pumps::PumpCharacteristics, }; use crate::pneumatic::PressurizeableReservoir; + use crate::shared::{ interpolation, low_pass_filter::LowPassFilter, random_from_normal_distribution, - random_from_range, DelayedTrueLogicGate, ElectricalBusType, ElectricalBuses, HydraulicColor, - SectionPressure, + random_from_range, AirbusElectricPumpId, AirbusEngineDrivenPumpId, DelayedTrueLogicGate, + ElectricalBusType, ElectricalBuses, HydraulicColor, SectionPressure, }; use crate::simulation::{ InitContext, Read, SimulationElement, SimulationElementVisitor, SimulatorReader, @@ -75,15 +76,48 @@ pub trait PressureSource { pub struct Fluid { current_bulk: Pressure, + heat_state: HeatingProperties, } impl Fluid { + const HEATING_TIME_CONSTANT_MEAN_S: f64 = 40.; + const HEATING_TIME_CONSTANT_STD_S: f64 = 10.; + + const COOLING_TIME_CONSTANT: Duration = Duration::from_secs(60 * 3); + const DAMAGE_TIME_CONSTANT: Duration = Duration::from_secs(60 * 3); + pub fn new(bulk: Pressure) -> Self { - Self { current_bulk: bulk } + Self { + current_bulk: bulk, + heat_state: HeatingProperties::new( + Duration::from_secs_f64( + random_from_normal_distribution( + Self::HEATING_TIME_CONSTANT_MEAN_S, + Self::HEATING_TIME_CONSTANT_STD_S, + ) + .max(10.), + ), + Self::COOLING_TIME_CONSTANT, + Self::DAMAGE_TIME_CONSTANT, + ), + } } pub fn bulk_mod(&self) -> Pressure { self.current_bulk } + + fn update(&mut self, context: &UpdateContext, is_heating: bool) { + self.heat_state.update(context, is_heating); + } +} +impl HeatingElement for Fluid { + fn is_overheating(&self) -> bool { + self.heat_state.is_overheating() + } + + fn is_damaged(&self) -> bool { + self.heat_state.is_damaged() + } } #[derive(PartialEq, Eq, Clone, Copy)] @@ -174,6 +208,80 @@ impl LevelSwitch { } } +pub trait HeatingElement { + fn is_overheating(&self) -> bool { + false + } + fn is_damaged(&self) -> bool { + false + } +} + +pub trait HeatingPressureSource: PressureSource + HeatingElement {} + +pub struct HeatingProperties { + is_overheating: bool, + is_damaged_by_heat: bool, + + damaging_time: DelayedTrueLogicGate, + + heat_factor: LowPassFilter, + heat_time: Duration, + cool_time: Duration, +} +impl HeatingProperties { + const OVERHEATING_THRESHOLD: f64 = 0.5; + + fn new(heat_time: Duration, cool_time: Duration, damage_time: Duration) -> Self { + Self { + is_overheating: false, + is_damaged_by_heat: false, + damaging_time: DelayedTrueLogicGate::new(damage_time), + heat_factor: LowPassFilter::new(heat_time), + heat_time, + cool_time, + } + } + + fn update(&mut self, context: &UpdateContext, is_heating: bool) { + if is_heating { + self.heat_factor.set_time_constant(self.heat_time); + self.heat_factor + .update(context.delta(), Ratio::new::(1.)); + } else { + self.heat_factor.set_time_constant(self.cool_time); + self.heat_factor + .update(context.delta(), Ratio::new::(0.)); + }; + + self.is_overheating = + self.heat_factor.output().get::() > Self::OVERHEATING_THRESHOLD; + + self.damaging_time.update(context, self.is_overheating); + self.is_damaged_by_heat = self.is_damaged_by_heat || self.damaging_time.output(); + } + + /// When overheating, provides a ratio of the heating severity + /// Above OVERHEATING_THRESHOLD it will rise from 0 to 1, while always 0 under the threshold + fn overheat_ratio(&self) -> Ratio { + Ratio::new::( + ((self.heat_factor.output().get::() - Self::OVERHEATING_THRESHOLD) + / (1. - Self::OVERHEATING_THRESHOLD)) + .max(0.) + .min(1.), + ) + } +} +impl HeatingElement for HeatingProperties { + fn is_overheating(&self) -> bool { + self.is_overheating + } + + fn is_damaged(&self) -> bool { + self.is_damaged_by_heat + } +} + pub trait PowerTransferUnitController { fn should_enable(&self) -> bool; } @@ -224,6 +332,8 @@ pub struct PowerTransferUnit { has_stopped_since_last_write: bool, efficiency: Ratio, + + heat_state: HeatingProperties, } impl PowerTransferUnit { const MIN_SPEED_SIMULATION_RPM: f64 = 50.; @@ -258,6 +368,13 @@ impl PowerTransferUnit { const THRESHOLD_DELTA_TO_DECLARE_CONTINUOUS_RPM: f64 = 400.; const DURATION_BEFORE_CAPTURING_BARK_STRENGTH_SPEED: Duration = Duration::from_millis(133); + const HEATING_TIME_CONSTANT_MEAN_S: f64 = 20.; + const HEATING_TIME_CONSTANT_STD_S: f64 = 5.; + const COOLING_TIME_CONSTANT: Duration = Duration::from_secs(60 * 3); + const DAMAGE_TIME_CONSTANT: Duration = Duration::from_secs(60 * 3); + + const MAX_SPEED_BEFORE_HEATING_UP_RPM: f64 = 2000.; + pub fn new( context: &mut InitContext, characteristics: &impl PowerTransferUnitCharacteristics, @@ -304,6 +421,18 @@ impl PowerTransferUnit { has_stopped_since_last_write: false, efficiency: characteristics.efficiency(), + + heat_state: HeatingProperties::new( + Duration::from_secs_f64( + random_from_normal_distribution( + Self::HEATING_TIME_CONSTANT_MEAN_S, + Self::HEATING_TIME_CONSTANT_STD_S, + ) + .max(10.), + ), + Self::COOLING_TIME_CONSTANT, + Self::DAMAGE_TIME_CONSTANT, + ), } } @@ -338,6 +467,12 @@ impl PowerTransferUnit { self.update_continuous_state(context); self.capture_bark_strength(); self.update_flows(); + + self.heat_state.update( + context, + self.shaft_speed.get::().abs() + > Self::MAX_SPEED_BEFORE_HEATING_UP_RPM, + ); } fn update_displacement( @@ -432,12 +567,16 @@ impl PowerTransferUnit { let left_side_torque = -Self::calc_generated_torque(left_pressure, self.left_displacement); let right_side_torque = Self::calc_generated_torque(right_pressure, self.right_displacement.output()); + let friction_torque = Torque::new::( Self::SHAFT_FRICTION * -self.shaft_speed.get::(), ); + let total_torque = friction_torque + left_side_torque + right_side_torque; - if self.is_rotating() || total_torque.abs().get::() > Self::BREAKOUT_TORQUE_NM + if !self.heat_state.is_damaged() + && (self.is_rotating() + || total_torque.abs().get::() > Self::BREAKOUT_TORQUE_NM) { let acc = total_torque.get::() / Self::SHAFT_INERTIA; self.shaft_speed += @@ -594,6 +733,15 @@ impl SimulationElement for PowerTransferUnit { self.has_stopped_since_last_write = false; } } +impl HeatingElement for PowerTransferUnit { + fn is_overheating(&self) -> bool { + self.heat_state.is_overheating() + } + + fn is_damaged(&self) -> bool { + self.heat_state.is_damaged() + } +} pub trait HydraulicCircuitController { fn should_open_fire_shutoff_valve(&self, pump_index: usize) -> bool; @@ -871,7 +1019,6 @@ impl HydraulicCircuit { } else { None }, - pump_sections_check_valves: pump_to_system_check_valves, pump_section_routed_to_auxiliary_section: pump_section_to_auxiliary, fluid: Fluid::new(Pressure::new::(Self::FLUID_BULK_MODULUS_PASCAL)), @@ -899,14 +1046,39 @@ impl HydraulicCircuit { pub fn update( &mut self, context: &UpdateContext, - main_section_pumps: &mut [&mut dyn PressureSource], - system_section_pump: Option<&mut impl PressureSource>, - auxiliary_section_pump: Option<&mut impl PressureSource>, + main_section_pumps: &mut [&mut dyn HeatingPressureSource], + system_section_pump: Option<&mut impl HeatingPressureSource>, + auxiliary_section_pump: Option<&mut impl HeatingPressureSource>, ptu: Option<&PowerTransferUnit>, controller: &impl HydraulicCircuitController, reservoir_pressure: Pressure, ) { - self.reservoir.update(context, reservoir_pressure); + let mut any_pump_is_overheating = false; + for pump in main_section_pumps.iter() { + if pump.flow().get::() > 0.01 && pump.is_overheating() { + any_pump_is_overheating = true; + } + } + + if let Some(pump) = system_section_pump.as_ref() { + if pump.flow().get::() > 0.01 && pump.is_overheating() { + any_pump_is_overheating = true; + } + } + + if let Some(pump) = auxiliary_section_pump.as_ref() { + if pump.flow().get::() > 0.01 && pump.is_overheating() { + any_pump_is_overheating = true; + } + } + + let ptu_overheats_fluid = ptu.map_or(false, |p| p.is_overheating() && p.is_rotating()); + + self.fluid + .update(context, ptu_overheats_fluid || any_pump_is_overheating); + + self.reservoir + .update(context, reservoir_pressure, &self.fluid); self.update_shutoff_valves(controller); self.update_leak_measurement_valves(context, controller); @@ -974,9 +1146,9 @@ impl HydraulicCircuit { fn update_pumps( &mut self, context: &UpdateContext, - main_section_pumps: &mut [&mut dyn PressureSource], - system_section_pump: Option<&mut impl PressureSource>, - auxiliary_section_pump: Option<&mut impl PressureSource>, + main_section_pumps: &mut [&mut dyn HeatingPressureSource], + system_section_pump: Option<&mut impl HeatingPressureSource>, + auxiliary_section_pump: Option<&mut impl HeatingPressureSource>, ) { for (pump_index, section) in self.pump_sections.iter_mut().enumerate() { section.update_pump_state(context, main_section_pumps[pump_index], &mut self.reservoir); @@ -1026,9 +1198,9 @@ impl HydraulicCircuit { fn update_maximum_pumping_capacities( &mut self, - main_section_pumps: &mut [&mut dyn PressureSource], - system_section_pump: &Option<&mut impl PressureSource>, - auxiliary_section_pump: &Option<&mut impl PressureSource>, + main_section_pumps: &mut [&mut dyn HeatingPressureSource], + system_section_pump: &Option<&mut impl HeatingPressureSource>, + auxiliary_section_pump: &Option<&mut impl HeatingPressureSource>, ) { for (pump_index, section) in self.pump_sections.iter_mut().enumerate() { section.update_maximum_pumping_capacity(main_section_pumps[pump_index]); @@ -1419,7 +1591,7 @@ impl Section { self.total_actuator_consumed_volume = Volume::new::(0.); } - pub fn update_maximum_pumping_capacity(&mut self, pump: &dyn PressureSource) { + pub fn update_maximum_pumping_capacity(&mut self, pump: &dyn HeatingPressureSource) { self.max_pumpable_volume = if self.fire_valve_is_open() { pump.delta_vol_max() } else { @@ -1440,7 +1612,7 @@ impl Section { pub fn update_pump_state( &mut self, context: &UpdateContext, - pump: &mut dyn PressureSource, + pump: &mut dyn HeatingPressureSource, reservoir: &mut Reservoir, ) { // Final volume target to reach target pressure is: @@ -1941,6 +2113,7 @@ pub struct Reservoir { level_id: VariableIdentifier, low_level_id: VariableIdentifier, low_air_press_id: VariableIdentifier, + overheating_id: VariableIdentifier, max_capacity: Volume, max_gaugeable: Volume, @@ -1956,6 +2129,11 @@ pub struct Reservoir { return_failure: Failure, fluid_physics: FluidPhysics, + + heat_state: HeatingProperties, + + total_return_flow: VolumeRate, + total_return_volume: Volume, } impl Reservoir { const MIN_USABLE_VOLUME_GAL: f64 = 0.2; @@ -1965,6 +2143,11 @@ impl Reservoir { // Part of the fluid lost instead of returning to reservoir const RETURN_FAILURE_LEAK_RATIO: f64 = 0.1; + const HEATING_TIME_CONSTANT_MEAN_S: f64 = 30.; + const HEATING_TIME_CONSTANT_STD_S: f64 = 5.; + const COOLING_TIME_CONSTANT: Duration = Duration::from_secs(60 * 3); + const DAMAGE_TIME_CONSTANT: Duration = Duration::from_secs(60 * 5); + pub fn new( context: &mut InitContext, hyd_loop_id: HydraulicColor, @@ -1980,6 +2163,7 @@ impl Reservoir { .get_identifier(format!("HYD_{}_RESERVOIR_LEVEL_IS_LOW", hyd_loop_id)), low_air_press_id: context .get_identifier(format!("HYD_{}_RESERVOIR_AIR_PRESSURE_IS_LOW", hyd_loop_id)), + overheating_id: context.get_identifier(format!("HYD_{}_RESERVOIR_OVHT", hyd_loop_id)), max_capacity, max_gaugeable, @@ -1991,12 +2175,34 @@ impl Reservoir { air_pressure_switches, level_switch: LevelSwitch::new(low_level_threshold), fluid_physics: FluidPhysics::new(), + + heat_state: HeatingProperties::new( + Duration::from_secs_f64( + random_from_normal_distribution( + Self::HEATING_TIME_CONSTANT_MEAN_S, + Self::HEATING_TIME_CONSTANT_STD_S, + ) + .max(10.), + ), + Self::COOLING_TIME_CONSTANT, + Self::DAMAGE_TIME_CONSTANT, + ), + total_return_flow: VolumeRate::default(), + total_return_volume: Volume::default(), } } - fn update(&mut self, context: &UpdateContext, air_pressure: Pressure) { + fn update( + &mut self, + context: &UpdateContext, + air_pressure: Pressure, + fluid: &impl HeatingElement, + ) { self.air_pressure = air_pressure; + self.update_return_flow(context); + self.update_heat(context, fluid); + self.fluid_physics.update(context); self.level_switch.update( @@ -2009,6 +2215,17 @@ impl Reservoir { self.update_leak_failure(context); } + fn update_return_flow(&mut self, context: &UpdateContext) { + self.total_return_flow = self.total_return_volume / context.delta_as_time(); + self.total_return_volume = Volume::default(); + } + + fn update_heat(&mut self, context: &UpdateContext, fluid: &impl HeatingElement) { + let has_fluid_return = self.total_return_flow.get::() > 0.01; + self.heat_state + .update(context, has_fluid_return && fluid.is_overheating()) + } + fn update_leak_failure(&mut self, context: &UpdateContext) { if self.leak_failure.is_active() { self.current_level -= @@ -2058,6 +2275,8 @@ impl Reservoir { }; self.current_level = (self.current_level + volume_actually_returned).min(self.max_capacity); + + self.total_return_volume += volume_actually_returned; } fn fluid_level_real(&self) -> Volume { @@ -2104,6 +2323,7 @@ impl SimulationElement for Reservoir { writer.write(&self.level_id, self.fluid_level_from_gauge()); writer.write(&self.low_level_id, self.is_low_level()); writer.write(&self.low_air_press_id, self.is_low_air_pressure()); + writer.write(&self.overheating_id, self.is_overheating()); } } impl PressurizeableReservoir for Reservoir { @@ -2111,6 +2331,15 @@ impl PressurizeableReservoir for Reservoir { self.max_capacity - self.fluid_level_real() } } +impl HeatingElement for Reservoir { + fn is_damaged(&self) -> bool { + self.heat_state.is_damaged() + } + + fn is_overheating(&self) -> bool { + self.heat_state.is_overheating() + } +} pub trait PumpController { fn should_pressurise(&self) -> bool; @@ -2180,8 +2409,10 @@ impl Pump { fn update_cavitation(&mut self, reservoir: &Reservoir) { self.cavitation_efficiency = if !reservoir.is_empty() { - self.pump_characteristics - .cavitation_efficiency(reservoir.air_pressure()) + self.pump_characteristics.cavitation_efficiency( + reservoir.air_pressure(), + reservoir.heat_state.overheat_ratio(), + ) } else { Ratio::new::(0.) }; @@ -2279,13 +2510,14 @@ impl PressureSource for Pump { pub struct ElectricPump { cavitation_id: VariableIdentifier, + overheat_id: VariableIdentifier, pump: Pump, pump_physics: ElectricalPumpPhysics, } impl ElectricPump { pub fn new( context: &mut InitContext, - id: &str, + id: AirbusElectricPumpId, bus_type: ElectricalBusType, max_current: ElectricCurrent, pump_characteristics: PumpCharacteristics, @@ -2293,6 +2525,7 @@ impl ElectricPump { let regulated_speed = pump_characteristics.regulated_speed(); Self { cavitation_id: context.get_identifier(format!("HYD_{}_EPUMP_CAVITATION", id)), + overheat_id: context.get_identifier(format!("HYD_{}_EPUMP_OVHT", id)), pump: Pump::new(pump_characteristics), pump_physics: ElectricalPumpPhysics::new( context, @@ -2376,8 +2609,19 @@ impl SimulationElement for ElectricPump { &self.cavitation_id, self.cavitation_efficiency().get::(), ); + writer.write(&self.overheat_id, self.is_overheating()); } } +impl HeatingElement for ElectricPump { + fn is_damaged(&self) -> bool { + self.pump_physics.is_damaged() + } + + fn is_overheating(&self) -> bool { + self.pump_physics.is_overheating() + } +} +impl HeatingPressureSource for ElectricPump {} pub struct EngineDrivenPump { active_id: VariableIdentifier, @@ -2385,11 +2629,22 @@ pub struct EngineDrivenPump { is_active: bool, speed: AngularVelocity, pump: Pump, + + overheat_failure: Failure, + heat_state: HeatingProperties, } impl EngineDrivenPump { + const HEATING_TIME_CONSTANT_MEAN_S: f64 = 30.; + const HEATING_TIME_CONSTANT_STD_S: f64 = 5.; + + const COOLING_TIME_CONSTANT: Duration = Duration::from_secs(60 * 2); + const DAMAGE_TIME_CONSTANT: Duration = Duration::from_secs(60 * 2); + + const MIN_SPEED_TO_REPORT_HEATING_RPM: f64 = 200.; + pub fn new( context: &mut InitContext, - id: &str, + id: AirbusEngineDrivenPumpId, pump_characteristics: PumpCharacteristics, ) -> Self { Self { @@ -2397,6 +2652,18 @@ impl EngineDrivenPump { is_active: false, speed: AngularVelocity::new::(0.), pump: Pump::new(pump_characteristics), + overheat_failure: Failure::new(FailureType::EnginePumpOverheat(id)), + heat_state: HeatingProperties::new( + Duration::from_secs_f64( + random_from_normal_distribution( + Self::HEATING_TIME_CONSTANT_MEAN_S, + Self::HEATING_TIME_CONSTANT_STD_S, + ) + .max(10.), + ), + Self::COOLING_TIME_CONSTANT, + Self::DAMAGE_TIME_CONSTANT, + ), } } @@ -2408,9 +2675,22 @@ impl EngineDrivenPump { pump_speed: AngularVelocity, controller: &impl PumpController, ) { - self.speed = pump_speed; + self.heat_state.update( + context, + self.overheat_failure.is_active() + && pump_speed.get::() + > Self::MIN_SPEED_TO_REPORT_HEATING_RPM, + ); + + self.speed = if !self.is_damaged() { + pump_speed + } else { + AngularVelocity::default() + }; + self.pump - .update(context, section, reservoir, pump_speed, controller); + .update(context, section, reservoir, self.speed, controller); + self.is_active = controller.should_pressurise(); } } @@ -2443,10 +2723,25 @@ impl PressureSource for EngineDrivenPump { } } impl SimulationElement for EngineDrivenPump { + fn accept(&mut self, visitor: &mut T) { + self.overheat_failure.accept(visitor); + visitor.visit(self); + } + fn write(&self, writer: &mut SimulatorWriter) { writer.write(&self.active_id, self.is_active); } } +impl HeatingElement for EngineDrivenPump { + fn is_damaged(&self) -> bool { + self.heat_state.is_damaged() + } + + fn is_overheating(&self) -> bool { + self.heat_state.is_overheating() + } +} +impl HeatingPressureSource for EngineDrivenPump {} struct WindTurbine { rpm_id: VariableIdentifier, @@ -2682,6 +2977,8 @@ impl SimulationElement for RamAirTurbine { writer.write(&self.stow_position_id, self.position); } } +impl HeatingElement for RamAirTurbine {} +impl HeatingPressureSource for RamAirTurbine {} #[cfg(test)] mod tests { @@ -2695,6 +2992,24 @@ mod tests { use super::*; + struct TestFluid { + is_hot: bool, + } + impl TestFluid { + fn overheat() -> Self { + Self { is_hot: true } + } + + fn nominal() -> Self { + Self { is_hot: false } + } + } + impl HeatingElement for TestFluid { + fn is_overheating(&self) -> bool { + self.is_hot + } + } + #[test] fn section_writes_its_state() { let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { @@ -2820,7 +3135,7 @@ mod tests { })); test_bed.set_update_after_power_distribution(|reservoir, context| { - reservoir.update(context, Pressure::new::(50.)) + reservoir.update(context, Pressure::new::(50.), &TestFluid::nominal()) }); test_bed.fail(FailureType::ReservoirLeak(HydraulicColor::Green)); @@ -2843,7 +3158,7 @@ mod tests { })); test_bed.set_update_after_power_distribution(|reservoir, context| { - reservoir.update(context, Pressure::new::(50.)) + reservoir.update(context, Pressure::new::(50.), &TestFluid::nominal()) }); test_bed.fail(FailureType::ReservoirLeak(HydraulicColor::Green)); @@ -2866,7 +3181,7 @@ mod tests { })); test_bed.set_update_after_power_distribution(|reservoir, context| { - reservoir.update(context, Pressure::new::(50.)) + reservoir.update(context, Pressure::new::(50.), &TestFluid::nominal()) }); let is_low: bool = test_bed.read_by_name("HYD_GREEN_RESERVOIR_LEVEL_IS_LOW"); @@ -2892,7 +3207,7 @@ mod tests { })); test_bed.set_update_after_power_distribution(|reservoir, context| { - reservoir.update(context, Pressure::new::(50.)) + reservoir.update(context, Pressure::new::(50.), &TestFluid::nominal()) }); test_bed.run_multiple_frames(Duration::from_secs(2)); @@ -2919,7 +3234,7 @@ mod tests { ) })) .with_update_after_power_distribution(|el, context| { - el.update(context, Pressure::new::(50.)) + el.update(context, Pressure::new::(50.), &TestFluid::nominal()) }); test_bed.write_by_name("PLANE BANK DEGREES", 180.); @@ -2962,6 +3277,58 @@ mod tests { ); } + #[test] + fn reservoir_receiving_heating_fluid_overheats() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + reservoir( + context, + HydraulicColor::Green, + Volume::new::(5.), + Volume::new::(2.), + Volume::new::(0.5), + ) + })); + + test_bed.set_update_after_power_distribution(|reservoir, context| { + reservoir.update(context, Pressure::new::(50.), &TestFluid::overheat()); + + reservoir.try_take_volume(Volume::new::(0.10)); + + reservoir.add_return_volume(Volume::new::(0.10)); + }); + + test_bed.run_multiple_frames(Duration::from_secs_f64( + Reservoir::HEATING_TIME_CONSTANT_MEAN_S + 4. * Reservoir::HEATING_TIME_CONSTANT_STD_S, + )); + + let is_overheating: bool = test_bed.read_by_name("HYD_GREEN_RESERVOIR_OVHT"); + assert!(is_overheating); + } + + #[test] + fn reservoir_receiving_zero_flow_of_heating_fluid_do_not_overheat() { + let mut test_bed = SimulationTestBed::from(ElementCtorFn(|context| { + reservoir( + context, + HydraulicColor::Green, + Volume::new::(5.), + Volume::new::(2.), + Volume::new::(0.5), + ) + })); + + test_bed.set_update_after_power_distribution(|reservoir, context| { + reservoir.update(context, Pressure::new::(50.), &TestFluid::overheat()); + }); + + test_bed.run_multiple_frames(Duration::from_secs_f64( + Reservoir::HEATING_TIME_CONSTANT_MEAN_S + 4. * Reservoir::HEATING_TIME_CONSTANT_STD_S, + )); + + let is_overheating: bool = test_bed.read_by_name("HYD_GREEN_RESERVOIR_OVHT"); + assert!(!is_overheating); + } + fn section( context: &mut InitContext, loop_id: HydraulicColor, @@ -3051,7 +3418,11 @@ mod tests { } fn engine_driven_pump(context: &mut InitContext) -> EngineDrivenPump { - EngineDrivenPump::new(context, "DEFAULT", PumpCharacteristics::a320_edp()) + EngineDrivenPump::new( + context, + AirbusEngineDrivenPumpId::Green, + PumpCharacteristics::a320_edp(), + ) } #[cfg(test)] diff --git a/src/systems/systems/src/hydraulic/pumps.rs b/src/systems/systems/src/hydraulic/pumps.rs index ffb189ab3c1c..5095aa3f5bbf 100644 --- a/src/systems/systems/src/hydraulic/pumps.rs +++ b/src/systems/systems/src/hydraulic/pumps.rs @@ -137,12 +137,15 @@ impl PumpCharacteristics { )) } - pub fn cavitation_efficiency(&self, air_pressure: Pressure) -> Ratio { - Ratio::new::(interpolation( - &self.air_pressure_map_breakpoints_psi, - &self.cavitation_map_ratio, - air_pressure.get::(), - )) + pub fn cavitation_efficiency(&self, air_pressure: Pressure, heat_factor: Ratio) -> Ratio { + Ratio::new::( + (1. - heat_factor.get::()) + * interpolation( + &self.air_pressure_map_breakpoints_psi, + &self.cavitation_map_ratio, + air_pressure.get::(), + ), + ) } pub fn regulated_speed(&self) -> AngularVelocity { diff --git a/src/systems/systems/src/shared/mod.rs b/src/systems/systems/src/shared/mod.rs index fff8a27688f2..e525309e7f47 100644 --- a/src/systems/systems/src/shared/mod.rs +++ b/src/systems/systems/src/shared/mod.rs @@ -251,6 +251,60 @@ impl Display for HydraulicColor { } } +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AirbusEngineDrivenPumpId { + Edp1a, + Edp1b, + Edp2a, + Edp2b, + Edp3a, + Edp3b, + Edp4a, + Edp4b, + Green, + Yellow, +} +impl Display for AirbusEngineDrivenPumpId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AirbusEngineDrivenPumpId::Edp1a => write!(f, "GREEN_1A"), + AirbusEngineDrivenPumpId::Edp1b => write!(f, "GREEN_1B"), + AirbusEngineDrivenPumpId::Edp2a => write!(f, "GREEN_2A"), + AirbusEngineDrivenPumpId::Edp2b => write!(f, "GREEN_2B"), + AirbusEngineDrivenPumpId::Edp3a => write!(f, "YELLOW_3A"), + AirbusEngineDrivenPumpId::Edp3b => write!(f, "YELLOW_3B"), + AirbusEngineDrivenPumpId::Edp4a => write!(f, "YELLOW_4A"), + AirbusEngineDrivenPumpId::Edp4b => write!(f, "YELLOW_4B"), + AirbusEngineDrivenPumpId::Green => write!(f, "GREEN"), + AirbusEngineDrivenPumpId::Yellow => write!(f, "YELLOW"), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AirbusElectricPumpId { + GreenA, + GreenB, + YellowA, + YellowB, + Green, + Blue, + Yellow, +} +impl Display for AirbusElectricPumpId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AirbusElectricPumpId::GreenA => write!(f, "GA"), + AirbusElectricPumpId::YellowA => write!(f, "YA"), + AirbusElectricPumpId::GreenB => write!(f, "GB"), + AirbusElectricPumpId::YellowB => write!(f, "YB"), + AirbusElectricPumpId::Green => write!(f, "GREEN"), + AirbusElectricPumpId::Blue => write!(f, "BLUE"), + AirbusElectricPumpId::Yellow => write!(f, "YELLOW"), + } + } +} + /// The common types of electrical buses within Airbus aircraft. /// These include types such as AC, DC, AC ESS, etc. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]