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)]