From 85a5b7744218052963a31b37ae192f5bc7fb97fb Mon Sep 17 00:00:00 2001
From: davydecorps <38904654+davydecorps@users.noreply.github.com>
Date: Wed, 3 Mar 2021 08:58:39 +0100
Subject: [PATCH 001/122] merging first PR in clean master
---
.../AirPlanes/Asobo_A320_NEO/approach.FLT | 22 +-
.../AirPlanes/Asobo_A320_NEO/apron.FLT | 22 +-
.../AirPlanes/Asobo_A320_NEO/cruise.FLT | 22 +-
.../AirPlanes/Asobo_A320_NEO/final.FLT | 22 +-
.../model/A320_NEO_INTERIOR.xml | 42 +-
.../AirPlanes/Asobo_A320_NEO/runway.FLT | 22 +-
.../AirPlanes/Asobo_A320_NEO/taxi.flt | 2 +-
.../Airliners/A320_Neo/BRK/A320_Neo_BRK.js | 16 +-
Cargo.lock | 115 +
src/systems/a320_systems/src/hydraulic.rs | 869 ++++++-
src/systems/a320_systems/src/lib.rs | 13 +-
src/systems/a320_systems_wasm/src/lib.rs | 50 +
src/systems/systems/Cargo.toml | 2 +
.../systems/src/hydraulic/brakecircuit.rs | 519 ++++
src/systems/systems/src/hydraulic/mod.rs | 2164 +++++++++++++++++
.../src/hydraulic/study/test_Hy_CompConcept.m | 486 ++++
.../hydraulic/study/test_Hy_CompConcept_V2.m | 531 ++++
.../systems/src/simulation/update_context.rs | 30 +-
18 files changed, 4854 insertions(+), 95 deletions(-)
create mode 100644 src/systems/systems/src/hydraulic/brakecircuit.rs
create mode 100644 src/systems/systems/src/hydraulic/study/test_Hy_CompConcept.m
create mode 100644 src/systems/systems/src/hydraulic/study/test_Hy_CompConcept_V2.m
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/approach.FLT b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/approach.FLT
index b49339bfe90..bef1195cfbd 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/approach.FLT
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/approach.FLT
@@ -163,18 +163,18 @@ A32NX_AIRCOND_RAMAIR_TOGGLE=1
A32NX_CALLS_EMERLOCK_TOGGLE=1
A32NX_CALLS_EMER_ON=0
A32NX_OVHD_COCKPITDOORVIDEO_TOGGLE=1
-A32NX_HYD_ENG1PUMP_FAULT=0
-A32NX_HYD_ENG1PUMP_TOGGLE=1
-A32NX_HYD_ENG2PUMP_FAULT=0
-A32NX_HYD_ENG2PUMP_TOGGLE=1
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO=1
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO=1
A32NX_HYD_RATMANONLOCK_TOGGLE=0
-A32NX_HYD_ELECPUMP_FAULT=0
-A32NX_HYD_ELECPUMP_TOGGLE=1
+A32NX_OVHD_HYD_EPUMPB_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO=1
A32NX_HYD_ELECPUMPLOCK_TOGGLE=0
-A32NX_HYD_PTU_FAULT=0
-A32NX_HYD_PTU_TOGGLE=1
-A32NX_HYD_ELECPUMPY_FAULT=0
-A32NX_HYD_ELECPUMPY_TOGGLE=1
+A32NX_OVHD_HYD_PTU_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_PTU_PB_IS_AUTO=1
+A32NX_OVHD_HYD_EPUMPY_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO=1
A32NX_ENGMANSTART1LOCK_TOGGLE=0
A32NX_ENGMANSTART2LOCK_TOGGLE=0
A32NX_ENGMANSTART1_TOGGLE=0
@@ -201,7 +201,7 @@ A32NX_KNOB_SWITCHING_3_Position=1
A32NX_KNOB_SWITCHING_4_Position=1
A32NX_PANEL_DCDU_L_BRIGHTNESS=0.5
A32NX_PANEL_DCDU_R_BRIGHTNESS=0.5
-A32NX_OVHD_HYD_BLUEPUMP_OVRD=0
+A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON=0
A32NX_OVHD_HYD_LEAK_MEASUREMENT_G=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_B=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_Y=1
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/apron.FLT b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/apron.FLT
index 5fb2596de8f..e57f0acc3d4 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/apron.FLT
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/apron.FLT
@@ -169,18 +169,18 @@ A32NX_AIRCOND_RAMAIR_TOGGLE=1
A32NX_CALLS_EMERLOCK_TOGGLE=1
A32NX_CALLS_EMER_ON=0
A32NX_OVHD_COCKPITDOORVIDEO_TOGGLE=1
-A32NX_HYD_ENG1PUMP_FAULT=0
-A32NX_HYD_ENG1PUMP_TOGGLE=1
-A32NX_HYD_ENG2PUMP_FAULT=0
-A32NX_HYD_ENG2PUMP_TOGGLE=1
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO=1
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO=1
A32NX_HYD_RATMANONLOCK_TOGGLE=0
-A32NX_HYD_ELECPUMP_FAULT=0
-A32NX_HYD_ELECPUMP_TOGGLE=1
+A32NX_OVHD_HYD_EPUMPB_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO=1
A32NX_HYD_ELECPUMPLOCK_TOGGLE=0
-A32NX_HYD_PTU_FAULT=0
-A32NX_HYD_PTU_TOGGLE=1
-A32NX_HYD_ELECPUMPY_FAULT=0
-A32NX_HYD_ELECPUMPY_TOGGLE=1
+A32NX_OVHD_HYD_PTU_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_PTU_PB_IS_AUTO=1
+A32NX_OVHD_HYD_EPUMPY_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO=1
A32NX_ENGMANSTART1LOCK_TOGGLE=0
A32NX_ENGMANSTART2LOCK_TOGGLE=0
A32NX_ENGMANSTART1_TOGGLE=0
@@ -207,7 +207,7 @@ A32NX_KNOB_SWITCHING_3_Position=1
A32NX_KNOB_SWITCHING_4_Position=1
A32NX_PANEL_DCDU_L_BRIGHTNESS=0.5
A32NX_PANEL_DCDU_R_BRIGHTNESS=0.5
-A32NX_OVHD_HYD_BLUEPUMP_OVRD=0
+A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON=0
A32NX_OVHD_HYD_LEAK_MEASUREMENT_G=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_B=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_Y=1
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/cruise.FLT b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/cruise.FLT
index c8ecc716162..39d589f9b35 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/cruise.FLT
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/cruise.FLT
@@ -163,18 +163,18 @@ A32NX_AIRCOND_RAMAIR_TOGGLE=1
A32NX_CALLS_EMERLOCK_TOGGLE=1
A32NX_CALLS_EMER_ON=0
A32NX_OVHD_COCKPITDOORVIDEO_TOGGLE=1
-A32NX_HYD_ENG1PUMP_FAULT=0
-A32NX_HYD_ENG1PUMP_TOGGLE=1
-A32NX_HYD_ENG2PUMP_FAULT=0
-A32NX_HYD_ENG2PUMP_TOGGLE=1
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO=1
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO=1
A32NX_HYD_RATMANONLOCK_TOGGLE=0
-A32NX_HYD_ELECPUMP_FAULT=0
-A32NX_HYD_ELECPUMP_TOGGLE=1
+A32NX_OVHD_HYD_EPUMPB_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO=1
A32NX_HYD_ELECPUMPLOCK_TOGGLE=0
-A32NX_HYD_PTU_FAULT=0
-A32NX_HYD_PTU_TOGGLE=1
-A32NX_HYD_ELECPUMPY_FAULT=0
-A32NX_HYD_ELECPUMPY_TOGGLE=1
+A32NX_OVHD_HYD_PTU_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_PTU_PB_IS_AUTO=1
+A32NX_OVHD_HYD_EPUMPY_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO=1
A32NX_ENGMANSTART1LOCK_TOGGLE=0
A32NX_ENGMANSTART2LOCK_TOGGLE=0
A32NX_ENGMANSTART1_TOGGLE=0
@@ -201,7 +201,7 @@ A32NX_KNOB_SWITCHING_3_Position=1
A32NX_KNOB_SWITCHING_4_Position=1
A32NX_PANEL_DCDU_L_BRIGHTNESS=0.5
A32NX_PANEL_DCDU_R_BRIGHTNESS=0.5
-A32NX_OVHD_HYD_BLUEPUMP_OVRD=0
+A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON=0
A32NX_OVHD_HYD_LEAK_MEASUREMENT_G=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_B=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_Y=1
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/final.FLT b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/final.FLT
index 6e52d2150e2..1ec8c84ffae 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/final.FLT
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/final.FLT
@@ -163,18 +163,18 @@ A32NX_AIRCOND_RAMAIR_TOGGLE=1
A32NX_CALLS_EMERLOCK_TOGGLE=1
A32NX_CALLS_EMER_ON=0
A32NX_OVHD_COCKPITDOORVIDEO_TOGGLE=1
-A32NX_HYD_ENG1PUMP_FAULT=0
-A32NX_HYD_ENG1PUMP_TOGGLE=1
-A32NX_HYD_ENG2PUMP_FAULT=0
-A32NX_HYD_ENG2PUMP_TOGGLE=1
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO=1
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO=1
A32NX_HYD_RATMANONLOCK_TOGGLE=0
-A32NX_HYD_ELECPUMP_FAULT=0
-A32NX_HYD_ELECPUMP_TOGGLE=1
+A32NX_OVHD_HYD_EPUMPB_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO=1
A32NX_HYD_ELECPUMPLOCK_TOGGLE=0
-A32NX_HYD_PTU_FAULT=0
-A32NX_HYD_PTU_TOGGLE=1
-A32NX_HYD_ELECPUMPY_FAULT=0
-A32NX_HYD_ELECPUMPY_TOGGLE=1
+A32NX_OVHD_HYD_PTU_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_PTU_PB_IS_AUTO=1
+A32NX_OVHD_HYD_EPUMPY_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO=1
A32NX_ENGMANSTART1LOCK_TOGGLE=0
A32NX_ENGMANSTART2LOCK_TOGGLE=0
A32NX_ENGMANSTART1_TOGGLE=0
@@ -201,7 +201,7 @@ A32NX_KNOB_SWITCHING_3_Position=1
A32NX_KNOB_SWITCHING_4_Position=1
A32NX_PANEL_DCDU_L_BRIGHTNESS=0.5
A32NX_PANEL_DCDU_R_BRIGHTNESS=0.5
-A32NX_OVHD_HYD_BLUEPUMP_OVRD=0
+A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON=0
A32NX_OVHD_HYD_LEAK_MEASUREMENT_G=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_B=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_Y=1
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/model/A320_NEO_INTERIOR.xml b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/model/A320_NEO_INTERIOR.xml
index 89d56cd79a0..32596bd0f7c 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/model/A320_NEO_INTERIOR.xml
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/model/A320_NEO_INTERIOR.xml
@@ -1937,24 +1937,24 @@
PUSH_OVHD_HYD_ENG1PUMP
- L:A32NX_HYD_ENG1PUMP_TOGGLE
+ L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO
- (L:A32NX_HYD_ENG1PUMP_FAULT, Bool)
- (L:A32NX_HYD_ENG1PUMP_TOGGLE, Bool) !
+ (L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT, Bool)
+ (L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO, Bool) !
- %((L:A32NX_HYD_ENG1PUMP_TOGGLE, Bool))%{if}Turn OFF eng 1 hyd pump%{else}Turn ON eng 1 hyd pump%{end}
+ %((L:A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO, Bool))%{if}Turn OFF eng 1 hyd pump%{else}Turn ON eng 1 hyd pump%{end}
PUSH_OVHD_HYD_ENG2PUMP
- L:A32NX_HYD_ENG2PUMP_TOGGLE
+ L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO
- (L:A32NX_HYD_ENG2PUMP_FAULT, Bool)
- (L:A32NX_HYD_ENG2PUMP_TOGGLE, Bool) !
+ (L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT, Bool)
+ (L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO, Bool) !
- %((L:A32NX_HYD_ENG2PUMP_TOGGLE, Bool))%{if}Turn OFF eng 2 hyd pump%{else}Turn ON eng 2 hyd pump%{end}
+ %((L:A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO, Bool))%{if}Turn OFF eng 2 hyd pump%{else}Turn ON eng 2 hyd pump%{end}
@@ -1970,36 +1970,36 @@
PUSH_OVHD_HYD_ELECPUMP
LOCK_OVHD_HYD_ELECPUMP
- L:A32NX_HYD_ELECPUMP_TOGGLE
+ L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO
- (L:A32NX_HYD_ELECPUMP_FAULT, Bool)
- (L:A32NX_HYD_ELECPUMP_TOGGLE, Bool) !
+ (L:A32NX_OVHD_HYD_EPUMPB_PB_HAS_FAULT, Bool)
+ (L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO, Bool) !
- %((L:A32NX_HYD_ELECPUMP_TOGGLE, Bool))%{if}Turn OFF elec hyd pump%{else}Turn ON elec hyd pump%{end}
+ %((L:A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO, Bool))%{if}Turn OFF elec hyd pump%{else}Turn ON elec hyd pump%{end}
PUSH_OVHD_HYD_PTU
- L:A32NX_HYD_PTU_TOGGLE
+ L:A32NX_OVHD_HYD_PTU_PB_IS_AUTO
- (L:A32NX_HYD_PTU_FAULT, Bool)
- (L:A32NX_HYD_PTU_TOGGLE, Bool) !
+ (L:A32NX_OVHD_HYD_PTU_PB_HAS_FAULT, Bool)
+ (L:A32NX_OVHD_HYD_PTU_PB_IS_AUTO, Bool) !
- %((L:A32NX_HYD_PTU_TOGGLE, Bool))%{if}Turn OFF PTU%{else}Turn ON PTU%{end}
+ %((L:A32NX_OVHD_HYD_PTU_PB_IS_AUTO, Bool))%{if}Turn OFF PTU%{else}Turn ON PTU%{end}
PUSH_OVHD_HYD_ELECPUMPY
- L:A32NX_HYD_ELECPUMPY_TOGGLE
+ L:A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO
- (L:A32NX_HYD_ELECPUMPY_FAULT, Bool)
- (L:A32NX_HYD_ELECPUMPY_TOGGLE, Bool) !
+ (L:A32NX_OVHD_HYD_EPUMPY_PB_HAS_FAULT, Bool)
+ (L:A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO, Bool) !
- %((L:A32NX_HYD_ELECPUMPY_TOGGLE, Bool))%{if}Turn ON yellow elec hyd pump%{else}Turn OFF yellow elec hyd pump%{end}
+ %((L:A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO, Bool))%{if}Turn ON yellow elec hyd pump%{else}Turn OFF yellow elec hyd pump%{end}
@@ -2603,7 +2603,7 @@
PUSH_OVHD_HYD_BLUEPUMP
LOCK_OVHD_HYD_BLUEPUMPOVRD
- L:A32NX_OVHD_HYD_BLUEPUMP_OVRD
+ L:A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/runway.FLT b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/runway.FLT
index 671a6262044..d54e19caf07 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/runway.FLT
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/runway.FLT
@@ -170,18 +170,18 @@ A32NX_AIRCOND_RAMAIR_TOGGLE=1
A32NX_CALLS_EMERLOCK_TOGGLE=1
A32NX_CALLS_EMER_ON=0
A32NX_OVHD_COCKPITDOORVIDEO_TOGGLE=1
-A32NX_HYD_ENG1PUMP_FAULT=0
-A32NX_HYD_ENG1PUMP_TOGGLE=1
-A32NX_HYD_ENG2PUMP_FAULT=0
-A32NX_HYD_ENG2PUMP_TOGGLE=1
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_1_PUMP_PB_IS_AUTO=1
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_ENG_2_PUMP_PB_IS_AUTO=1
A32NX_HYD_RATMANONLOCK_TOGGLE=0
-A32NX_HYD_ELECPUMP_FAULT=0
-A32NX_HYD_ELECPUMP_TOGGLE=1
+A32NX_OVHD_HYD_EPUMPB_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPB_PB_IS_AUTO=1
A32NX_HYD_ELECPUMPLOCK_TOGGLE=0
-A32NX_HYD_PTU_FAULT=0
-A32NX_HYD_PTU_TOGGLE=1
-A32NX_HYD_ELECPUMPY_FAULT=0
-A32NX_HYD_ELECPUMPY_TOGGLE=1
+A32NX_OVHD_HYD_PTU_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_PTU_PB_IS_AUTO=1
+A32NX_OVHD_HYD_EPUMPY_PB_HAS_FAULT=0
+A32NX_OVHD_HYD_EPUMPY_PB_IS_AUTO=1
A32NX_ENGMANSTART1LOCK_TOGGLE=0
A32NX_ENGMANSTART2LOCK_TOGGLE=0
A32NX_ENGMANSTART1_TOGGLE=0
@@ -208,7 +208,7 @@ A32NX_KNOB_SWITCHING_3_Position=1
A32NX_KNOB_SWITCHING_4_Position=1
A32NX_PANEL_DCDU_L_BRIGHTNESS=0.5
A32NX_PANEL_DCDU_R_BRIGHTNESS=0.5
-A32NX_OVHD_HYD_BLUEPUMP_OVRD=0
+A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON=0
A32NX_OVHD_HYD_LEAK_MEASUREMENT_G=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_B=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_Y=1
diff --git a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/taxi.flt b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/taxi.flt
index f8c9be17e59..db6ceec4f71 100644
--- a/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/taxi.flt
+++ b/A32NX/SimObjects/AirPlanes/Asobo_A320_NEO/taxi.flt
@@ -182,7 +182,7 @@ A32NX_KNOB_SWITCHING_3_Position=1
A32NX_KNOB_SWITCHING_4_Position=1
A32NX_PANEL_DCDU_L_BRIGHTNESS=0.5
A32NX_PANEL_DCDU_R_BRIGHTNESS=0.5
-A32NX_OVHD_HYD_BLUEPUMP_OVRD=0
+A32NX_OVHD_HYD_EPUMPY_OVRD_PB_IS_ON=0
A32NX_OVHD_HYD_LEAK_MEASUREMENT_G=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_B=1
A32NX_OVHD_HYD_LEAK_MEASUREMENT_Y=1
diff --git a/A32NX/html_ui/Pages/VCockpit/Instruments/Airliners/A320_Neo/BRK/A320_Neo_BRK.js b/A32NX/html_ui/Pages/VCockpit/Instruments/Airliners/A320_Neo/BRK/A320_Neo_BRK.js
index 7d1229a00ff..f9546b27c9c 100644
--- a/A32NX/html_ui/Pages/VCockpit/Instruments/Airliners/A320_Neo/BRK/A320_Neo_BRK.js
+++ b/A32NX/html_ui/Pages/VCockpit/Instruments/Airliners/A320_Neo/BRK/A320_Neo_BRK.js
@@ -74,11 +74,11 @@ var A320_Neo_BRK;
if (this.updateThrottler.canUpdate(_deltaTime) === -1) {
return;
}
- const currentPKGBrakeState = SimVar.GetSimVarValue("BRAKE PARKING POSITION", "Bool");
+
const powerAvailable = SimVar.GetSimVarValue("L:DCPowerAvailable","Bool");
if (this.topGauge != null) {
if (powerAvailable) {
- this.topGauge.setValue(3);
+ this.topGauge.setValue(3 * (SimVar.GetSimVarValue("L:A32NX_HYD_BRAKE_ALTN_ACC_PRESS","PSI") / 3000));
} else {
this.topGauge.setValue(0);
}
@@ -86,22 +86,14 @@ var A320_Neo_BRK;
if (this.leftGauge != null) {
if (powerAvailable) {
- if (currentPKGBrakeState != 0) {
- this.leftGauge.setValue(2);
- } else {
- this.leftGauge.setValue(2 * (SimVar.GetSimVarValue("BRAKE LEFT POSITION","SINT32") / 32000));
- }
+ this.leftGauge.setValue(3 * (SimVar.GetSimVarValue("L:A32NX_HYD_BRAKE_ALTN_LEFT_PRESS","PSI") / 3000));
} else {
this.leftGauge.setValue(0);
}
}
if (this.rightGauge != null) {
if (powerAvailable) {
- if (currentPKGBrakeState != 0) {
- this.rightGauge.setValue(2);
- } else {
- this.rightGauge.setValue(2 * (SimVar.GetSimVarValue("BRAKE RIGHT POSITION","SINT32") / 32000));
- }
+ this.rightGauge.setValue(3 * (SimVar.GetSimVarValue("L:A32NX_HYD_BRAKE_ALTN_RIGHT_PRESS","PSI") / 3000));
} else {
this.rightGauge.setValue(0);
}
diff --git a/Cargo.lock b/Cargo.lock
index a81a8d4762d..8005684a5c7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -18,6 +18,21 @@ dependencies = [
"uom",
]
+[[package]]
+name = "addr2line"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
[[package]]
name = "aho-corasick"
version = "0.7.15"
@@ -53,6 +68,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+[[package]]
+name = "backtrace"
+version = "0.3.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
+dependencies = [
+ "addr2line",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
[[package]]
name = "bindgen"
version = "0.55.1"
@@ -149,6 +178,28 @@ dependencies = [
"termcolor",
]
+[[package]]
+name = "failure"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
+dependencies = [
+ "backtrace",
+ "failure_derive",
+]
+
+[[package]]
+name = "failure_derive"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
[[package]]
name = "futures"
version = "0.3.12"
@@ -255,6 +306,12 @@ dependencies = [
"wasi",
]
+[[package]]
+name = "gimli"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+
[[package]]
name = "glob"
version = "0.3.0"
@@ -322,6 +379,16 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
[[package]]
name = "msfs"
version = "0.0.1-alpha.2"
@@ -410,6 +477,12 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "object"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
+
[[package]]
name = "once_cell"
version = "1.5.2"
@@ -434,6 +507,16 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "plotlib"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9462104f987d8d0f6625f0c7764f1c8b890bd1dc8584d8293e031f25c5a0d242"
+dependencies = [
+ "failure",
+ "svg",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.10"
@@ -543,12 +626,24 @@ version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+[[package]]
+name = "rustc-demangle"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
+
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+[[package]]
+name = "rustplotlib"
+version = "0.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4326f7ac67e4ff419282ad12dabf1fcad09481a849b72108c890e01414ebb88a"
+
[[package]]
name = "serde"
version = "1.0.123"
@@ -573,6 +668,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+[[package]]
+name = "svg"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3685c82a045a6af0c488f0550b0f52b4c77d2a52b0ca8aba719f9d268fa96965"
+
[[package]]
name = "syn"
version = "1.0.60"
@@ -584,12 +685,26 @@ dependencies = [
"unicode-xid",
]
+[[package]]
+name = "synstructure"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
[[package]]
name = "systems"
version = "0.1.0"
dependencies = [
"ntest",
+ "plotlib",
"rand",
+ "rustplotlib",
"uom",
]
diff --git a/src/systems/a320_systems/src/hydraulic.rs b/src/systems/a320_systems/src/hydraulic.rs
index 6760fec7ce7..e433a94983b 100644
--- a/src/systems/a320_systems/src/hydraulic.rs
+++ b/src/systems/a320_systems/src/hydraulic.rs
@@ -1,16 +1,877 @@
+use std::time::{Duration, Instant};
+use uom::si::{
+ acceleration::foot_per_second_squared, area::square_meter, f64::*, force::newton, length::foot,
+ length::meter, mass_density::kilogram_per_cubic_meter, pressure::atmosphere, pressure::pascal,
+ pressure::psi, ratio::percent, thermodynamic_temperature::degree_celsius, time::second,
+ velocity::knot, volume::cubic_inch, volume::gallon, volume::liter,
+ volume_rate::cubic_meter_per_second, volume_rate::gallon_per_second,
+};
+
+//use crate::{ hydraulic::{ElectricPump, EngineDrivenPump, HydFluid, HydLoop, LoopColor, PressureSource, Ptu, Pump, RatPump}, overhead::{OnOffFaultPushButton}, shared::DelayedTrueLogicGate};
+use systems::engine::Engine;
+use systems::hydraulic::brakecircuit::BrakeCircuit;
+use systems::hydraulic::{
+ ElectricPump, EngineDrivenPump, HydFluid, HydLoop, LoopColor, PressureSource, Ptu,
+ RatPropeller, RatPump,
+};
+use systems::overhead::{AutoOffFaultPushButton, FirePushButton, OnOffFaultPushButton};
+use systems::{
+ hydraulic::brakecircuit::AutoBrakeController,
+ simulation::{
+ SimulationElement, SimulationElementVisitor, SimulatorReader, SimulatorWriter,
+ UpdateContext,
+ },
+};
+
pub struct A320Hydraulic {
- // Until hydraulic is implemented, we'll fake it with this boolean.
- blue_pressurised: bool,
+ hyd_logic_inputs: A320HydraulicLogic,
+ hyd_brake_logic: A320HydraulicBrakingLogic,
+ blue_loop: HydLoop,
+ green_loop: HydLoop,
+ yellow_loop: HydLoop,
+ engine_driven_pump_1: EngineDrivenPump,
+ engine_driven_pump_2: EngineDrivenPump,
+ blue_electric_pump: ElectricPump,
+ yellow_electric_pump: ElectricPump,
+ rat: RatPump,
+ ptu: Ptu,
+ braking_circuit_norm: BrakeCircuit,
+ //autobrake_controller : AutoBrakeController,
+ braking_circuit_altn: BrakeCircuit,
+ total_sim_time_elapsed: Duration,
+ lag_time_accumulator: Duration,
+ debug_refresh_duration: Duration,
}
impl A320Hydraulic {
+ const MIN_PRESS_PRESSURISED: f64 = 150.0;
+ const HYDRAULIC_SIM_TIME_STEP: u64 = 100; //refresh rate of hydraulic simulation in ms
+ const ACTUATORS_SIM_TIME_STEP_MULT: u32 = 2; //refresh rate of actuators as multiplier of hydraulics. 2 means double frequency update
+
pub fn new() -> A320Hydraulic {
A320Hydraulic {
- blue_pressurised: true,
+ hyd_logic_inputs: A320HydraulicLogic::new(),
+ hyd_brake_logic: A320HydraulicBrakingLogic::new(),
+
+ blue_loop: HydLoop::new(
+ LoopColor::Blue,
+ false,
+ false,
+ Volume::new::(15.8),
+ Volume::new::(15.85),
+ Volume::new::(8.0),
+ Volume::new::(1.56),
+ HydFluid::new(Pressure::new::(1450000000.0)),
+ ),
+ green_loop: HydLoop::new(
+ LoopColor::Green,
+ true,
+ false,
+ Volume::new::(26.38),
+ Volume::new::(26.41),
+ Volume::new::(15.),
+ Volume::new::(3.6),
+ HydFluid::new(Pressure::new::(1450000000.0)),
+ ),
+ yellow_loop: HydLoop::new(
+ LoopColor::Yellow,
+ false,
+ true,
+ Volume::new::(19.75),
+ Volume::new::(19.81),
+ Volume::new::(10.0),
+ Volume::new::(3.15),
+ HydFluid::new(Pressure::new::(1450000000.0)),
+ ),
+ engine_driven_pump_1: EngineDrivenPump::new(),
+ engine_driven_pump_2: EngineDrivenPump::new(),
+ blue_electric_pump: ElectricPump::new(),
+ yellow_electric_pump: ElectricPump::new(),
+ rat: RatPump::new(),
+ ptu: Ptu::new(),
+
+ braking_circuit_norm: BrakeCircuit::new(
+ Volume::new::(0.),
+ Volume::new::(0.),
+ Volume::new::(0.08),
+ ),
+
+ //autobrake_controller : AutoBrakeController::new(vec![Acceleration::new::(-5.),Acceleration::new::(-10.),Acceleration::new::(-20.)]),
+ braking_circuit_altn: BrakeCircuit::new(
+ Volume::new::(1.5),
+ Volume::new::(0.0),
+ Volume::new::(0.08),
+ ),
+
+ total_sim_time_elapsed: Duration::new(0, 0),
+ lag_time_accumulator: Duration::new(0, 0),
+ debug_refresh_duration: Duration::new(0, 0),
}
}
pub fn is_blue_pressurised(&self) -> bool {
- self.blue_pressurised
+ self.blue_loop.get_pressure().get::() >= A320Hydraulic::MIN_PRESS_PRESSURISED
+ }
+
+ pub fn is_green_pressurised(&self) -> bool {
+ self.green_loop.get_pressure().get::() >= A320Hydraulic::MIN_PRESS_PRESSURISED
+ }
+
+ pub fn is_yellow_pressurised(&self) -> bool {
+ self.yellow_loop.get_pressure().get::() >= A320Hydraulic::MIN_PRESS_PRESSURISED
+ }
+
+ pub fn update(
+ &mut self,
+ ct: &UpdateContext,
+ engine1: &Engine,
+ engine2: &Engine,
+ overhead_panel: &A320HydraulicOverheadPanel,
+ ) {
+ let min_hyd_loop_timestep = Duration::from_millis(A320Hydraulic::HYDRAULIC_SIM_TIME_STEP); //Hyd Sim rate = 10 Hz
+
+ self.total_sim_time_elapsed += ct.delta;
+
+ //time to catch up in our simulation = new delta + time not updated last iteration
+ let time_to_catch = ct.delta + self.lag_time_accumulator;
+
+ //Number of time steps to do according to required time step
+ let number_of_steps_f64 = time_to_catch.as_secs_f64() / min_hyd_loop_timestep.as_secs_f64();
+
+ self.debug_refresh_duration += ct.delta;
+ if self.debug_refresh_duration > Duration::from_secs_f64(0.3) {
+ println!(
+ "---HYDRAULIC UPDATE : t={}",
+ self.total_sim_time_elapsed.as_secs_f64()
+ );
+ println!(
+ "---G: {:.0} B: {:.0} Y: {:.0}",
+ self.green_loop.get_pressure().get::(),
+ self.blue_loop.get_pressure().get::(),
+ self.yellow_loop.get_pressure().get::()
+ );
+ println!(
+ "---RAT stow {:.0} Rat rpm: {:.0}",
+ self.rat.get_stow_position(),
+ self.rat.prop.get_rpm(),
+ );
+ // println!("---BRAKELOGIC : Ldmn={} Rdmn={} PbrakInd={} Askid={}",self.hyd_brake_logic.left_brake_command,self.hyd_brake_logic.right_brake_command,self.hyd_brake_logic.parking_brake_demand,self.hyd_brake_logic.anti_skid_activated);
+ // println!("---BRAKEDMNDS : LG={} RG={} LY={} RY={}",self.hyd_brake_logic.left_brake_green_command,self.hyd_brake_logic.right_brake_green_command,self.hyd_brake_logic.left_brake_yellow_command,self.hyd_brake_logic.right_brake_yellow_command);
+ //println!("---L={:.0} C={:.0} R={:.0}",ct.wheel_left_rpm,ct.wheel_center_rpm,ct.wheel_right_rpm);
+
+ // let leftLock = ct.wheel_center_rpm / ct.wheel_left_rpm.max(1.);
+ // let rightLock = ct.wheel_center_rpm / ct.wheel_right_rpm.max(1.);
+ // println!("---LS={:.0} RS={:.0}",leftLock,rightLock);
+ // println!("---EDP1 n2={} EDP2 n2={}", engine1.n2.get::(), engine2.n2.get::());
+ // println!("---EDP1 flowMax={:.1}gpm EDP2 flowMax={:.1}gpm", (self.engine_driven_pump_1.get_delta_vol_max().get::() / min_hyd_loop_timestep.as_secs_f64() )* 60.0, (self.engine_driven_pump_2.get_delta_vol_max().get::()/min_hyd_loop_timestep.as_secs_f64())*60.0);
+ //println!("---AutoBrakes={:.0} command={:0.2} accel={:0.2} accerror={:0.2}",self.hyd_brake_logic.autobrakes_setting,self.autobrake_controller.get_brake_command(),self.autobrake_controller.current_filtered_accel.get::(),self.autobrake_controller.current_accel_error.get::());
+ // println!("---steps required: {:.2}", number_of_steps_f64);
+ self.debug_refresh_duration = Duration::from_secs_f64(0.0);
+ }
+
+ //updating rat stowed pos on all frames in case it's used for graphics
+ self.rat.update_stow_pos(&ct.delta);
+
+ if number_of_steps_f64 < 1.0 {
+ //Can't do a full time step
+ //we can either do an update with smaller step or wait next iteration
+
+ self.lag_time_accumulator =
+ Duration::from_secs_f64(number_of_steps_f64 * min_hyd_loop_timestep.as_secs_f64());
+ //Time lag is float part of num of steps * fixed time step to get a result in time
+ } else {
+ let num_of_update_loops = number_of_steps_f64.floor() as u32; //Int part is the actual number of loops to do
+ //Rest of floating part goes into accumulator
+ self.lag_time_accumulator = Duration::from_secs_f64(
+ (number_of_steps_f64 - (num_of_update_loops as f64))
+ * min_hyd_loop_timestep.as_secs_f64(),
+ ); //Keep track of time left after all fixed loop are done
+
+ //UPDATING HYDRAULICS AT FIXED STEP
+ for cur_loop in 0..num_of_update_loops {
+ //Base logic update based on overhead Could be done only once (before that loop) but if so delta time should be set accordingly
+ self.update_hyd_logic_inputs(&min_hyd_loop_timestep, &overhead_panel, &ct);
+
+ //Process brake logic (which circuit brakes) and send brake demands (how much)
+ self.hyd_brake_logic
+ .update_brake_demands(&min_hyd_loop_timestep, &self.green_loop);
+ self.hyd_brake_logic.send_brake_demands(
+ &mut self.braking_circuit_norm,
+ &mut self.braking_circuit_altn,
+ );
+
+ //UPDATE HYDRAULICS FIXED TIME STEP
+ self.ptu.update(&self.green_loop, &self.yellow_loop);
+ self.engine_driven_pump_1.update(
+ &min_hyd_loop_timestep,
+ &ct,
+ &self.green_loop,
+ &engine1,
+ );
+ self.engine_driven_pump_2.update(
+ &min_hyd_loop_timestep,
+ &ct,
+ &self.yellow_loop,
+ &engine2,
+ );
+ self.yellow_electric_pump
+ .update(&min_hyd_loop_timestep, &ct, &self.yellow_loop);
+ self.blue_electric_pump
+ .update(&min_hyd_loop_timestep, &ct, &self.blue_loop);
+
+ self.rat
+ .update(&min_hyd_loop_timestep, &ct, &self.blue_loop);
+ self.green_loop.update(
+ &min_hyd_loop_timestep,
+ &ct,
+ Vec::new(),
+ vec![&self.engine_driven_pump_1],
+ Vec::new(),
+ vec![&self.ptu],
+ );
+ self.yellow_loop.update(
+ &min_hyd_loop_timestep,
+ &ct,
+ vec![&self.yellow_electric_pump],
+ vec![&self.engine_driven_pump_2],
+ Vec::new(),
+ vec![&self.ptu],
+ );
+ self.blue_loop.update(
+ &min_hyd_loop_timestep,
+ &ct,
+ vec![&self.blue_electric_pump],
+ Vec::new(),
+ vec![&self.rat],
+ Vec::new(),
+ );
+
+ //self.autobrake_controller.update(&ct.delta, &ct);
+ self.braking_circuit_norm
+ .update(&min_hyd_loop_timestep, &self.green_loop);
+ self.braking_circuit_altn
+ .update(&min_hyd_loop_timestep, &self.yellow_loop);
+ self.braking_circuit_norm.reset_accumulators();
+ self.braking_circuit_altn.reset_accumulators();
+ }
+
+ //UPDATING ACTUATOR PHYSICS AT "FIXED STEP / ACTUATORS_SIM_TIME_STEP_MULT"
+ //Here put everything that needs higher simulation rates
+ let num_of_actuators_update_loops =
+ num_of_update_loops * A320Hydraulic::ACTUATORS_SIM_TIME_STEP_MULT;
+ let delta_time_physics =
+ min_hyd_loop_timestep / A320Hydraulic::ACTUATORS_SIM_TIME_STEP_MULT; //If X times faster we divide step by X
+ for cur_loop in 0..num_of_actuators_update_loops {
+ self.rat
+ .update_physics(&delta_time_physics, &ct.indicated_airspeed);
+ }
+ }
+ }
+
+ pub fn update_hyd_logic_inputs(
+ &mut self,
+ delta_time_update: &Duration,
+ overhead_panel: &A320HydraulicOverheadPanel,
+ ct: &UpdateContext,
+ ) {
+ let mut cargo_operated_ptu = false;
+ let mut cargo_operated_ypump = false;
+ let mut nsw_pin_inserted = false;
+
+ //Only evaluate ground conditions if on ground, if superman needs to operate cargo door in flight feel free to update
+ if self.hyd_logic_inputs.weight_on_wheels {
+ cargo_operated_ptu = self
+ .hyd_logic_inputs
+ .is_cargo_operation_ptu_flag(&delta_time_update);
+ cargo_operated_ypump = self
+ .hyd_logic_inputs
+ .is_cargo_operation_flag(&delta_time_update);
+ nsw_pin_inserted = self
+ .hyd_logic_inputs
+ .is_nsw_pin_inserted_flag(&delta_time_update);
+ }
+
+ //Basic faults of pumps
+ if self.yellow_electric_pump.is_active() && !self.is_yellow_pressurised() {
+ self.hyd_logic_inputs.yellow_epump_has_fault = true;
+ } else {
+ self.hyd_logic_inputs.yellow_epump_has_fault = false;
+ }
+ if self.engine_driven_pump_1.is_active() && !self.is_green_pressurised() {
+ self.hyd_logic_inputs.green_edp_has_fault = true;
+ } else {
+ self.hyd_logic_inputs.green_edp_has_fault = false;
+ }
+ if self.engine_driven_pump_2.is_active() && !self.is_yellow_pressurised() {
+ self.hyd_logic_inputs.yellow_edp_has_fault = true;
+ } else {
+ self.hyd_logic_inputs.yellow_edp_has_fault = false;
+ }
+ if self.blue_electric_pump.is_active() && !self.is_blue_pressurised() {
+ self.hyd_logic_inputs.blue_epump_has_fault = true;
+ } else {
+ self.hyd_logic_inputs.blue_epump_has_fault = false;
+ }
+
+ //RAT Deployment //Todo check all other needed conditions
+ if !self.hyd_logic_inputs.eng_1_master_on
+ && !self.hyd_logic_inputs.eng_2_master_on
+ && ct.indicated_airspeed > Velocity::new::(100.)
+ //Todo get speed from ADIRS
+ {
+ self.rat.set_active();
+ }
+
+ if overhead_panel.edp1_push_button.is_auto()
+ && self.hyd_logic_inputs.eng_1_master_on
+ && !overhead_panel.eng1_fire_pb.is_released()
+ {
+ self.engine_driven_pump_1.start();
+ } else if overhead_panel.edp1_push_button.is_off() {
+ self.engine_driven_pump_1.stop();
+ }
+
+ //FIRE valves logic for EDP1
+ if overhead_panel.eng1_fire_pb.is_released() {
+ self.engine_driven_pump_1.stop();
+ self.green_loop.set_fire_shutoff_valve_state(false);
+ } else {
+ self.green_loop.set_fire_shutoff_valve_state(true);
+ }
+
+ if overhead_panel.edp2_push_button.is_auto()
+ && self.hyd_logic_inputs.eng_2_master_on
+ && !overhead_panel.eng2_fire_pb.is_released()
+ {
+ self.engine_driven_pump_2.start();
+ } else if overhead_panel.edp2_push_button.is_off() {
+ self.engine_driven_pump_2.stop();
+ }
+
+ //FIRE valves logic for EDP2
+ if overhead_panel.eng2_fire_pb.is_released() {
+ self.engine_driven_pump_2.stop();
+ self.yellow_loop.set_fire_shutoff_valve_state(false);
+ } else {
+ self.yellow_loop.set_fire_shutoff_valve_state(true);
+ }
+
+ if overhead_panel.yellow_epump_push_button.is_off() || cargo_operated_ypump {
+ self.yellow_electric_pump.start();
+ } else if overhead_panel.yellow_epump_push_button.is_auto() {
+ self.yellow_electric_pump.stop();
+ }
+ if overhead_panel.blue_epump_push_button.is_auto() {
+ if self.hyd_logic_inputs.eng_1_master_on
+ || self.hyd_logic_inputs.eng_2_master_on
+ || overhead_panel.blue_epump_override_push_button.is_on()
+ {
+ self.blue_electric_pump.start();
+ } else {
+ self.blue_electric_pump.stop();
+ }
+ } else if overhead_panel.blue_epump_push_button.is_off() {
+ self.blue_electric_pump.stop();
+ }
+
+ let ptu_inhibit = cargo_operated_ptu && overhead_panel.yellow_epump_push_button.is_auto(); //TODO is auto will change once auto/on button is created in overhead library
+ if overhead_panel.ptu_push_button.is_auto()
+ && (!self.hyd_logic_inputs.weight_on_wheels
+ || self.hyd_logic_inputs.eng_1_master_on && self.hyd_logic_inputs.eng_2_master_on
+ || !self.hyd_logic_inputs.eng_1_master_on && !self.hyd_logic_inputs.eng_2_master_on
+ || (!self.hyd_logic_inputs.parking_brake_applied && !nsw_pin_inserted))
+ && !ptu_inhibit
+ {
+ self.ptu.enabling(true);
+ } else {
+ self.ptu.enabling(false);
+ }
+ }
+}
+
+impl SimulationElement for A320Hydraulic {
+ fn accept(&mut self, visitor: &mut T) {
+ visitor.visit(&mut self.hyd_logic_inputs);
+ visitor.visit(&mut self.hyd_brake_logic);
+ visitor.visit(self);
+ }
+
+ fn write(&self, writer: &mut SimulatorWriter) {
+ writer.write_f64(
+ "HYD_GREEN_PRESSURE",
+ self.green_loop.get_pressure().get::(),
+ );
+ writer.write_f64(
+ "HYD_GREEN_RESERVOIR",
+ self.green_loop.get_reservoir_volume().get::(),
+ );
+ writer.write_bool(
+ "HYD_GREEN_EDPUMP_ACTIVE",
+ self.engine_driven_pump_1.is_active(),
+ );
+ writer.write_bool(
+ "HYD_GREEN_EDPUMP_LOW_PRESS",
+ self.hyd_logic_inputs.green_edp_has_fault,
+ );
+ writer.write_bool(
+ "HYD_GREEN_FIRE_VALVE_OPENED",
+ self.green_loop.get_fire_shutoff_valve_state(),
+ );
+
+ writer.write_f64(
+ "HYD_BLUE_PRESSURE",
+ self.blue_loop.get_pressure().get::(),
+ );
+ writer.write_f64(
+ "HYD_BLUE_RESERVOIR",
+ self.blue_loop.get_reservoir_volume().get::(),
+ );
+ writer.write_bool("HYD_BLUE_EPUMP_ACTIVE", self.blue_electric_pump.is_active());
+ writer.write_bool(
+ "HYD_BLUE_EPUMP_LOW_PRESS",
+ self.hyd_logic_inputs.blue_epump_has_fault,
+ );
+
+ writer.write_f64(
+ "HYD_YELLOW_PRESSURE",
+ self.yellow_loop.get_pressure().get::(),
+ );
+ writer.write_f64(
+ "HYD_YELLOW_RESERVOIR",
+ self.yellow_loop.get_reservoir_volume().get::(),
+ );
+ writer.write_bool(
+ "HYD_YELLOW_EDPUMP_ACTIVE",
+ self.engine_driven_pump_2.is_active(),
+ );
+ writer.write_bool(
+ "HYD_YELLOW_EDPUMP_LOW_PRESS",
+ self.hyd_logic_inputs.yellow_edp_has_fault,
+ );
+ writer.write_bool(
+ "HYD_YELLOW_FIRE_VALVE_OPENED",
+ self.yellow_loop.get_fire_shutoff_valve_state(),
+ );
+ writer.write_bool(
+ "HYD_YELLOW_EPUMP_ACTIVE",
+ self.yellow_electric_pump.is_active(),
+ );
+ writer.write_bool(
+ "HYD_YELLOW_EPUMP_LOW_PRESS",
+ self.hyd_logic_inputs.yellow_epump_has_fault,
+ );
+
+ writer.write_bool("HYD_PTU_VALVE_OPENED", self.ptu.is_enabled());
+ writer.write_bool("HYD_PTU_ACTIVE_Y2G", self.ptu.get_is_active_right_to_left());
+ writer.write_bool("HYD_PTU_ACTIVE_G2Y", self.ptu.get_is_active_left_to_right());
+ writer.write_f64(
+ "HYD_PTU_MOTOR_FLOW",
+ self.ptu.get_flow().get::(),
+ );
+
+ writer.write_f64("HYD_RAT_STOW_POSITION", self.rat.get_stow_position());
+
+ writer.write_f64("HYD_RAT_RPM", self.rat.prop.get_rpm());
+
+ //BRAKES
+ writer.write_f64(
+ "HYD_BRAKE_NORM_LEFT_PRESS",
+ self.braking_circuit_norm
+ .get_brake_pressure_left()
+ .get::(),
+ );
+ writer.write_f64(
+ "HYD_BRAKE_NORM_RIGHT_PRESS",
+ self.braking_circuit_norm
+ .get_brake_pressure_right()
+ .get::(),
+ );
+ writer.write_f64(
+ "HYD_BRAKE_ALTN_LEFT_PRESS",
+ self.braking_circuit_altn
+ .get_brake_pressure_left()
+ .get::(),
+ );
+ writer.write_f64(
+ "HYD_BRAKE_ALTN_RIGHT_PRESS",
+ self.braking_circuit_altn
+ .get_brake_pressure_right()
+ .get::(),
+ );
+ writer.write_f64(
+ "HYD_BRAKE_ALTN_ACC_PRESS",
+ self.braking_circuit_altn.get_acc_pressure().get::(),
+ );
+
+ //Send overhead fault info
+ writer.write_bool(
+ "OVHD_HYD_ENG_1_PUMP_PB_HAS_FAULT",
+ self.hyd_logic_inputs.green_edp_has_fault,
+ );
+ writer.write_bool(
+ "OVHD_HYD_ENG_2_PUMP_PB_HAS_FAULT",
+ self.hyd_logic_inputs.yellow_edp_has_fault,
+ );
+ writer.write_bool(
+ "OVHD_HYD_EPUMPB_PB_HAS_FAULT",
+ self.hyd_logic_inputs.blue_epump_has_fault,
+ );
+ writer.write_bool(
+ "OVHD_HYD_EPUMPY_PB_HAS_FAULT",
+ self.hyd_logic_inputs.yellow_epump_has_fault,
+ );
+
+ //TODO Write here brake position based on applied pressure?
+ //TODO Decide here to set parking brake position to true if it's actually the case in hydraulic?
+ // let mut max_left_press= self.braking_circuit_norm.get_brake_pressure_left().get::().max(self.braking_circuit_altn.get_brake_pressure_left().get::());
+ // max_left_press = max_left_press / 3000.0;
+ // max_left_press = max_left_press.min(1.0).max(0.0);
+
+ // let mut max_right_press= self.braking_circuit_norm.get_brake_pressure_right().get::().max(self.braking_circuit_altn.get_brake_pressure_right().get::());
+ // max_right_press = max_right_press / 3000.0;
+ // max_right_press = max_right_press.min(1.0).max(0.0);
+
+ // writer.write_f64("BRAKE LEFT DMND", max_left_press);
+ // writer.write_f64("BRAKE RIGHT DMND", max_right_press);
+ }
+}
+
+pub struct A320HydraulicBrakingLogic {
+ parking_brake_demand: bool,
+ weight_on_wheels: bool,
+ left_brake_command: f64,
+ right_brake_command: f64,
+ left_brake_green_command: f64, //Actual command sent to left green circuit
+ left_brake_yellow_command: f64, //Actual command sent to left yellow circuit
+ right_brake_green_command: f64, //Actual command sent to right green circuit
+ right_brake_yellow_command: f64, //Actual command sent to right yellow circuit
+ anti_skid_activated: bool,
+ autobrakes_setting: u8,
+}
+
+//Implements brakes computers logic
+impl A320HydraulicBrakingLogic {
+ const PARK_BRAKE_DEMAND_DYNAMIC: f64 = 0.8; //Dynamic of the parking brake application/removal in (percent/100) per s
+ const LOW_PASS_FILTER_BRAKE_COMMAND: f64 = 0.85; //Low pass filter on all brake commands
+
+ pub fn new() -> A320HydraulicBrakingLogic {
+ A320HydraulicBrakingLogic {
+ parking_brake_demand: true, //Position of parking brake lever
+ weight_on_wheels: true,
+ left_brake_command: 1.0, //Command read from pedals
+ right_brake_command: 1.0, //Command read from pedals
+ left_brake_green_command: 0.0, //Actual command sent to left green circuit
+ left_brake_yellow_command: 1.0, //Actual command sent to left yellow circuit
+ right_brake_green_command: 0.0, //Actual command sent to right green circuit
+ right_brake_yellow_command: 1.0, //Actual command sent to right yellow circuit
+ anti_skid_activated: true,
+ autobrakes_setting: 0,
+ }
+ }
+
+ //Updates final brake demands per hydraulic loop based on pilot pedal demands
+ //TODO: think about where to build those brake demands from autobrake if not from brake pedals
+ pub fn update_brake_demands(&mut self, delta_time_update: &Duration, green_loop: &HydLoop) {
+ let green_used_for_brakes = green_loop.get_pressure()
+ > Pressure::new::(A320Hydraulic::MIN_PRESS_PRESSURISED)
+ && self.anti_skid_activated
+ && !self.parking_brake_demand;
+
+ let dynamic_increment =
+ A320HydraulicBrakingLogic::PARK_BRAKE_DEMAND_DYNAMIC * delta_time_update.as_secs_f64();
+
+ if green_used_for_brakes {
+ self.left_brake_green_command = A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND
+ * self.left_brake_command
+ + (1.0 - A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND)
+ * self.left_brake_green_command;
+ self.right_brake_green_command =
+ A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND * self.right_brake_command
+ + (1.0 - A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND)
+ * self.right_brake_green_command;
+
+ self.left_brake_yellow_command -= dynamic_increment;
+ self.right_brake_yellow_command -= dynamic_increment;
+ } else {
+ if !self.parking_brake_demand {
+ //Normal braking but using alternate circuit
+ self.left_brake_yellow_command =
+ A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND
+ * self.left_brake_command
+ + (1.0 - A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND)
+ * self.left_brake_yellow_command;
+ self.right_brake_yellow_command =
+ A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND
+ * self.right_brake_command
+ + (1.0 - A320HydraulicBrakingLogic::LOW_PASS_FILTER_BRAKE_COMMAND)
+ * self.right_brake_yellow_command;
+ } else {
+ //Else we just use parking brake
+ self.left_brake_yellow_command += dynamic_increment;
+ self.right_brake_yellow_command += dynamic_increment;
+ }
+ self.left_brake_green_command -= dynamic_increment;
+ self.right_brake_green_command -= dynamic_increment;
+ }
+
+ //limiting final values
+ self.left_brake_yellow_command = self.left_brake_yellow_command.min(1.).max(0.);
+ self.right_brake_yellow_command = self.right_brake_yellow_command.min(1.).max(0.);
+ self.left_brake_green_command = self.left_brake_green_command.min(1.).max(0.);
+ self.right_brake_green_command = self.right_brake_green_command.min(1.).max(0.);
+ }
+
+ pub fn send_brake_demands(&mut self, norm: &mut BrakeCircuit, altn: &mut BrakeCircuit) {
+ norm.set_brake_demand_left(self.left_brake_green_command);
+ norm.set_brake_demand_right(self.right_brake_green_command);
+ altn.set_brake_demand_left(self.left_brake_yellow_command);
+ altn.set_brake_demand_right(self.right_brake_yellow_command);
+ }
+}
+
+impl SimulationElement for A320HydraulicBrakingLogic {
+ fn accept(&mut self, visitor: &mut T) {
+ visitor.visit(self);
+ }
+
+ fn read(&mut self, state: &mut SimulatorReader) {
+ self.parking_brake_demand = state.read_bool("PARK_BRAKE_DMND"); //TODO see if A32nx var exists
+ self.weight_on_wheels = state.read_bool("SIM ON GROUND");
+ self.anti_skid_activated = state.read_bool("ANTISKID ACTIVE");
+ self.left_brake_command = state.read_f64("BRAKE LEFT DMND") / 100.0;
+ self.right_brake_command = state.read_f64("BRAKE RIGHT DMND") / 100.0;
+ self.autobrakes_setting = state.read_f64("AUTOBRAKES SETTING").floor() as u8;
+ }
+
+ fn write(&self, writer: &mut SimulatorWriter) {
+ //TODO Decide here to set parking brake position to true if it's actually the case in hydraulic?
+ }
+}
+
+pub struct A320HydraulicLogic {
+ parking_brake_applied: bool,
+ weight_on_wheels: bool,
+ eng_1_master_on: bool,
+ eng_2_master_on: bool,
+ nws_tow_engaged_timer: Duration,
+ cargo_door_front_pos: f64,
+ cargo_door_back_pos: f64,
+ cargo_door_front_pos_prev: f64,
+ cargo_door_back_pos_prev: f64,
+ cargo_door_timer: Duration,
+ cargo_door_timer_ptu: Duration,
+ pushback_angle: f64,
+ pushback_angle_prev: f64,
+ pushback_state: f64,
+ yellow_epump_has_fault: bool,
+ blue_epump_has_fault: bool,
+ green_edp_has_fault: bool,
+ yellow_edp_has_fault: bool,
+}
+
+//Implements low level logic for all hydraulics commands
+impl A320HydraulicLogic {
+ const CARGO_OPERATED_TIMEOUT_YPUMP: f64 = 20.0; //Timeout to shut off yellow epump after cargo operation
+ const CARGO_OPERATED_TIMEOUT_PTU: f64 = 40.0; //Timeout to keep ptu inhibited after cargo operation
+ const NWS_PIN_REMOVE_TIMEOUT: f64 = 15.0; //Time for ground crew to remove pin after tow
+
+ pub fn new() -> A320HydraulicLogic {
+ A320HydraulicLogic {
+ parking_brake_applied: true,
+ weight_on_wheels: true,
+ eng_1_master_on: false,
+ eng_2_master_on: false,
+ nws_tow_engaged_timer: Duration::from_secs_f64(0.0),
+ cargo_door_front_pos: 0.0,
+ cargo_door_back_pos: 0.0,
+ cargo_door_front_pos_prev: 0.0,
+ cargo_door_back_pos_prev: 0.0,
+ cargo_door_timer: Duration::from_secs_f64(0.0),
+ cargo_door_timer_ptu: Duration::from_secs_f64(0.0),
+ pushback_angle: 0.0,
+ pushback_angle_prev: 0.0,
+ pushback_state: 0.0,
+ yellow_epump_has_fault: false,
+ blue_epump_has_fault: false,
+ green_edp_has_fault: false,
+ yellow_edp_has_fault: false,
+ }
+ }
+
+ //TODO, code duplication to handle timeouts: generic function to do
+ pub fn is_cargo_operation_flag(&mut self, delta_time_update: &Duration) -> bool {
+ let cargo_door_moved = self.cargo_door_back_pos != self.cargo_door_back_pos_prev
+ || self.cargo_door_front_pos != self.cargo_door_front_pos_prev;
+
+ if cargo_door_moved {
+ self.cargo_door_timer =
+ Duration::from_secs_f64(A320HydraulicLogic::CARGO_OPERATED_TIMEOUT_YPUMP);
+ } else {
+ if self.cargo_door_timer > *delta_time_update {
+ self.cargo_door_timer -= *delta_time_update;
+ } else {
+ self.cargo_door_timer = Duration::from_secs(0);
+ }
+ }
+
+ self.cargo_door_timer > Duration::from_secs_f64(0.0)
+ }
+
+ pub fn is_cargo_operation_ptu_flag(&mut self, delta_time_update: &Duration) -> bool {
+ let cargo_door_moved = self.cargo_door_back_pos != self.cargo_door_back_pos_prev
+ || self.cargo_door_front_pos != self.cargo_door_front_pos_prev;
+
+ if cargo_door_moved {
+ self.cargo_door_timer_ptu =
+ Duration::from_secs_f64(A320HydraulicLogic::CARGO_OPERATED_TIMEOUT_PTU);
+ } else {
+ if self.cargo_door_timer_ptu > *delta_time_update {
+ self.cargo_door_timer_ptu -= *delta_time_update;
+ } else {
+ self.cargo_door_timer_ptu = Duration::from_secs(0);
+ }
+ }
+
+ self.cargo_door_timer_ptu > Duration::from_secs_f64(0.0)
+ }
+
+ pub fn is_nsw_pin_inserted_flag(&mut self, delta_time_update: &Duration) -> bool {
+ let pushback_in_progress =
+ (self.pushback_angle != self.pushback_angle_prev) && self.pushback_state != 3.0;
+
+ if pushback_in_progress {
+ self.nws_tow_engaged_timer =
+ Duration::from_secs_f64(A320HydraulicLogic::NWS_PIN_REMOVE_TIMEOUT);
+ } else {
+ if self.nws_tow_engaged_timer > *delta_time_update {
+ self.nws_tow_engaged_timer -= *delta_time_update; //TODO CHECK if rollover issue to expect if not limiting to 0
+ } else {
+ self.nws_tow_engaged_timer = Duration::from_secs(0);
+ }
+ }
+
+ self.nws_tow_engaged_timer > Duration::from_secs(0)
+ }
+}
+
+impl SimulationElement for A320HydraulicLogic {
+ fn accept(&mut self, visitor: &mut T) {
+ visitor.visit(self);
+ }
+
+ fn read(&mut self, state: &mut SimulatorReader) {
+ self.parking_brake_applied = state.read_bool("PARK_BRAKE_ON");
+ self.eng_1_master_on = state.read_bool("ENG MASTER 1");
+ self.eng_2_master_on = state.read_bool("ENG MASTER 2");
+ self.weight_on_wheels = state.read_bool("SIM ON GROUND");
+
+ //Handling here of the previous values of cargo doors
+ self.cargo_door_front_pos_prev = self.cargo_door_front_pos;
+ self.cargo_door_front_pos = state.read_f64("CARGO FRONT POS");
+ self.cargo_door_back_pos_prev = self.cargo_door_back_pos;
+ self.cargo_door_back_pos = state.read_f64("CARGO BACK POS");
+
+ //Handling here of the previous values of pushback angle. Angle keeps moving while towed. Feel free to find better hack
+ self.pushback_angle_prev = self.pushback_angle;
+ self.pushback_angle = state.read_f64("PUSHBACK ANGLE");
+ self.pushback_state = state.read_f64("PUSHBACK STATE");
+ }
+}
+
+pub struct A320HydraulicOverheadPanel {
+ pub edp1_push_button: AutoOffFaultPushButton,
+ pub edp2_push_button: AutoOffFaultPushButton,
+ pub blue_epump_push_button: AutoOffFaultPushButton,
+ pub ptu_push_button: AutoOffFaultPushButton,
+ pub rat_push_button: AutoOffFaultPushButton,
+ pub yellow_epump_push_button: AutoOffFaultPushButton,
+ pub blue_epump_override_push_button: OnOffFaultPushButton,
+ pub eng1_fire_pb: FirePushButton,
+ pub eng2_fire_pb: FirePushButton,
+}
+
+impl A320HydraulicOverheadPanel {
+ pub fn new() -> A320HydraulicOverheadPanel {
+ A320HydraulicOverheadPanel {
+ edp1_push_button: AutoOffFaultPushButton::new_auto("HYD_ENG_1_PUMP"),
+ edp2_push_button: AutoOffFaultPushButton::new_auto("HYD_ENG_2_PUMP"),
+ blue_epump_push_button: AutoOffFaultPushButton::new_auto("HYD_EPUMPB"),
+ ptu_push_button: AutoOffFaultPushButton::new_auto("HYD_PTU"),
+ rat_push_button: AutoOffFaultPushButton::new_off("HYD_RAT"),
+ yellow_epump_push_button: AutoOffFaultPushButton::new_off("HYD_EPUMPY"),
+ blue_epump_override_push_button: OnOffFaultPushButton::new_off("HYD_EPUMPY_OVRD"),
+ eng1_fire_pb: FirePushButton::new("ENG1"),
+ eng2_fire_pb: FirePushButton::new("ENG2"),
+ }
+ }
+
+ pub fn update(&mut self, context: &UpdateContext) {}
+}
+
+impl SimulationElement for A320HydraulicOverheadPanel {
+ fn accept(&mut self, visitor: &mut T) {
+ self.edp1_push_button.accept(visitor);
+ self.edp2_push_button.accept(visitor);
+ self.blue_epump_push_button.accept(visitor);
+ self.ptu_push_button.accept(visitor);
+ self.rat_push_button.accept(visitor);
+ self.yellow_epump_push_button.accept(visitor);
+ self.blue_epump_override_push_button.accept(visitor);
+ self.eng1_fire_pb.accept(visitor);
+ self.eng2_fire_pb.accept(visitor);
+
+ visitor.visit(self);
+ }
+}
+
+#[cfg(test)]
+pub mod tests {
+ use std::time::Duration;
+
+ use uom::si::{
+ acceleration::{foot_per_second_squared, Acceleration},
+ f64::*,
+ length::foot,
+ thermodynamic_temperature::degree_celsius,
+ velocity::knot,
+ };
+
+ use super::A320HydraulicLogic;
+ use super::A320HydraulicOverheadPanel;
+ use crate::UpdateContext;
+
+ fn overhead() -> A320HydraulicOverheadPanel {
+ A320HydraulicOverheadPanel::new()
+ }
+
+ fn hyd_logic() -> A320HydraulicLogic {
+ A320HydraulicLogic::new()
+ }
+
+ fn context(delta_time: Duration) -> UpdateContext {
+ UpdateContext::new(
+ delta_time,
+ Velocity::new::(0.),
+ Length::new::(2000.),
+ ThermodynamicTemperature::new::(25.0),
+ true,
+ Acceleration::new::(0.),
+ 0.0,
+ 0.0,
+ 0.0,
+ )
+ }
+
+ #[test]
+ fn is_nws_pin_engaged_test() {
+ let mut overhead = overhead();
+ let mut logic = hyd_logic();
+
+ let update_delta = Duration::from_secs_f64(0.08);
+ assert!(!logic.is_nsw_pin_inserted_flag(&update_delta));
+
+ logic.pushback_angle = 1.001;
+ logic.pushback_state = 2.0;
+ assert!(logic.is_nsw_pin_inserted_flag(&update_delta));
}
}
diff --git a/src/systems/a320_systems/src/lib.rs b/src/systems/a320_systems/src/lib.rs
index 8ac5914b4c0..2a63b621dc3 100644
--- a/src/systems/a320_systems/src/lib.rs
+++ b/src/systems/a320_systems/src/lib.rs
@@ -5,7 +5,7 @@ mod pneumatic;
use self::{fuel::A320Fuel, pneumatic::A320PneumaticOverheadPanel};
use electrical::{A320Electrical, A320ElectricalOverheadPanel};
-use hydraulic::A320Hydraulic;
+use hydraulic::{A320Hydraulic,A320HydraulicOverheadPanel};
use systems::{
apu::{
Aps3200ApuGenerator, AuxiliaryPowerUnit, AuxiliaryPowerUnitFactory,
@@ -28,6 +28,7 @@ pub struct A320 {
electrical: A320Electrical,
ext_pwr: ExternalPowerSource,
hydraulic: A320Hydraulic,
+ hydraulic_overhead: A320HydraulicOverheadPanel,
}
impl A320 {
pub fn new() -> A320 {
@@ -43,6 +44,7 @@ impl A320 {
electrical: A320Electrical::new(),
ext_pwr: ExternalPowerSource::new(),
hydraulic: A320Hydraulic::new(),
+ hydraulic_overhead: A320HydraulicOverheadPanel::new(),
}
}
}
@@ -80,6 +82,13 @@ impl Aircraft for A320 {
&self.electrical_overhead,
);
self.electrical_overhead.update_after_elec(&self.electrical);
+
+ self.hydraulic.update(
+ context,
+ &self.engine_1,
+ &self.engine_2,
+ &self.hydraulic_overhead,
+ );
}
}
impl SimulationElement for A320 {
@@ -94,6 +103,8 @@ impl SimulationElement for A320 {
self.engine_2.accept(visitor);
self.electrical.accept(visitor);
self.ext_pwr.accept(visitor);
+ self.hydraulic.accept(visitor);
+ self.hydraulic_overhead.accept(visitor);
visitor.visit(self);
}
}
diff --git a/src/systems/a320_systems_wasm/src/lib.rs b/src/systems/a320_systems_wasm/src/lib.rs
index b35059250aa..59af5a9774a 100644
--- a/src/systems/a320_systems_wasm/src/lib.rs
+++ b/src/systems/a320_systems_wasm/src/lib.rs
@@ -36,6 +36,21 @@ struct A320SimulatorReaderWriter {
fuel_tank_left_main_quantity: AircraftVariable,
sim_on_ground: AircraftVariable,
unlimited_fuel: AircraftVariable,
+ parking_brake: AircraftVariable,
+ parking_brake_demand: AircraftVariable,
+ master_eng_1: AircraftVariable,
+ master_eng_2: AircraftVariable,
+ cargo_door_front_pos: AircraftVariable,
+ cargo_door_back_pos: AircraftVariable,
+ pushback_angle: AircraftVariable,
+ pushback_state: AircraftVariable,
+ anti_skid_activated: AircraftVariable,
+ left_brake_command: AircraftVariable,
+ right_brake_command: AircraftVariable,
+ long_accel: AircraftVariable,
+ wheel_rpm_center: AircraftVariable,
+ wheel_rpm_left: AircraftVariable,
+ wheel_rpm_right: AircraftVariable,
}
impl A320SimulatorReaderWriter {
fn new() -> Result> {
@@ -70,6 +85,26 @@ impl A320SimulatorReaderWriter {
)?,
sim_on_ground: AircraftVariable::from("SIM ON GROUND", "Bool", 0)?,
unlimited_fuel: AircraftVariable::from("UNLIMITED FUEL", "Bool", 0)?,
+
+ parking_brake: AircraftVariable::from("BRAKE PARKING POSITION", "Bool", 1)?,
+ parking_brake_demand: AircraftVariable::from("BRAKE PARKING INDICATOR", "Bool", 0)?,
+ master_eng_1: AircraftVariable::from("GENERAL ENG STARTER ACTIVE", "Bool", 1)?,
+ master_eng_2: AircraftVariable::from("GENERAL ENG STARTER ACTIVE", "Bool", 2)?,
+ cargo_door_front_pos: AircraftVariable::from("EXIT OPEN", "Percent", 5)?,
+ cargo_door_back_pos: AircraftVariable::from("EXIT OPEN", "Percent", 3)?, //TODO It is the catering door for now
+ pushback_angle: AircraftVariable::from("PUSHBACK ANGLE", "Radian", 0)?,
+ pushback_state: AircraftVariable::from("PUSHBACK STATE", "Enum", 0)?,
+ anti_skid_activated: AircraftVariable::from("ANTISKID BRAKES ACTIVE", "Bool", 0)?,
+ left_brake_command: AircraftVariable::from("BRAKE LEFT POSITION", "Percent", 0)?,
+ right_brake_command: AircraftVariable::from("BRAKE RIGHT POSITION", "Percent", 0)?,
+ long_accel: AircraftVariable::from(
+ "ACCELERATION BODY Z",
+ "feet per second squared",
+ 0,
+ )?,
+ wheel_rpm_center: AircraftVariable::from("CENTER WHEEL RPM", "Rpm", 0)?,
+ wheel_rpm_left: AircraftVariable::from("LEFT WHEEL RPM", "Rpm", 0)?,
+ wheel_rpm_right: AircraftVariable::from("RIGHT WHEEL RPM", "Rpm", 0)?,
})
}
}
@@ -90,6 +125,21 @@ impl SimulatorReaderWriter for A320SimulatorReaderWriter {
"AIRSPEED INDICATED" => self.airspeed_indicated.get(),
"INDICATED ALTITUDE" => self.indicated_altitude.get(),
"SIM ON GROUND" => self.sim_on_ground.get(),
+ "ENG MASTER 1" => self.master_eng_1.get(),
+ "ENG MASTER 2" => self.master_eng_2.get(),
+ "PARK_BRAKE_ON" => self.parking_brake.get(),
+ "PARK_BRAKE_DMND" => self.parking_brake_demand.get(),
+ "CARGO FRONT POS" => self.cargo_door_front_pos.get(),
+ "CARGO BACK POS" => self.cargo_door_back_pos.get(),
+ "PUSHBACK ANGLE" => self.pushback_angle.get(),
+ "PUSHBACK STATE" => self.pushback_state.get(),
+ "ANTISKID ACTIVE" => self.anti_skid_activated.get(),
+ "BRAKE LEFT DMND" => self.left_brake_command.get(),
+ "BRAKE RIGHT DMND" => self.right_brake_command.get(),
+ "LONG ACC" => self.long_accel.get(),
+ "WHEEL RPM CENTER" => self.wheel_rpm_center.get(),
+ "WHEEL RPM LEFT" => self.wheel_rpm_left.get(),
+ "WHEEL RPM RIGHT" => self.wheel_rpm_right.get(),
_ => {
lookup_named_variable(&mut self.dynamic_named_variables, "A32NX_", name).get_value()
}
diff --git a/src/systems/systems/Cargo.toml b/src/systems/systems/Cargo.toml
index c3f093e37b0..2cb0ee3b3a7 100644
--- a/src/systems/systems/Cargo.toml
+++ b/src/systems/systems/Cargo.toml
@@ -8,3 +8,5 @@ edition = "2018"
uom = "0.30.0"
rand = "0.8.0"
ntest = "0.7.2"
+plotlib = "0.5.1"
+rustplotlib = "0.0.4"
diff --git a/src/systems/systems/src/hydraulic/brakecircuit.rs b/src/systems/systems/src/hydraulic/brakecircuit.rs
new file mode 100644
index 00000000000..0f21bfeec74
--- /dev/null
+++ b/src/systems/systems/src/hydraulic/brakecircuit.rs
@@ -0,0 +1,519 @@
+use crate::{hydraulic::HydLoop, simulation::UpdateContext};
+
+use std::f64::consts::E;
+use std::time::Duration;
+use uom::{
+ si::{
+ acceleration::foot_per_second_squared,
+ f64::*,
+ force::newton,
+ length::foot,
+ length::meter,
+ mass_density::kilogram_per_cubic_meter,
+ pressure::atmosphere,
+ pressure::pascal,
+ pressure::psi,
+ ratio::percent,
+ thermodynamic_temperature::{self, degree_celsius},
+ time::second,
+ velocity::knot,
+ volume::cubic_inch,
+ volume::gallon,
+ volume::liter,
+ volume_rate::cubic_meter_per_second,
+ volume_rate::gallon_per_second,
+ },
+ typenum::private::IsLessOrEqualPrivate,
+};
+
+pub trait ActuatorHydInterface {
+ fn get_used_volume(&self) -> Volume;
+ fn get_reservoir_return(&self) -> Volume;
+}
+
+//Brakes implementation. This tries to do a simple model with a possibility to have an accumulator (or not)
+//Brake model is simplified as we just move brake position from 0 to 1 and take conrresponding fluid volume (vol = max_displacement * brake_position).
+// So it's fairly simplified as we just end up with brake pressure = hyd_pressure * current_position
+pub struct BrakeCircuit {
+ //Total volume used when at max braking position.
+ //Simple model for now we assume at max braking we have max brake displacement
+ total_displacement: Volume,
+
+ //actuator position //TODO make this more dynamic with a per wheel basis instead of left/right?
+ current_brake_position_left: f64,
+ demanded_brake_position_left: f64,
+ pressure_applied_left: Pressure,
+ current_brake_position_right: f64,
+ demanded_brake_position_right: f64,
+ pressure_applied_right: Pressure,
+
+ //Brake accumulator variables. Accumulator can have 0 volume if no accumulator
+ has_accumulator: bool,
+ accumulator_total_volume: Volume,
+ accumulator_gas_pressure: Pressure,
+ accumulator_gas_volume: Volume,
+ accumulator_fluid_volume: Volume,
+ accumulator_press_breakpoints: [f64; 9],
+ accumulator_flow_carac: [f64; 9],
+
+ //Common vars to all actuators: will be used by the calling loop to know what is used
+ //and what comes back to reservoir at each iteration
+ volume_to_actuator_accumulator: Volume,
+ volume_to_res_accumulator: Volume,
+
+ accumulator_fluid_pressure_sensor_filtered: Pressure, //Fluid pressure in brake circuit filtered for cockpit gauges
+}
+
+impl BrakeCircuit {
+ const ACCUMULATOR_GAS_PRE_CHARGE: f64 = 1000.0; // Nitrogen PSI
+ const ACCUMULATOR_PRESS_BREAKPTS: [f64; 9] =
+ [0.0, 5.0, 10.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 10000.0];
+ const ACCUMULATOR_FLOW_CARAC: [f64; 9] = [0.0, 0.01, 0.016, 0.02, 0.04, 0.1, 0.15, 0.35, 0.5];
+
+ //Filtered using time constant low pass: new_val = old_val + (new_val - old_val)* (1 - e^(-dt/TCONST))
+ const ACC_PRESSURE_SENSOR_FILTER_TIMECONST: f64 = 0.1; //Time constant of the filter used to measure brake circuit pressure
+
+ pub fn new(
+ accumulator_volume: Volume,
+ accumulator_fluid_volume_at_init: Volume,
+ total_displacement: Volume,
+ ) -> BrakeCircuit {
+ let mut has_accu = true;
+ if accumulator_volume <= Volume::new::(0.) {
+ has_accu = false;
+ }
+
+ //taking care init volume is maxed at accumulator capacity: we can't exceed max_volume minus a margin for gas to compress
+ let limited_volume = accumulator_fluid_volume_at_init.min(accumulator_volume * 0.9);
+
+ //If we don't start with empty accumulator we need to init pressure too
+ let mut gas_press_at_init = Pressure::new::(0.);
+ if has_accu {
+ gas_press_at_init = (Pressure::new::(BrakeCircuit::ACCUMULATOR_GAS_PRE_CHARGE)
+ * accumulator_volume)
+ / (accumulator_volume - limited_volume);
+ }
+
+ //Fluid pressure measure is equal to gas volume in accumulator only if there is fluid in the accumulator
+ let mut acc_fluid_press_init = Pressure::new::(0.);
+ if limited_volume > Volume::new::(0.) {
+ acc_fluid_press_init = gas_press_at_init;
+ }
+
+ BrakeCircuit {
+ total_displacement: total_displacement,
+ current_brake_position_left: 0.0,
+ demanded_brake_position_left: 0.0,
+ pressure_applied_left: Pressure::new::(0.0),
+ current_brake_position_right: 0.0,
+ demanded_brake_position_right: 0.0,
+ pressure_applied_right: Pressure::new::(0.0),
+ has_accumulator: has_accu,
+ accumulator_total_volume: accumulator_volume,
+ accumulator_gas_pressure: gas_press_at_init,
+ accumulator_gas_volume: accumulator_volume - limited_volume,
+ accumulator_fluid_volume: limited_volume,
+ accumulator_press_breakpoints: BrakeCircuit::ACCUMULATOR_PRESS_BREAKPTS,
+ accumulator_flow_carac: BrakeCircuit::ACCUMULATOR_FLOW_CARAC,
+ volume_to_actuator_accumulator: Volume::new::(0.),
+ volume_to_res_accumulator: Volume::new::(0.),
+ accumulator_fluid_pressure_sensor_filtered: acc_fluid_press_init, //Pressure measured after accumulator in brake circuit
+ }
+ }
+
+ pub fn update(
+ &mut self,
+ delta_time: &Duration,
+ //context: &UpdateContext,
+ hyd_loop: &HydLoop,
+ ) {
+ let delta_vol = ((self.demanded_brake_position_left - self.current_brake_position_left)
+ + (self.demanded_brake_position_right - self.current_brake_position_right))
+ * self.total_displacement;
+
+ //TODO this is a duplicate of an accumulator: refactor an accumulator object usable in all hydraulics code
+ if self.has_accumulator {
+ let accumulator_delta_press = self.accumulator_gas_pressure - hyd_loop.get_pressure();
+ let flow_variation = VolumeRate::new::(super::interpolation(
+ &self.accumulator_press_breakpoints,
+ &self.accumulator_flow_carac,
+ accumulator_delta_press.get::().abs(),
+ ));
+
+ if accumulator_delta_press.get::() < 0.0 {
+ let volume_to_acc = flow_variation * Time::new::(delta_time.as_secs_f64());
+ self.accumulator_fluid_volume += volume_to_acc;
+ self.accumulator_gas_volume -= volume_to_acc;
+ self.volume_to_actuator_accumulator += volume_to_acc;
+ }
+
+ if delta_vol > Volume::new::(0.0) {
+ let volume_from_acc = self.accumulator_fluid_volume.min(delta_vol);
+ if volume_from_acc != Volume::new::(0.0) {
+ self.accumulator_fluid_volume -= volume_from_acc;
+ self.accumulator_gas_volume += volume_from_acc;
+ } else {
+ self.demanded_brake_position_left = self.current_brake_position_left;
+ self.demanded_brake_position_right = self.current_brake_position_right;
+ }
+ } else {
+ self.volume_to_res_accumulator = self.volume_to_res_accumulator
+ + delta_vol.abs().min(self.accumulator_fluid_volume);
+ }
+
+ self.accumulator_gas_pressure =
+ (Pressure::new::(BrakeCircuit::ACCUMULATOR_GAS_PRE_CHARGE)
+ * self.accumulator_total_volume)
+ / (self.accumulator_total_volume - self.accumulator_fluid_volume);
+ } else {
+ //Else case if no accumulator: we just take deltavol needed or return it back to res
+ if delta_vol > Volume::new::(0.0)
+ && hyd_loop.get_pressure() >= Pressure::new::(100.)
+ {
+ self.volume_to_actuator_accumulator += delta_vol;
+ } else {
+ self.volume_to_res_accumulator += delta_vol.abs();
+ }
+ }
+
+ if self.accumulator_fluid_volume > Volume::new::(0.0) {
+ self.pressure_applied_left =
+ self.accumulator_gas_pressure * self.demanded_brake_position_left;
+ self.pressure_applied_right =
+ self.accumulator_gas_pressure * self.demanded_brake_position_right;
+
+ self.accumulator_fluid_pressure_sensor_filtered = self
+ .accumulator_fluid_pressure_sensor_filtered
+ + (self.accumulator_gas_pressure - self.accumulator_fluid_pressure_sensor_filtered)
+ * (1.
+ - E.powf(
+ -delta_time.as_secs_f64()
+ / BrakeCircuit::ACC_PRESSURE_SENSOR_FILTER_TIMECONST,
+ ));
+ } else {
+ self.pressure_applied_left =
+ hyd_loop.get_pressure() * self.demanded_brake_position_left;
+ self.pressure_applied_right =
+ hyd_loop.get_pressure() * self.demanded_brake_position_right;
+
+ self.accumulator_fluid_pressure_sensor_filtered = self
+ .accumulator_fluid_pressure_sensor_filtered
+ + (hyd_loop.get_pressure() - self.accumulator_fluid_pressure_sensor_filtered)
+ * (1.
+ - E.powf(
+ -delta_time.as_secs_f64()
+ / BrakeCircuit::ACC_PRESSURE_SENSOR_FILTER_TIMECONST,
+ ));
+ }
+ self.current_brake_position_left = self.demanded_brake_position_left;
+ self.current_brake_position_right = self.demanded_brake_position_right;
+ }
+
+ pub fn set_brake_demand_left(&mut self, brake_ratio: f64) {
+ self.demanded_brake_position_left = brake_ratio.min(1.0).max(0.0);
+ }
+ pub fn set_brake_demand_right(&mut self, brake_ratio: f64) {
+ self.demanded_brake_position_right = brake_ratio.min(1.0).max(0.0);
+ }
+
+ pub fn get_brake_pressure_left(&self) -> Pressure {
+ self.pressure_applied_left
+ }
+ pub fn get_brake_pressure_right(&self) -> Pressure {
+ self.pressure_applied_right
+ }
+ pub fn get_acc_pressure(&self) -> Pressure {
+ self.accumulator_fluid_pressure_sensor_filtered
+ }
+
+ pub fn reset_accumulators(&mut self) {
+ self.volume_to_res_accumulator = Volume::new::(0.);
+ self.volume_to_actuator_accumulator = Volume::new::(0.);
+ }
+}
+
+impl ActuatorHydInterface for BrakeCircuit {
+ fn get_used_volume(&self) -> Volume {
+ self.volume_to_actuator_accumulator
+ }
+ fn get_reservoir_return(&self) -> Volume {
+ self.volume_to_res_accumulator
+ }
+}
+
+pub struct AutoBrakeController {
+ accel_targets: Vec,
+ num_of_modes: usize,
+
+ current_selected_mode: usize,
+
+ pub current_filtered_accel: Acceleration,
+
+ pub current_accel_error: Acceleration,
+ accel_error_prev: Acceleration,
+ current_integral_term: f64,
+ current_brake_dmnd: f64, //Controller brake demand to satisfy autobrake mode [0:1]
+
+ is_enabled: bool,
+}
+
+impl AutoBrakeController {
+ const LONG_ACC_FILTER_TIMECONST: f64 = 0.1;
+
+ const CONTROLLER_P_GAIN: f64 = 0.05;
+ const CONTROLLER_I_GAIN: f64 = 0.001;
+ const CONTROLLER_D_GAIN: f64 = 0.01;
+
+ pub fn new(accel_targets: Vec) -> AutoBrakeController {
+ let num = accel_targets.len();
+ assert!(num > 0);
+ AutoBrakeController {
+ accel_targets: accel_targets,
+ num_of_modes: num,
+ current_selected_mode: 0,
+ current_filtered_accel: Acceleration::new::(0.0),
+ current_accel_error: Acceleration::new::(0.0),
+ accel_error_prev: Acceleration::new::(0.0),
+ current_integral_term: 0.,
+ current_brake_dmnd: 0.,
+ is_enabled: false,
+ }
+ }
+
+ pub fn update(&mut self, delta_time: &Duration, ct: &UpdateContext) {
+ self.current_filtered_accel = self.current_filtered_accel
+ + (ct.acc_long - self.current_filtered_accel)
+ * (1.
+ - E.powf(
+ -delta_time.as_secs_f64() / AutoBrakeController::LONG_ACC_FILTER_TIMECONST,
+ ));
+
+ self.current_accel_error =
+ self.current_filtered_accel - self.accel_targets[self.current_selected_mode];
+
+ if self.is_enabled && ct.is_on_ground {
+ let pterm = self.current_accel_error.get::()
+ * AutoBrakeController::CONTROLLER_P_GAIN;
+ let dterm = (self.current_accel_error - self.accel_error_prev)
+ .get::()
+ * AutoBrakeController::CONTROLLER_D_GAIN;
+ self.current_integral_term += self.current_accel_error.get::()
+ * AutoBrakeController::CONTROLLER_I_GAIN;
+
+ let current_brake_dmnd = pterm + self.current_integral_term + dterm;
+ self.current_brake_dmnd = current_brake_dmnd.min(1.).max(0.);
+ } else {
+ self.current_brake_dmnd = 0.0;
+ self.current_integral_term = 0.0;
+ }
+ }
+
+ pub fn get_brake_command(&self) -> f64 {
+ self.current_brake_dmnd
+ }
+
+ pub fn set_mode(&mut self, selected_mode: &usize) {
+ self.current_selected_mode = *selected_mode.min(&self.num_of_modes);
+ }
+
+ pub fn set_enable(&mut self, ena: bool) {
+ self.is_enabled = ena;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ //use uom::si::volume_rate::VolumeRate;
+ //use BrakeCircuit;
+ use super::*;
+ use crate::{
+ hydraulic::{HydFluid, HydLoop, LoopColor},
+ simulation::{UpdateContext, UpdateContextBuilder},
+ };
+
+ #[test]
+ //Runs engine driven pump, checks pressure OK, shut it down, check drop of pressure after 20s
+ fn brake_state_at_init() {
+ let init_max_vol = Volume::new::(1.5);
+ let mut brake_circuit_unprimed = BrakeCircuit::new(
+ init_max_vol,
+ Volume::new::(0.0),
+ Volume::new::(0.1),
+ );
+
+ assert!(
+ brake_circuit_unprimed.get_brake_pressure_left()
+ + brake_circuit_unprimed.get_brake_pressure_right()
+ < Pressure::new::(10.0)
+ );
+ assert!(brake_circuit_unprimed.accumulator_total_volume == init_max_vol);
+ assert!(brake_circuit_unprimed.accumulator_fluid_volume == Volume::new::(0.0));
+ assert!(brake_circuit_unprimed.accumulator_gas_volume == init_max_vol);
+
+ let mut brake_circuit_primed =
+ BrakeCircuit::new(init_max_vol, init_max_vol / 2.0, Volume::new::(0.1));
+
+ assert!(
+ brake_circuit_unprimed.get_brake_pressure_left()
+ + brake_circuit_unprimed.get_brake_pressure_right()
+ < Pressure::new::(10.0)
+ );
+ assert!(brake_circuit_primed.accumulator_total_volume == init_max_vol);
+ assert!(brake_circuit_primed.accumulator_fluid_volume == init_max_vol / 2.0);
+ assert!(brake_circuit_primed.accumulator_gas_volume < init_max_vol);
+ }
+
+ #[test]
+ fn brake_pressure_rise() {
+ let init_max_vol = Volume::new::(1.5);
+ let mut hyd_loop = hydraulic_loop(LoopColor::Yellow);
+ hyd_loop.loop_pressure = Pressure::new::(2500.0);
+
+ let mut brake_circuit_primed =
+ BrakeCircuit::new(init_max_vol, init_max_vol / 2.0, Volume::new::(0.1));
+
+ assert!(
+ brake_circuit_primed.get_brake_pressure_left()
+ + brake_circuit_primed.get_brake_pressure_right()
+ < Pressure::new::(10.0)
+ );
+
+ brake_circuit_primed.update(&Duration::from_secs_f64(0.1), &hyd_loop);
+
+ assert!(
+ brake_circuit_primed.get_brake_pressure_left()
+ + brake_circuit_primed.get_brake_pressure_right()
+ < Pressure::new::(10.0)
+ );
+
+ brake_circuit_primed.set_brake_demand_left(1.0);
+ brake_circuit_primed.update(&Duration::from_secs_f64(0.1), &hyd_loop);
+
+ assert!(brake_circuit_primed.get_brake_pressure_left() >= Pressure::new::(1000.));
+ assert!(brake_circuit_primed.get_brake_pressure_right() <= Pressure::new::(50.));
+ assert!(brake_circuit_primed.accumulator_fluid_volume >= Volume::new::(0.1));
+
+ brake_circuit_primed.set_brake_demand_left(0.0);
+ brake_circuit_primed.set_brake_demand_right(1.0);
+ brake_circuit_primed.update(&Duration::from_secs_f64(0.1), &hyd_loop);
+ assert!(brake_circuit_primed.get_brake_pressure_right() >= Pressure::new::(1000.));
+ assert!(brake_circuit_primed.get_brake_pressure_left() <= Pressure::new::(50.));
+ assert!(brake_circuit_primed.accumulator_fluid_volume >= Volume::new::(0.1));
+ }
+
+ #[test]
+ fn brake_pressure_rise_no_accumulator() {
+ let init_max_vol = Volume::new::(0.0);
+ let mut hyd_loop = hydraulic_loop(LoopColor::Yellow);
+ hyd_loop.loop_pressure = Pressure::new::(2500.0);
+
+ let mut brake_circuit_primed =
+ BrakeCircuit::new(init_max_vol, init_max_vol / 2.0, Volume::new::(0.1));
+
+ assert!(
+ brake_circuit_primed.get_brake_pressure_left()
+ + brake_circuit_primed.get_brake_pressure_right()
+ < Pressure::new::(10.0)
+ );
+
+ brake_circuit_primed.update(&Duration::from_secs_f64(0.1), &hyd_loop);
+
+ assert!(
+ brake_circuit_primed.get_brake_pressure_left()
+ + brake_circuit_primed.get_brake_pressure_right()
+ < Pressure::new::(10.0)
+ );
+
+ brake_circuit_primed.set_brake_demand_left(1.0);
+ brake_circuit_primed.update(&Duration::from_secs_f64(0.1), &hyd_loop);
+
+ assert!(brake_circuit_primed.get_brake_pressure_left() >= Pressure::new::(2500.));
+ assert!(brake_circuit_primed.get_brake_pressure_right() <= Pressure::new::(50.));
+
+ brake_circuit_primed.set_brake_demand_left(0.0);
+ brake_circuit_primed.set_brake_demand_right(1.0);
+ brake_circuit_primed.update(&Duration::from_secs_f64(0.1), &hyd_loop);
+ assert!(brake_circuit_primed.get_brake_pressure_right() >= Pressure::new::(2500.));
+ assert!(brake_circuit_primed.get_brake_pressure_left() <= Pressure::new::(50.));
+ assert!(brake_circuit_primed.accumulator_fluid_volume == Volume::new::(0.0));
+ }
+
+ #[test]
+ fn auto_brake_controller() {
+ // let init_max_vol = Volume::new::(0.0);
+ // let mut hyd_loop = hydraulic_loop(LoopColor::Yellow);
+ // hyd_loop.loop_pressure= Pressure::new::(3000.0);
+
+ // let mut brake_circuit_primed = BrakeCircuit::new(
+ // init_max_vol,
+ // init_max_vol,
+ // Volume::new::(0.1)
+ // );
+
+ let mut controller = AutoBrakeController::new(vec![
+ Acceleration::new::(-1.5),
+ Acceleration::new::(-3.),
+ Acceleration::new::(-15.),
+ ]);
+ let mut context = context(Duration::from_secs_f64(0.));
+ context.acc_long = Acceleration::new::(0.0);
+
+ assert!(controller.get_brake_command() <= 0.0);
+
+ controller.update(&context.delta, &context);
+ assert!(controller.get_brake_command() <= 0.0);
+
+ controller.set_enable(true);
+ controller.update(&context.delta, &context);
+ assert!(controller.get_brake_command() >= 0.0);
+ }
+
+ fn hydraulic_loop(loop_color: LoopColor) -> HydLoop {
+ match loop_color {
+ LoopColor::Yellow => HydLoop::new(
+ loop_color,
+ false,
+ true,
+ Volume::new::(26.00),
+ Volume::new::(26.41),
+ Volume::new::(10.0),
+ Volume::new::(3.83),
+ HydFluid::new(Pressure::new::(1450000000.0)),
+ ),
+ LoopColor::Green => HydLoop::new(
+ loop_color,
+ true,
+ false,
+ Volume::new::(10.2),
+ Volume::new::(10.2),
+ Volume::new::(8.0),
+ Volume::new::(3.3),
+ HydFluid::new(Pressure::new::(1450000000.0)),
+ ),
+ _ => HydLoop::new(
+ loop_color,
+ false,
+ false,
+ Volume::new::(15.85),
+ Volume::new::(15.85),
+ Volume::new::(8.0),
+ Volume::new::(1.5),
+ HydFluid::new(Pressure::new::(1450000000.0)),
+ ),
+ }
+ }
+
+ fn context(delta_time: Duration) -> UpdateContext {
+ UpdateContext::new(
+ delta_time,
+ Velocity::new::(250.),
+ Length::new::(5000.),
+ ThermodynamicTemperature::new::(25.0),
+ true,
+ Acceleration::new::(0.),
+ 0.0,
+ 0.0,
+ 0.0,
+ )
+ }
+}
diff --git a/src/systems/systems/src/hydraulic/mod.rs b/src/systems/systems/src/hydraulic/mod.rs
index 8b137891791..c089b2fe62b 100644
--- a/src/systems/systems/src/hydraulic/mod.rs
+++ b/src/systems/systems/src/hydraulic/mod.rs
@@ -1 +1,2165 @@
+use std::f64::consts;
+use std::time::Duration;
+use std::{borrow::Borrow, cmp::Ordering, fmt::Pointer};
+use uom::{
+ si::{
+ acceleration::galileo,
+ area::square_meter,
+ f64::*,
+ force::newton,
+ length::foot,
+ length::meter,
+ mass_density::kilogram_per_cubic_meter,
+ pressure::atmosphere,
+ pressure::pascal,
+ pressure::psi,
+ ratio::percent,
+ thermodynamic_temperature::{self, degree_celsius},
+ time::second,
+ velocity::knot,
+ volume::cubic_inch,
+ volume::gallon,
+ volume::liter,
+ volume_rate::cubic_meter_per_second,
+ volume_rate::gallon_per_second,
+ },
+ typenum::private::IsLessOrEqualPrivate,
+};
+
+use crate::{engine::Engine, simulation::UpdateContext};
+
+pub mod brakecircuit;
+
+// //Interpolate values_map_y at point value_at_point in breakpoints break_points_x
+pub fn interpolation(xs: &[f64], ys: &[f64], intermediate_x: f64) -> f64 {
+ debug_assert!(xs.len() == ys.len());
+ debug_assert!(xs.len() >= 2);
+ debug_assert!(ys.len() >= 2);
+ // The function also assumes xs are ordered from small to large. Consider adding a debug_assert! for that as well.
+
+ if intermediate_x <= xs[0] {
+ *ys.first().unwrap()
+ } else if intermediate_x >= xs[xs.len() - 1] {
+ *ys.last().unwrap()
+ } else {
+ let mut idx: usize = 1;
+
+ while idx < xs.len() - 1 {
+ if intermediate_x < xs[idx] {
+ break;
+ }
+ idx += 1;
+ }
+
+ ys[idx - 1]
+ + (intermediate_x - xs[idx - 1]) / (xs[idx] - xs[idx - 1]) * (ys[idx] - ys[idx - 1])
+ }
+}
+
+// TODO:
+// - Priority valve
+// - Engine fire shutoff valve
+// - Leak measurement valve
+// - RAT pump implementation
+// - Connecting electric pumps to electric sources
+// - Connecting RAT pump/blue loop to emergency generator
+// - Actuators
+// - Bleed air sources for reservoir/line anti-cavitation
+
+////////////////////////////////////////////////////////////////////////////////
+// DATA & REFERENCES
+////////////////////////////////////////////////////////////////////////////////
+///
+/// On A320, the reservoir level variation can, depending on the system,
+/// decrease in flight by about 3.5 l (G RSVR), 4 l (Y RSVR) and 0.5 l (B RSVR)
+///
+/// Each MLG door open (2 total) uses 0.25 liters each of green hyd fluid
+/// Each cargo door open (3 total) uses 0.2 liters each of yellow hyd fluid
+///
+/// Reservoirs
+/// ------------------------------------------
+/// Normal Qty:
+/// -----------
+/// Blue: 6.5L (1.7 US Gal)
+/// Yellow: 12.5L (3.3 US Gal)
+/// Green: 14.5L (3.8 US Gal)
+///
+/// Loops
+/// ------------------------------------------
+/// Max loop volume - green: 100L 26.41gal including reservoir
+/// Max loop volume - yellow: 75L 19.81gal including reservoir
+/// Max loop volume - blue: 50L 15.85gal including reservoir
+///
+/// EDP (Eaton PV3-240-10C/D/F (F is neo)):
+/// ------------------------------------------
+/// 37.5 GPM max (100% N2)
+/// 3750 RPM
+/// 3000 PSI
+/// Displacement: 2.40 in3/rev
+///
+///
+/// Electric Pump (Eaton MPEV3-032-EA2 (neo) MPEV-032-15 (ceo)):
+/// ------------------------------------------
+/// Uses 115/200 VAC, 400HZ electric motor
+/// 8.45 GPM max
+/// 7600 RPM at full displacement, 8000 RPM at no displacement
+/// 3000 PSI
+/// Displacement: 0.263 in3/rev
+///
+///
+/// PTU (Eaton Vickers MPHV3-115-1C):
+/// ------------------------------------------
+/// 2987 PSI
+///
+/// Yellow to Green
+/// ---------------
+/// 34 GPM (130 L/min) from Yellow system
+/// 24 GPM (90 L/min) to Green system
+/// Maintains constant pressure near 3000PSI in green
+///
+/// Green to Yellow
+/// ---------------
+/// 16 GPM (60 L/min) from Green system
+/// 13 GPM (50 L/min) to Yellow system
+/// Maintains constant pressure near 3000PSI in yellow
+///
+///
+/// RAT PUMP (Eaton PV3-115):
+/// ------------------------------------------
+/// Max displacement: 1.15 in3/rev, 18.85 mL/rev
+/// Normal speed: 6,600 RPM
+/// Max. Ov. Speed: 8,250 RPM
+/// Theoretical Flow at normal speed: 32.86 gpm, 124.4 l/m
+///
+///
+/// Equations:
+/// ------------------------------------------
+/// Flow (Q), gpm: Q = (in3/rev * rpm) / 231
+/// Velocity (V), ft/s: V = (0.3208 * flow rate, gpm) / internal area, sq in
+/// Force (F), lbs: F = density * area * velocity^2
+/// Pressure (P), PSI: P = force / area
+/// PressureDelta = VolumeDelta / Total_uncompressed_volume * Fluid_Bulk_Modulus
+///
+///
+/// Hydraulic Fluid: EXXON HyJet IV
+/// ------------------------------------------
+/// Kinematic viscosity at 40C: 10.55 mm^2 s^-1, +/- 20%
+/// Density at 25C: 996 kg m^-3
+///
+/// Hydraulic Line (HP) inner diameter
+/// ------------------------------------------
+/// Currently unknown. Estimating to be 7.5mm for now?
+///
+///
+/// Actuator Force Simvars
+/// -------------------------------------------
+/// ACCELERATION BODY X (relative to aircraft, "east/west", Feet per second squared)
+/// ACCELERATION BODY Y (relative to aircraft, vertical, Feet per second squared)
+/// ACCELERATION BODY Z (relative to aircraft, "north/south", Feet per second squared)
+/// ROTATION VELOCITY BODY X (feet per second)
+/// ROTATION VELOCITY BODY Y (feet per second)
+/// ROTATION VELOCITY BODY Z (feet per second)
+/// VELOCITY BODY X (feet per second)
+/// VELOCITY BODY Y (feet per second)
+/// VELOCITY BODY Z (feet per second)
+/// WING FLEX PCT (:1 for left, :2 for right; settable) (percent over 100)
+///
+
+////////////////////////////////////////////////////////////////////////////////
+// ENUMERATIONS
+////////////////////////////////////////////////////////////////////////////////
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ActuatorType {
+ Aileron,
+ BrakesNormal,
+ BrakesAlternate,
+ BrakesParking,
+ CargoDoor,
+ Elevator,
+ EmergencyGenerator,
+ EngReverser,
+ Flaps,
+ LandingGearNose,
+ LandingGearMain,
+ LandingGearDoorNose,
+ LandingGearDoorMain,
+ NoseWheelSteering,
+ Rudder,
+ Slat,
+ Spoiler,
+ Stabilizer,
+ YawDamper,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum LoopColor {
+ Blue,
+ Green,
+ Yellow,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum PtuState {
+ Off,
+ GreenToYellow,
+ YellowToGreen,
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TRAITS
+////////////////////////////////////////////////////////////////////////////////
+
+// Trait common to all hydraulic pumps
+// Max gives maximum available volume at that time as if it is a variable displacement
+// pump it can be adjusted by pump regulation
+// Min will give minimum volume that will be outputed no matter what. example if there is a minimal displacement or
+// a fixed displacement (ie. elec pump)
+pub trait PressureSource {
+ fn get_delta_vol_max(&self) -> Volume;
+ fn get_delta_vol_min(&self) -> Volume;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LOOP DEFINITION - INCLUDES RESERVOIR AND ACCUMULATOR
+////////////////////////////////////////////////////////////////////////////////
+
+//Implements fluid structure.
+//TODO update method that can update physic constants from given temperature
+//This would change pressure response to volume
+pub struct HydFluid {
+ //temp : thermodynamic_temperature,
+ current_bulk: Pressure,
+}
+
+impl HydFluid {
+ pub fn new(bulk: Pressure) -> HydFluid {
+ HydFluid {
+ //temp:temp,
+ current_bulk: bulk,
+ }
+ }
+
+ pub fn get_bulk_mod(&self) -> Pressure {
+ return self.current_bulk;
+ }
+}
+
+//Power Transfer Unit
+//TODO enhance simulation with RPM and variable displacement on one side?
+pub struct Ptu {
+ isEnabled: bool,
+ isActiveRight: bool,
+ isActiveLeft: bool,
+ flow_to_right: VolumeRate,
+ flow_to_left: VolumeRate,
+ last_flow: VolumeRate,
+}
+
+impl Ptu {
+ //Low pass filter to handle flow dynamic: avoids instantaneous flow transient,
+ // simulating RPM dynamic of PTU
+ const FLOW_DYNAMIC_LOW_PASS_LEFT_SIDE: f64 = 0.1;
+ const FLOW_DYNAMIC_LOW_PASS_RIGHT_SIDE: f64 = 0.08;
+
+ //Part of the max total pump capacity PTU model is allowed to take. Set to 1 all capacity used
+ // set to 0.5 PTU will only use half of the flow that all pumps are able to generate
+ const AGRESSIVENESS_FACTOR: f64 = 0.7;
+
+ pub fn new() -> Ptu {
+ Ptu {
+ isEnabled: false,
+ isActiveRight: false,
+ isActiveLeft: false,
+ flow_to_right: VolumeRate::new::(0.0),
+ flow_to_left: VolumeRate::new::(0.0),
+ last_flow: VolumeRate::new::(0.0),
+ }
+ }
+
+ pub fn get_flow(&self) -> VolumeRate {
+ self.last_flow
+ }
+
+ pub fn get_is_active(&self) -> bool {
+ self.isActiveRight || self.isActiveLeft
+ }
+
+ pub fn is_enabled(&self) -> bool {
+ self.isEnabled
+ }
+
+ pub fn get_is_active_left_to_right(&self) -> bool {
+ self.isActiveLeft
+ }
+
+ pub fn get_is_active_right_to_left(&self) -> bool {
+ self.isActiveRight
+ }
+
+ pub fn update(&mut self, loopLeft: &HydLoop, loopRight: &HydLoop) {
+ let deltaP = loopLeft.get_pressure() - loopRight.get_pressure();
+
+ //TODO: use maped characteristics for PTU?
+ //TODO Use variable displacement available on one side?
+ //TODO Handle RPM of ptu so transient are bit slower?
+ //TODO Handle it as a min/max flow producer using PressureSource trait?
+ if self.isActiveLeft || (!self.isActiveRight && deltaP.get::() > 500.0) {
+ //Left sends flow to right
+ let mut vr = 16.0f64.min(loopLeft.loop_pressure.get::() * 0.0058) / 60.0;
+
+ //Limiting available flow with maximum flow capacity of all pumps of the loop.
+ //This is a workaround to limit PTU greed for flow
+ vr = vr.min(
+ loopLeft.current_max_flow.get::() * Ptu::AGRESSIVENESS_FACTOR,
+ );
+
+ //Low pass on flow
+ vr = Ptu::FLOW_DYNAMIC_LOW_PASS_LEFT_SIDE * vr
+ + (1.0 - Ptu::FLOW_DYNAMIC_LOW_PASS_LEFT_SIDE)
+ * self.last_flow.get::();
+
+ self.flow_to_left = VolumeRate::new::(-vr);
+ self.flow_to_right = VolumeRate::new::(vr * 0.81);
+ self.last_flow = VolumeRate::new::(vr);
+
+ self.isActiveLeft = true;
+ } else if self.isActiveRight || (!self.isActiveLeft && deltaP.get::() < -500.0) {
+ //Right sends flow to left
+ let mut vr = 34.0f64.min(loopRight.loop_pressure.get::() * 0.0125) / 60.0;
+
+ //Limiting available flow with maximum flow capacity of all pumps of the loop.
+ //This is a workaround to limit PTU greed for flow
+ vr = vr.min(
+ loopRight.current_max_flow.get::() * Ptu::AGRESSIVENESS_FACTOR,
+ );
+
+ //Low pass on flow
+ vr = Ptu::FLOW_DYNAMIC_LOW_PASS_RIGHT_SIDE * vr
+ + (1.0 - Ptu::FLOW_DYNAMIC_LOW_PASS_RIGHT_SIDE)
+ * self.last_flow.get::();
+
+ self.flow_to_left = VolumeRate::new::(vr * 0.70);
+ self.flow_to_right = VolumeRate::new::(-vr);
+ self.last_flow = VolumeRate::new::(vr);
+
+ self.isActiveRight = true;
+ }
+
+ //TODO REVIEW DEACTICATION LOGIC
+ if !self.isEnabled
+ || self.isActiveRight && loopLeft.loop_pressure.get::() > 2950.0
+ || self.isActiveLeft && loopRight.loop_pressure.get::() > 2950.0
+ || self.isActiveRight && loopRight.loop_pressure.get::() < 500.0
+ || self.isActiveLeft && loopLeft.loop_pressure.get::() < 500.0
+ {
+ self.flow_to_left = VolumeRate::new::(0.0);
+ self.flow_to_right = VolumeRate::new::(0.0);
+ self.isActiveRight = false;
+ self.isActiveLeft = false;
+ self.last_flow = VolumeRate::new::(0.0);
+ }
+ }
+
+ pub fn enabling(&mut self, enable_flag: bool) {
+ self.isEnabled = enable_flag;
+ }
+}
+
+pub struct HydLoop {
+ fluid: HydFluid,
+ accumulator_gas_pressure: Pressure,
+ accumulator_gas_volume: Volume,
+ accumulator_fluid_volume: Volume,
+ accumulator_press_breakpoints: [f64; 9],
+ accumulator_flow_carac: [f64; 9],
+ color: LoopColor,
+ connected_to_ptu_left_side: bool,
+ connected_to_ptu_right_side: bool,
+ loop_pressure: Pressure,
+ loop_volume: Volume,
+ max_loop_volume: Volume,
+ high_pressure_volume: Volume,
+ ptu_active: bool,
+ reservoir_volume: Volume,
+ current_delta_vol: Volume,
+ current_flow: VolumeRate,
+ current_max_flow: VolumeRate, //Current total max flow available from pressure sources
+ fire_shutoff_valve_opened: bool,
+}
+
+impl HydLoop {
+ const ACCUMULATOR_GAS_PRE_CHARGE: f64 = 1885.0; // Nitrogen PSI
+ const ACCUMULATOR_MAX_VOLUME: f64 = 0.264; // in gallons
+ //const HYDRAULIC_FLUID_DENSITY: f64 = 1000.55; // Exxon Hyjet IV, kg/m^3
+
+ //Low pass filter on pressure. This has to be pretty high not to modify behavior of the loop, but still dampening numerical instability
+ const PRESSURE_LOW_PASS_FILTER: f64 = 0.75;
+
+ const DELTA_VOL_LOW_PASS_FILTER: f64 = 0.1;
+
+ const ACCUMULATOR_PRESS_BREAKPTS: [f64; 9] =
+ [0.0, 5.0, 10.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 10000.0];
+ const ACCUMULATOR_FLOW_CARAC: [f64; 9] = [0.0, 0.005, 0.008, 0.01, 0.02, 0.08, 0.15, 0.35, 0.5];
+
+ pub fn new(
+ color: LoopColor,
+ connected_to_ptu_left_side: bool, //Is connected to PTU "left" side: non variable displacement side
+ connected_to_ptu_right_side: bool, //Is connected to PTU "right" side: variable displacement side
+ loop_volume: Volume,
+ max_loop_volume: Volume,
+ high_pressure_volume: Volume,
+ reservoir_volume: Volume,
+ fluid: HydFluid,
+ ) -> HydLoop {
+ HydLoop {
+ accumulator_gas_pressure: Pressure::new::(HydLoop::ACCUMULATOR_GAS_PRE_CHARGE),
+ accumulator_gas_volume: Volume::new::(HydLoop::ACCUMULATOR_MAX_VOLUME),
+ accumulator_fluid_volume: Volume::new::(0.),
+ color,
+ connected_to_ptu_left_side,
+ connected_to_ptu_right_side,
+ loop_pressure: Pressure::new::(14.7),
+ loop_volume,
+ max_loop_volume,
+ high_pressure_volume,
+ ptu_active: false,
+ reservoir_volume,
+ fluid,
+ current_delta_vol: Volume::new::(0.),
+ current_flow: VolumeRate::new::(0.),
+ accumulator_press_breakpoints: HydLoop::ACCUMULATOR_PRESS_BREAKPTS,
+ accumulator_flow_carac: HydLoop::ACCUMULATOR_FLOW_CARAC,
+ current_max_flow: VolumeRate::new::(0.),
+ fire_shutoff_valve_opened: true,
+ }
+ }
+
+ pub fn get_pressure(&self) -> Pressure {
+ self.loop_pressure
+ }
+
+ pub fn get_reservoir_volume(&self) -> Volume {
+ self.reservoir_volume
+ }
+
+ pub fn get_usable_reservoir_fluid(&self, amount: Volume) -> Volume {
+ let mut drawn = amount;
+ if amount > self.reservoir_volume {
+ drawn = self.reservoir_volume;
+ }
+ drawn
+ }
+
+ //Returns the max flow that can be output from reservoir in dt time
+ pub fn get_usable_reservoir_flow(&self, amount: VolumeRate, delta_time: Time) -> VolumeRate {
+ let mut drawn = amount;
+
+ let max_flow = self.reservoir_volume / delta_time;
+ if amount > max_flow {
+ drawn = max_flow;
+ }
+ drawn
+ }
+
+ //Method to update pressure of a loop. The more delta volume is added, the more pressure rises
+ //Directly from bulk modulus equation
+ pub fn delta_pressure_from_delta_volume(&self, delta_vol: Volume) -> Pressure {
+ return delta_vol / self.high_pressure_volume * self.fluid.get_bulk_mod();
+ }
+
+ //Gives the exact volume of fluid needed to get to any target_press pressure
+ pub fn vol_to_target(&self, target_press: Pressure) -> Volume {
+ (target_press - self.loop_pressure) * (self.high_pressure_volume)
+ / self.fluid.get_bulk_mod()
+ }
+
+ pub fn set_fire_shutoff_valve_state(&mut self, opened: bool) {
+ self.fire_shutoff_valve_opened = opened;
+ }
+
+ pub fn get_fire_shutoff_valve_state(&self) -> bool {
+ self.fire_shutoff_valve_opened
+ }
+
+ pub fn update(
+ &mut self,
+ delta_time: &Duration,
+ context: &UpdateContext,
+ electric_pumps: Vec<&ElectricPump>,
+ engine_driven_pumps: Vec<&EngineDrivenPump>,
+ ram_air_pumps: Vec<&RatPump>,
+ ptus: Vec<&Ptu>,
+ ) {
+ let mut pressure = self.loop_pressure;
+ let mut delta_vol_max = Volume::new::(0.);
+ let mut delta_vol_min = Volume::new::(0.);
+ let mut reservoir_return = Volume::new::(0.);
+ let mut delta_vol = Volume::new::(0.);
+
+ if self.fire_shutoff_valve_opened {
+ for p in engine_driven_pumps {
+ delta_vol_max += p.get_delta_vol_max();
+ delta_vol_min += p.get_delta_vol_min();
+ }
+ }
+ for p in electric_pumps {
+ delta_vol_max += p.get_delta_vol_max();
+ delta_vol_min += p.get_delta_vol_min();
+ }
+ for p in ram_air_pumps {
+ delta_vol_max += p.get_delta_vol_max();
+ delta_vol_min += p.get_delta_vol_min();
+ }
+
+ //Storing max pump capacity available. for now used in PTU model to limit it's input flow
+ self.current_max_flow = delta_vol_max / Time::new::(delta_time.as_secs_f64());
+
+ //Static leaks
+ //TODO: separate static leaks per zone of high pressure or actuator
+ //TODO: Use external pressure and/or reservoir pressure instead of 14.7 psi default
+ let static_leaks_vol = Volume::new::(
+ 0.04 * delta_time.as_secs_f64() * (self.loop_pressure.get::() - 14.7) / 3000.0,
+ );
+
+ // Draw delta_vol from reservoir
+ delta_vol -= static_leaks_vol;
+ reservoir_return += static_leaks_vol;
+
+ //PTU flows handling
+ let mut ptu_act = false;
+ for ptu in ptus {
+ let mut actualFlow = VolumeRate::new::(0.0);
+ if self.connected_to_ptu_left_side {
+ if ptu.isActiveLeft || ptu.isActiveLeft {
+ ptu_act = true;
+ }
+ if ptu.flow_to_left > VolumeRate::new::(0.0) {
+ //were are left side of PTU and positive flow so we receive flow using own reservoir
+ actualFlow = self.get_usable_reservoir_flow(
+ ptu.flow_to_left,
+ Time::new::(delta_time.as_secs_f64()),
+ );
+ self.reservoir_volume -=
+ actualFlow * Time::new::(delta_time.as_secs_f64());
+ } else {
+ //we are using own flow to power right side so we send that back
+ //to our own reservoir
+ actualFlow = ptu.flow_to_left;
+ reservoir_return -= actualFlow * Time::new::(delta_time.as_secs_f64());
+ }
+ delta_vol += actualFlow * Time::new::(delta_time.as_secs_f64());
+ } else if self.connected_to_ptu_right_side {
+ if ptu.isActiveLeft || ptu.isActiveLeft {
+ ptu_act = true;
+ }
+ if ptu.flow_to_right > VolumeRate::new::(0.0) {
+ //were are right side of PTU and positive flow so we receive flow using own reservoir
+ actualFlow = self.get_usable_reservoir_flow(
+ ptu.flow_to_right,
+ Time::new::(delta_time.as_secs_f64()),
+ );
+ self.reservoir_volume -=
+ actualFlow * Time::new::(delta_time.as_secs_f64());
+ } else {
+ //we are using own flow to power left side so we send that back
+ //to our own reservoir
+ actualFlow = ptu.flow_to_right;
+ reservoir_return -= actualFlow * Time::new::(delta_time.as_secs_f64());
+ }
+ delta_vol += actualFlow * Time::new::(delta_time.as_secs_f64());
+ }
+ }
+ self.ptu_active = ptu_act;
+ //END PTU
+
+ //Priming the loop if not filled in
+ //TODO bug, ptu can't prime the loop is it is not providing flow through delta_vol_max
+ if self.loop_volume < self.max_loop_volume {
+ //} %TODO what to do if we are back under max volume and unprime the loop?
+ let difference = self.max_loop_volume - self.loop_volume;
+ // println!("---Priming diff {}", difference.get::());
+ let availableFluidVol = self.reservoir_volume.min(delta_vol_max);
+ let delta_loop_vol = availableFluidVol.min(difference);
+ delta_vol_max -= delta_loop_vol; //%TODO check if we cross the deltaVolMin?
+ self.loop_volume += delta_loop_vol;
+ self.reservoir_volume -= delta_loop_vol;
+ // println!("---Priming vol {} / {}", self.loop_volume.get::(),self.max_loop_volume.get::());
+ } else {
+ // println!("---Primed {}", self.loop_volume.get::());
+ }
+ //end priming
+
+ //ACCUMULATOR
+ let accumulatorDeltaPress = self.accumulator_gas_pressure - self.loop_pressure;
+ let flowVariation = VolumeRate::new::(interpolation(
+ &self.accumulator_press_breakpoints,
+ &self.accumulator_flow_carac,
+ accumulatorDeltaPress.get::().abs(),
+ ));
+
+ //TODO HANDLE OR CHECK IF RESERVOIR AVAILABILITY is OK
+ //TODO check if accumulator can be used as a min/max flow producer to
+ //avoid it being a consumer that might unsettle pressure
+ if accumulatorDeltaPress.get::() > 0.0 {
+ let volumeFromAcc = self
+ .accumulator_fluid_volume
+ .min(flowVariation * Time::new::(delta_time.as_secs_f64()));
+ self.accumulator_fluid_volume -= volumeFromAcc;
+ self.accumulator_gas_volume += volumeFromAcc;
+ delta_vol += volumeFromAcc;
+ } else {
+ let volumeToAcc = delta_vol
+ .max(Volume::new::(0.0))
+ .max(flowVariation * Time::new::(delta_time.as_secs_f64()));
+ self.accumulator_fluid_volume += volumeToAcc;
+ self.accumulator_gas_volume -= volumeToAcc;
+ delta_vol -= volumeToAcc;
+ }
+
+ self.accumulator_gas_pressure = (Pressure::new::(HydLoop::ACCUMULATOR_GAS_PRE_CHARGE)
+ * Volume::new::(HydLoop::ACCUMULATOR_MAX_VOLUME))
+ / (Volume::new::(HydLoop::ACCUMULATOR_MAX_VOLUME)
+ - self.accumulator_fluid_volume);
+ //END ACCUMULATOR
+
+ //Actuators
+ let used_fluidQty = Volume::new::(0.); // %%total fluid used
+ //foreach actuator
+ //used_fluidQty =used_fluidQty+aileron.volumeToActuatorAccumulated*264.172; %264.172 is m^3 to gallons
+ //reservoirReturn=reservoirReturn+aileron.volumeToResAccumulated*264.172;
+ //actuator.resetVolumes()
+ //actuator.set_available_pressure(self.loop_pressure)
+ //end foreach
+ //end actuator
+
+ delta_vol -= used_fluidQty;
+
+ //How much we need to reach target of 3000?
+ let mut volume_needed_to_reach_pressure_target =
+ self.vol_to_target(Pressure::new::(3000.0));
+ //Actually we need this PLUS what is used by consumers.
+ volume_needed_to_reach_pressure_target -= delta_vol;
+
+ //Now computing what we will actually use from flow providers limited by
+ //their min and max flows and reservoir availability
+ let actual_volume_added_to_pressurise = self
+ .reservoir_volume
+ .min(delta_vol_min.max(delta_vol_max.min(volume_needed_to_reach_pressure_target)));
+ delta_vol += actual_volume_added_to_pressurise;
+
+ //Loop Pressure update From Bulk modulus
+ let press_delta = self.delta_pressure_from_delta_volume(delta_vol);
+ let new_raw_press = self.loop_pressure + press_delta; //New raw pressure before we filter it
+
+ self.loop_pressure = HydLoop::PRESSURE_LOW_PASS_FILTER * new_raw_press
+ + (1. - HydLoop::PRESSURE_LOW_PASS_FILTER) * self.loop_pressure;
+ self.loop_pressure = self.loop_pressure.max(Pressure::new::(14.7)); //Forcing a min pressure
+
+ //Update reservoir
+ self.reservoir_volume -= actual_volume_added_to_pressurise; //%limit to 0 min? for case of negative added?
+ self.reservoir_volume += reservoir_return;
+
+ //Update Volumes
+
+ //Low pass filter on final delta vol to help with stability and final flow noise
+ delta_vol = HydLoop::DELTA_VOL_LOW_PASS_FILTER * delta_vol
+ + (1. - HydLoop::DELTA_VOL_LOW_PASS_FILTER) * self.current_delta_vol;
+ self.loop_volume += delta_vol;
+
+ self.current_delta_vol = delta_vol;
+ self.current_flow = delta_vol / Time::new::(delta_time.as_secs_f64());
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PUMP DEFINITION
+////////////////////////////////////////////////////////////////////////////////
+
+pub struct Pump {
+ delta_vol_max: Volume,
+ delta_vol_min: Volume,
+ pressBreakpoints: [f64; 9],
+ displacementCarac: [f64; 9],
+ displacement_dynamic: f64, //Displacement low pass filter. [0:1], 0 frozen -> 1 instantaneous dynamic
+}
+impl Pump {
+ fn new(
+ pressBreakpoints: [f64; 9],
+ displacementCarac: [f64; 9],
+ displacement_dynamic: f64,
+ ) -> Pump {
+ Pump {
+ delta_vol_max: Volume::new::(0.),
+ delta_vol_min: Volume::new::(0.),
+ pressBreakpoints: pressBreakpoints,
+ displacementCarac: displacementCarac,
+ displacement_dynamic: displacement_dynamic,
+ }
+ }
+
+ fn update(&mut self, delta_time: &Duration, context: &UpdateContext, line: &HydLoop, rpm: f64) {
+ let displacement = self.calculate_displacement(line.get_pressure());
+
+ let flow = Pump::calculate_flow(rpm, displacement);
+
+ self.delta_vol_max = (1.0 - self.displacement_dynamic) * self.delta_vol_max
+ + self.displacement_dynamic * flow * Time::new::(delta_time.as_secs_f64());
+ self.delta_vol_min = Volume::new::(0.0);
+ }
+
+ fn calculate_displacement(&self, pressure: Pressure) -> Volume {
+ Volume::new::(interpolation(
+ &self.pressBreakpoints,
+ &self.displacementCarac,
+ pressure.get::(),
+ ))
+ }
+
+ fn calculate_flow(rpm: f64, displacement: Volume) -> VolumeRate {
+ VolumeRate::new::(rpm * displacement.get::() / 231.0 / 60.0)
+ }
+}
+impl PressureSource for Pump {
+ fn get_delta_vol_max(&self) -> Volume {
+ self.delta_vol_max
+ }
+
+ fn get_delta_vol_min(&self) -> Volume {
+ self.delta_vol_min
+ }
+}
+
+pub struct ElectricPump {
+ active: bool,
+ rpm: f64,
+ pump: Pump,
+}
+impl ElectricPump {
+ const SPOOLUP_TIME: f64 = 4.0;
+ const SPOOLDOWN_TIME: f64 = 4.0;
+ const NOMINAL_SPEED: f64 = 7600.0;
+ const DISPLACEMENT_BREAKPTS: [f64; 9] = [
+ 0.0, 500.0, 1000.0, 1500.0, 2800.0, 2900.0, 3000.0, 3050.0, 3500.0,
+ ];
+ const DISPLACEMENT_MAP: [f64; 9] = [0.263, 0.263, 0.263, 0.263, 0.263, 0.263, 0.163, 0.0, 0.0];
+ const DISPLACEMENT_DYNAMICS: f64 = 1.0; //1 == No filtering
+
+ pub fn new() -> ElectricPump {
+ ElectricPump {
+ active: false,
+ rpm: 0.,
+ pump: Pump::new(
+ ElectricPump::DISPLACEMENT_BREAKPTS,
+ ElectricPump::DISPLACEMENT_MAP,
+ ElectricPump::DISPLACEMENT_DYNAMICS,
+ ),
+ }
+ }
+
+ pub fn start(&mut self) {
+ self.active = true;
+ }
+
+ pub fn stop(&mut self) {
+ self.active = false;
+ }
+
+ pub fn update(&mut self, delta_time: &Duration, context: &UpdateContext, line: &HydLoop) {
+ //TODO Simulate speed of pump depending on pump load (flow?/ current?)
+ //Pump startup/shutdown process
+ if self.active && self.rpm < ElectricPump::NOMINAL_SPEED {
+ self.rpm += (ElectricPump::NOMINAL_SPEED / ElectricPump::SPOOLUP_TIME)
+ * delta_time.as_secs_f64();
+ } else if !self.active && self.rpm > 0.0 {
+ self.rpm -= (ElectricPump::NOMINAL_SPEED / ElectricPump::SPOOLDOWN_TIME)
+ * delta_time.as_secs_f64();
+ }
+
+ //Limiting min and max speed
+ self.rpm = self.rpm.min(ElectricPump::NOMINAL_SPEED).max(0.0);
+
+ self.pump.update(delta_time, context, line, self.rpm);
+ }
+
+ pub fn is_active(&self) -> bool {
+ self.active
+ }
+}
+impl PressureSource for ElectricPump {
+ fn get_delta_vol_max(&self) -> Volume {
+ self.pump.get_delta_vol_max()
+ }
+ fn get_delta_vol_min(&self) -> Volume {
+ self.pump.get_delta_vol_min()
+ }
+}
+
+pub struct EngineDrivenPump {
+ active: bool,
+ pump: Pump,
+}
+impl EngineDrivenPump {
+ const LEAP_1A26_MAX_N2_RPM: f64 = 16645.0;
+ const DISPLACEMENT_BREAKPTS: [f64; 9] = [
+ 0.0, 500.0, 1000.0, 1500.0, 2800.0, 2900.0, 3000.0, 3050.0, 3500.0,
+ ];
+ const DISPLACEMENT_MAP: [f64; 9] = [2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 2.0, 0.0, 0.0];
+ const MAX_RPM: f64 = 4000.;
+
+ const DISPLACEMENT_DYNAMICS: f64 = 0.05; //0.1 == 90% filtering on max displacement transient
+
+ pub fn new() -> EngineDrivenPump {
+ EngineDrivenPump {
+ active: false,
+ pump: Pump::new(
+ EngineDrivenPump::DISPLACEMENT_BREAKPTS,
+ EngineDrivenPump::DISPLACEMENT_MAP,
+ EngineDrivenPump::DISPLACEMENT_DYNAMICS,
+ ),
+ }
+ }
+
+ pub fn update(
+ &mut self,
+ delta_time: &Duration,
+ context: &UpdateContext,
+ line: &HydLoop,
+ engine: &Engine,
+ ) {
+ let mut rpm = EngineDrivenPump::MAX_RPM.min(
+ engine.corrected_n2().get::().powi(2) * 0.08 * EngineDrivenPump::MAX_RPM
+ / 100.0,
+ );
+
+ //TODO Activate pumps realistically, maybe with a displacement rate limited when activated/deactivated?
+ if !self.active {
+ //Hack for pump activation
+ rpm = 0.0;
+ }
+ self.pump.update(delta_time, context, line, rpm);
+ }
+
+ pub fn start(&mut self) {
+ self.active = true;
+ }
+
+ pub fn stop(&mut self) {
+ self.active = false;
+ }
+
+ pub fn is_active(&self) -> bool {
+ self.active
+ }
+}
+impl PressureSource for EngineDrivenPump {
+ fn get_delta_vol_min(&self) -> Volume {
+ self.pump.get_delta_vol_min()
+ }
+ fn get_delta_vol_max(&self) -> Volume {
+ self.pump.get_delta_vol_max()
+ }
+}
+
+pub struct RatPropeller {
+ pos: f64,
+ speed: f64,
+ acc: f64,
+ rpm: f64,
+ torque_sum: f64,
+}
+
+impl RatPropeller {
+ const LOW_SPEED_PHYSICS_ACTIVATION: f64 = 50.; //Low speed special calculation threshold. Under that value we compute resistant torque depending on pump angle and displacement
+ const STOWED_ANGLE: f64 = std::f64::consts::PI / 2.;
+ const PROPELLER_INERTIA: f64 = 2.;
+ const RPM_GOVERNOR_BREAKPTS: [f64; 9] = [
+ 0.0, 4000., 5000.0, 5500.0, 6000.0, 7500.0, 8000.0, 9000.0, 15000.0,
+ ];
+ const PROP_ALPHA_MAP: [f64; 9] = [45., 45., 45., 45., 35., 25., 5., 1., 1.];
+
+ pub fn new() -> RatPropeller {
+ RatPropeller {
+ pos: RatPropeller::STOWED_ANGLE,
+ speed: 0.,
+ acc: 0.,
+ rpm: 0.,
+ torque_sum: 0.,
+ }
+ }
+
+ pub fn get_rpm(&self) -> f64 {
+ self.rpm
+ }
+
+ fn update_generated_torque(&mut self, indicated_speed: &Velocity, stow_pos: f64) {
+ let cur_aplha = interpolation(
+ &RatPropeller::RPM_GOVERNOR_BREAKPTS,
+ &RatPropeller::PROP_ALPHA_MAP,
+ self.rpm,
+ );
+
+ let air_speed_torque = cur_aplha.to_radians().sin()
+ * (indicated_speed.get::() * indicated_speed.get::() / 100.)
+ * 0.5
+ * (std::f64::consts::PI / 2. * stow_pos).sin(); //simple model. stow pos sin simulates the angle of the blades vs wind while deploying
+ self.torque_sum += air_speed_torque;
+ }
+
+ fn update_friction_torque(
+ &mut self,
+ delta_time: &Duration,
+ indicated_speed: &Velocity,
+ displacement_ratio: f64,
+ ) {
+ let mut pump_torque = 0.;
+ if self.rpm < RatPropeller::LOW_SPEED_PHYSICS_ACTIVATION {
+ pump_torque += (self.pos * 4.).cos() * displacement_ratio.max(0.35) * 35.;
+ pump_torque += -self.speed * 15.;
+ } else {
+ pump_torque += displacement_ratio.max(0.35) * 1. * -self.speed;
+ }
+ pump_torque -= self.speed * 0.05;
+ self.torque_sum += pump_torque; //Static air drag of the propeller
+ }
+
+ fn update_physics(&mut self, delta_time: &Duration) {
+ self.acc = self.torque_sum / RatPropeller::PROPELLER_INERTIA;
+ self.speed += self.acc * delta_time.as_secs_f64();
+ self.pos += self.speed * delta_time.as_secs_f64();
+
+ self.rpm = self.speed * 30. / std::f64::consts::PI; //rad/s to RPM
+ self.torque_sum = 0.; //Reset torque accumulator at end of update
+ }
+
+ pub fn update(
+ &mut self,
+ delta_time: &Duration,
+ indicated_speed: &Velocity,
+ stow_pos: f64,
+ displacement_ratio: f64,
+ ) {
+ self.update_generated_torque(indicated_speed, stow_pos);
+ self.update_friction_torque(delta_time, indicated_speed, displacement_ratio);
+ self.update_physics(delta_time);
+ }
+}
+pub struct RatPump {
+ active: bool,
+ pump: Pump,
+ pub prop: RatPropeller,
+ stowed_position: f64,
+ max_displacement: f64,
+}
+impl RatPump {
+ const DISPLACEMENT_BREAKPTS: [f64; 9] = [
+ 0.0, 500.0, 1000.0, 1500.0, 2800.0, 2900.0, 3000.0, 3050.0, 3500.0,
+ ];
+ const DISPLACEMENT_MAP: [f64; 9] = [1.15, 1.15, 1.15, 1.15, 1.15, 1.15, 0.5, 0.0, 0.0];
+
+ const DISPLACEMENT_DYNAMICS: f64 = 0.2; //1 == no filtering. !!Warning, this will be affected by a different delta time
+
+ const STOWING_SPEED: f64 = 1.; //Speed to go from 0 to 1 stow position per sec. 1 means full deploying in 1s
+
+ pub fn new() -> RatPump {
+ let mut max_disp = 0.;
+ for v in RatPump::DISPLACEMENT_MAP.iter() {
+ if v > &max_disp {
+ max_disp = *v;
+ }
+ }
+
+ RatPump {
+ active: false,
+ pump: Pump::new(
+ RatPump::DISPLACEMENT_BREAKPTS,
+ RatPump::DISPLACEMENT_MAP,
+ RatPump::DISPLACEMENT_DYNAMICS,
+ ),
+ prop: RatPropeller::new(),
+ stowed_position: 0.,
+ max_displacement: max_disp,
+ }
+ }
+
+ pub fn update(&mut self, delta_time: &Duration, context: &UpdateContext, line: &HydLoop) {
+ self.pump
+ .update(delta_time, context, line, self.prop.get_rpm());
+
+ //Now forcing min to max to force a true real time regulation.
+ self.pump.delta_vol_min = self.pump.delta_vol_max; //TODO: handle this properly by calculating who produced what volume at end of hyd loop update
+ }
+
+ pub fn update_physics(&mut self, delta_time: &Duration, indicated_airspeed: &Velocity) {
+ let displacement_ratio = self.get_delta_vol_max().get::() / self.max_displacement; //Calculate the ratio of current displacement vs max displacement as an image of the load of the pump
+ self.prop.update(
+ &delta_time,
+ &indicated_airspeed,
+ self.stowed_position,
+ displacement_ratio,
+ );
+ }
+
+ pub fn update_stow_pos(&mut self, delta_time: &Duration) {
+ if self.active {
+ self.stowed_position += delta_time.as_secs_f64() * RatPump::STOWING_SPEED;
+
+ //Finally limiting pos in [0:1] range
+ if self.stowed_position < 0. {
+ self.stowed_position = 0.;
+ } else if self.stowed_position > 1. {
+ self.stowed_position = 1.;
+ }
+ }
+ }
+
+ pub fn set_active(&mut self) {
+ self.active = true;
+ }
+
+ pub fn get_stow_position(&self) -> f64 {
+ self.stowed_position
+ }
+}
+impl PressureSource for RatPump {
+ fn get_delta_vol_max(&self) -> Volume {
+ self.pump.get_delta_vol_max()
+ }
+
+ fn get_delta_vol_min(&self) -> Volume {
+ self.pump.get_delta_vol_min()
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ACTUATOR DEFINITION
+////////////////////////////////////////////////////////////////////////////////
+
+pub struct Actuator {
+ a_type: ActuatorType,
+ active: bool,
+ affected_by_gravity: bool,
+ area: Area,
+ line: HydLoop,
+ neutral_is_zero: bool,
+ stall_load: Force,
+ volume_used_at_max_deflection: Volume,
+}
+
+// TODO
+impl Actuator {
+ pub fn new(a_type: ActuatorType, line: HydLoop) -> Actuator {
+ Actuator {
+ a_type,
+ active: false,
+ affected_by_gravity: false,
+ area: Area::new::(5.0),
+ line,
+ neutral_is_zero: true,
+ stall_load: Force::new::(47000.),
+ volume_used_at_max_deflection: Volume::new::(0.),
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TESTS
+////////////////////////////////////////////////////////////////////////////////
+
+use plotlib::page::Page;
+use plotlib::repr::Plot;
+use plotlib::style::{LineStyle, PointMarker, PointStyle};
+use plotlib::view::ContinuousView;
+
+extern crate rustplotlib;
+use rustplotlib::Figure;
+
+fn make_figure<'a>(h: &'a History) -> Figure<'a> {
+ use rustplotlib::{Axes2D, Line2D};
+
+ let mut allAxis: Vec